1use std::{sync::Arc, time::Duration};
2
3use sim_kernel::{
4 Consistency, Cx, Error, EvalMode, EvalRequest, Expr, Object, ObjectEncode, ObjectEncoding,
5 Result, Symbol, Table, Value, capability::eval_remote_capability, id::CORE_TABLE_CLASS_ID,
6 object::ClassRef, table::Dir,
7};
8use sim_lib_server::{EvalSite, eval_reply_from_frame, server_frame_from_request};
9use sim_table_core::{TableOp, encode_table_op};
10
11use crate::{citizen::remote_dir_class_symbol, table_remote_capability};
12
13#[derive(Clone)]
15pub struct RemoteDir {
16 site: Arc<dyn EvalSite>,
17 path: Vec<Symbol>,
18 codec: Symbol,
19}
20
21impl RemoteDir {
22 pub fn new(site: Arc<dyn EvalSite>, codec: Symbol) -> Result<Self> {
26 if !site.codecs().iter().any(|candidate| candidate == &codec) {
27 return Err(Error::Eval(format!(
28 "table/remote: codec {codec} is not supported by site {}",
29 site.site_kind()
30 )));
31 }
32 Ok(Self {
33 site,
34 path: Vec::new(),
35 codec,
36 })
37 }
38
39 fn with_path(&self, path: Vec<Symbol>) -> Self {
40 Self {
41 site: self.site.clone(),
42 path,
43 codec: self.codec.clone(),
44 }
45 }
46
47 fn path_expr(&self) -> Expr {
48 Expr::List(self.path.iter().cloned().map(Expr::Symbol).collect())
49 }
50
51 fn descriptor_path(&self) -> Vec<String> {
52 self.path
53 .iter()
54 .map(|segment| segment.name.to_string())
55 .collect()
56 }
57
58 fn remote_request(&self, op: &TableOp) -> EvalRequest {
59 let Expr::Call { operator, args } = encode_table_op(op) else {
63 unreachable!("encode_table_op always yields a Call");
64 };
65 let mut call_args = Vec::with_capacity(args.len() + 1);
66 call_args.push(self.path_expr());
67 call_args.extend(args);
68 EvalRequest {
69 expr: Expr::Call {
70 operator,
71 args: call_args,
72 },
73 mode: EvalMode::Eval,
74 result_shape: None,
75 answer_limit: None,
76 stream_buffer: None,
77 stream: false,
78 required_capabilities: Vec::new(),
79 deadline: Some(Duration::from_secs(5)),
80 consistency: Consistency::RemoteOnly,
81 trace: false,
82 }
83 }
84
85 fn call(&self, cx: &mut Cx, op: &TableOp) -> Result<Value> {
86 cx.require(&eval_remote_capability())?;
87 let frame = server_frame_from_request(cx, &self.codec, self.remote_request(op))?;
88 let reply = self.site.answer(cx, frame)?;
89 Ok(eval_reply_from_frame(cx, &reply)?.value)
90 }
91}
92
93impl Object for RemoteDir {
94 fn display(&self, _cx: &mut Cx) -> Result<String> {
95 if self.path.is_empty() {
96 Ok(format!("table/remote[{}:/]", self.site.site_kind()))
97 } else {
98 let suffix = self
99 .path
100 .iter()
101 .map(|segment| segment.to_string())
102 .collect::<Vec<_>>()
103 .join("/");
104 Ok(format!("table/remote[{}:/{suffix}]", self.site.site_kind()))
105 }
106 }
107
108 fn as_any(&self) -> &dyn std::any::Any {
109 self
110 }
111}
112
113impl sim_kernel::ObjectCompat for RemoteDir {
114 fn class(&self, cx: &mut Cx) -> Result<ClassRef> {
115 let symbol = remote_dir_class_symbol();
116 if let Some(value) = cx.registry().class_by_symbol(&symbol) {
117 return Ok(value.clone());
118 }
119 let symbol = Symbol::qualified("core", "Table");
120 if let Some(value) = cx.registry().class_by_symbol(&symbol) {
121 return Ok(value.clone());
122 }
123 cx.factory().class_stub(CORE_TABLE_CLASS_ID, symbol)
124 }
125 fn as_expr(&self, cx: &mut Cx) -> Result<Expr> {
126 self.as_table_expr(cx)
127 }
128 fn truth(&self, cx: &mut Cx) -> Result<bool> {
129 Ok(!self.is_empty(cx)?)
130 }
131 fn as_table_impl(&self) -> Option<&dyn Table> {
132 Some(self)
133 }
134 fn as_dir(&self) -> Option<&dyn Dir> {
135 Some(self)
136 }
137 fn as_object_encoder(&self) -> Option<&dyn ObjectEncode> {
138 Some(self)
139 }
140}
141
142impl ObjectEncode for RemoteDir {
143 fn object_encoding(&self, _cx: &mut Cx) -> Result<ObjectEncoding> {
144 Ok(ObjectEncoding::Constructor {
145 class: remote_dir_class_symbol(),
146 args: vec![
147 Expr::Symbol(Symbol::new("v0")),
148 Expr::String(self.site.site_kind().to_owned()),
149 Expr::Symbol(self.codec.clone()),
150 sim_table_core::citizen_fields::path_segments::encode(&self.descriptor_path()),
151 ],
152 })
153 }
154}
155
156impl sim_citizen::Citizen for RemoteDir {
157 fn citizen_symbol() -> Symbol {
158 remote_dir_class_symbol()
159 }
160
161 fn citizen_version() -> u32 {
162 0
163 }
164
165 fn citizen_arity() -> usize {
166 3
167 }
168
169 fn citizen_fields() -> &'static [&'static str] {
170 &["site_kind", "codec", "path"]
171 }
172}
173
174impl Table for RemoteDir {
175 fn backend_symbol(&self) -> Symbol {
176 Symbol::qualified("table", "remote")
177 }
178
179 fn get(&self, cx: &mut Cx, key: Symbol) -> Result<Value> {
180 self.call(cx, &TableOp::Get(key))
181 }
182
183 fn set(&self, cx: &mut Cx, key: Symbol, value: Value) -> Result<()> {
184 let expr = value.object().as_expr(cx)?;
185 self.call(cx, &TableOp::Set(key, expr)).map(|_| ())
186 }
187
188 fn has(&self, cx: &mut Cx, key: Symbol) -> Result<bool> {
189 self.call(cx, &TableOp::Has(key))?.object().truth(cx)
190 }
191
192 fn del(&self, cx: &mut Cx, key: Symbol) -> Result<Value> {
193 self.call(cx, &TableOp::Delete(key))
194 }
195
196 fn keys(&self, cx: &mut Cx) -> Result<Vec<Symbol>> {
197 let reply = self.call(cx, &TableOp::Keys)?;
198 let list = reply.object().as_list().ok_or(Error::TypeMismatch {
199 expected: "list",
200 found: "non-list",
201 })?;
202 list.to_vec(cx, None)?
203 .into_iter()
204 .map(|value| match value.object().as_expr(cx)? {
205 Expr::Symbol(symbol) => Ok(symbol),
206 _ => Err(Error::TypeMismatch {
207 expected: "symbol",
208 found: "non-symbol",
209 }),
210 })
211 .collect()
212 }
213
214 fn entries(&self, cx: &mut Cx) -> Result<Vec<(Symbol, Value)>> {
215 self.call(cx, &TableOp::Entries)?
216 .object()
217 .as_table_impl()
218 .ok_or(Error::TypeMismatch {
219 expected: "table",
220 found: "non-table",
221 })?
222 .entries(cx)
223 }
224
225 fn len(&self, cx: &mut Cx) -> Result<usize> {
226 match self.call(cx, &TableOp::Len)?.object().as_expr(cx)? {
227 Expr::Number(number) => number
228 .canonical
229 .parse::<usize>()
230 .map_err(|err| Error::Eval(format!("table/remote: invalid len reply: {err}"))),
231 Expr::String(text) => text
232 .parse::<usize>()
233 .map_err(|err| Error::Eval(format!("table/remote: invalid len reply: {err}"))),
234 _ => Err(Error::TypeMismatch {
235 expected: "number",
236 found: "non-number",
237 }),
238 }
239 }
240
241 fn clear(&self, cx: &mut Cx) -> Result<()> {
242 self.call(cx, &TableOp::Clear).map(|_| ())
243 }
244}
245
246impl Dir for RemoteDir {
247 fn mkdir(&self, cx: &mut Cx, name: Symbol) -> Result<Value> {
248 self.call(cx, &TableOp::Mkdir(name.clone()))?;
249 let mut path = self.path.clone();
250 path.push(name);
251 cx.factory().opaque(Arc::new(self.with_path(path)))
252 }
253
254 fn opendir(&self, cx: &mut Cx, name: Symbol) -> Result<Option<Value>> {
255 let reply = self.call(cx, &TableOp::Opendir(name.clone()))?;
256 if matches!(reply.object().as_expr(cx)?, Expr::Nil) {
257 return Ok(None);
258 }
259 let mut path = self.path.clone();
260 path.push(name);
261 Ok(Some(cx.factory().opaque(Arc::new(self.with_path(path)))?))
262 }
263
264 fn rmdir(&self, cx: &mut Cx, name: Symbol) -> Result<Value> {
265 self.call(cx, &TableOp::Rmdir(name))
266 }
267
268 fn is_dir(&self, cx: &mut Cx, name: Symbol) -> Result<bool> {
269 self.call(cx, &TableOp::IsDir(name))?.object().truth(cx)
270 }
271}
272
273pub fn remote_dir_value(cx: &mut Cx, site: Arc<dyn EvalSite>, codec: Symbol) -> Result<Value> {
277 cx.require(&table_remote_capability())?;
278 cx.factory().opaque(Arc::new(RemoteDir::new(site, codec)?))
279}