Skip to main content

sim_table_mount/
ops.rs

1//! Loadable function exports for mounted Table/Dir namespaces.
2
3use std::sync::Arc;
4
5use sim_kernel::{
6    AbiVersion, Args, Callable, ClassRef, Cx, DefaultFactory, Dependency, Error, Export, Expr,
7    Factory, Lib, LibManifest, LibTarget, Linker, LoadCx, Object, Result, Symbol, Value, Version,
8};
9use sim_table_core::TablePath;
10
11use crate::{MountedDir, routing::inspection_expr, table_mount_capability};
12
13/// Symbol for the namespace creation function.
14pub fn mount_create_symbol() -> Symbol {
15    Symbol::qualified("table/mount", "create")
16}
17
18/// Symbol for the directory-mount function.
19pub fn mount_dir_symbol() -> Symbol {
20    Symbol::qualified("table/mount", "dir")
21}
22
23/// Symbol for the table-mount function.
24pub fn mount_table_symbol() -> Symbol {
25    Symbol::qualified("table/mount", "table")
26}
27
28/// Symbol for the unmount function.
29pub fn mount_unmount_symbol() -> Symbol {
30    Symbol::qualified("table/mount", "unmount")
31}
32
33/// Symbol for the mount inspection function.
34pub fn mount_inspect_symbol() -> Symbol {
35    Symbol::qualified("table/mount", "inspect")
36}
37
38/// Loadable library for the mounted namespace function surface.
39pub struct MountDirLib;
40
41impl Lib for MountDirLib {
42    fn manifest(&self) -> LibManifest {
43        LibManifest {
44            id: Symbol::qualified("table", "mount"),
45            version: Version(env!("CARGO_PKG_VERSION").to_owned()),
46            abi: AbiVersion { major: 0, minor: 1 },
47            target: LibTarget::HostRegistered,
48            requires: Vec::<Dependency>::new(),
49            capabilities: vec![table_mount_capability()],
50            exports: function_symbols()
51                .into_iter()
52                .map(|symbol| Export::Function {
53                    symbol,
54                    function_id: None,
55                })
56                .collect(),
57        }
58    }
59
60    fn load(&self, cx: &mut LoadCx, linker: &mut Linker<'_>) -> Result<()> {
61        for symbol in function_symbols() {
62            linker.function_value(
63                symbol.clone(),
64                cx.factory().opaque(Arc::new(MountFunction { symbol }))?,
65            )?;
66        }
67        Ok(())
68    }
69}
70
71/// Install the mounted namespace library into `cx`.
72pub fn install_mount_dir_lib(cx: &mut Cx) -> Result<()> {
73    let lib_id = Symbol::qualified("table", "mount");
74    if cx.registry().lib(&lib_id).is_some() {
75        return Ok(());
76    }
77    cx.load_lib(&MountDirLib).map(|_| ())
78}
79
80fn function_symbols() -> Vec<Symbol> {
81    vec![
82        mount_create_symbol(),
83        mount_dir_symbol(),
84        mount_table_symbol(),
85        mount_unmount_symbol(),
86        mount_inspect_symbol(),
87    ]
88}
89
90#[derive(Clone)]
91struct MountFunction {
92    symbol: Symbol,
93}
94
95impl Object for MountFunction {
96    fn display(&self, _cx: &mut Cx) -> Result<String> {
97        Ok(format!("#<function {}>", self.symbol))
98    }
99
100    fn as_any(&self) -> &dyn std::any::Any {
101        self
102    }
103}
104
105impl sim_kernel::ObjectCompat for MountFunction {
106    fn class(&self, cx: &mut Cx) -> Result<ClassRef> {
107        if let Some(class) = cx
108            .registry()
109            .class_by_symbol(&Symbol::qualified("core", "Function"))
110        {
111            return Ok(class.clone());
112        }
113        DefaultFactory.class_stub(
114            sim_kernel::CORE_FUNCTION_CLASS_ID,
115            Symbol::qualified("core", "Function"),
116        )
117    }
118
119    fn as_expr(&self, _cx: &mut Cx) -> Result<Expr> {
120        Ok(Expr::Symbol(self.symbol.clone()))
121    }
122
123    fn as_callable(&self) -> Option<&dyn Callable> {
124        Some(self)
125    }
126}
127
128impl Callable for MountFunction {
129    fn call(&self, cx: &mut Cx, args: Args) -> Result<Value> {
130        let args = args.into_vec();
131        match self.symbol.to_string().as_str() {
132            "table/mount/create" => call_create(cx, args),
133            "table/mount/dir" => call_mount_dir(cx, args),
134            "table/mount/table" => call_mount_table(cx, args),
135            "table/mount/unmount" => call_unmount(cx, args),
136            "table/mount/inspect" => call_inspect(cx, args),
137            _ => Err(Error::Eval(format!(
138                "table/mount: unknown function {}",
139                self.symbol
140            ))),
141        }
142    }
143}
144
145fn call_create(cx: &mut Cx, args: Vec<Value>) -> Result<Value> {
146    cx.require(&table_mount_capability())?;
147    let [root] = expect_arity(args, 1, "table/mount/create")?;
148    cx.factory().opaque(Arc::new(MountedDir::new(root)?))
149}
150
151fn call_mount_dir(cx: &mut Cx, args: Vec<Value>) -> Result<Value> {
152    let [namespace, path, target] = expect_arity(args, 3, "table/mount/dir")?;
153    let dir = namespace_ref(&namespace)?;
154    let path = path_arg(cx, &path)?;
155    dir.mount_dir(cx, path, target)?;
156    Ok(namespace)
157}
158
159fn call_mount_table(cx: &mut Cx, args: Vec<Value>) -> Result<Value> {
160    let [namespace, path, target] = expect_arity(args, 3, "table/mount/table")?;
161    let dir = namespace_ref(&namespace)?;
162    let path = path_arg(cx, &path)?;
163    dir.mount_table(cx, path, target)?;
164    Ok(namespace)
165}
166
167fn call_unmount(cx: &mut Cx, args: Vec<Value>) -> Result<Value> {
168    let [namespace, path] = expect_arity(args, 2, "table/mount/unmount")?;
169    let dir = namespace_ref(&namespace)?;
170    let path = path_arg(cx, &path)?;
171    match dir.unmount(cx, &path)? {
172        Some(value) => Ok(value),
173        None => cx.factory().nil(),
174    }
175}
176
177fn call_inspect(cx: &mut Cx, args: Vec<Value>) -> Result<Value> {
178    let [namespace] = expect_arity(args, 1, "table/mount/inspect")?;
179    let dir = namespace_ref(&namespace)?;
180    cx.factory().expr(inspection_expr(&dir.inspect()?))
181}
182
183fn expect_arity<const N: usize>(
184    args: Vec<Value>,
185    expected: usize,
186    function: &str,
187) -> Result<[Value; N]> {
188    if args.len() != expected {
189        return Err(Error::Eval(format!(
190            "{function} expects {expected} argument(s), got {}",
191            args.len()
192        )));
193    }
194    args.try_into()
195        .map_err(|_| Error::Eval(format!("{function} internal arity mismatch")))
196}
197
198fn namespace_ref(value: &Value) -> Result<&MountedDir> {
199    value
200        .object()
201        .downcast_ref::<MountedDir>()
202        .ok_or_else(|| Error::Eval("table/mount: expected MountedDir namespace".to_owned()))
203}
204
205fn path_arg(cx: &mut Cx, value: &Value) -> Result<TablePath> {
206    let expr = value.object().as_expr(cx)?;
207    let text = match expr {
208        Expr::String(text) => text,
209        Expr::Symbol(symbol) => symbol.to_string(),
210        _ => {
211            return Err(Error::Eval(
212                "table/mount: path argument must be a string or symbol".to_owned(),
213            ));
214        }
215    };
216    TablePath::parse_absolute(&text)
217        .map_err(|err| Error::Eval(format!("table/mount: invalid mount path {text:?}: {err:?}")))
218}