1use 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
13pub fn mount_create_symbol() -> Symbol {
15 Symbol::qualified("table/mount", "create")
16}
17
18pub fn mount_dir_symbol() -> Symbol {
20 Symbol::qualified("table/mount", "dir")
21}
22
23pub fn mount_table_symbol() -> Symbol {
25 Symbol::qualified("table/mount", "table")
26}
27
28pub fn mount_unmount_symbol() -> Symbol {
30 Symbol::qualified("table/mount", "unmount")
31}
32
33pub fn mount_inspect_symbol() -> Symbol {
35 Symbol::qualified("table/mount", "inspect")
36}
37
38pub 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
71pub 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}