1use std::time::Duration;
2
3use sim_kernel::{
4 CapabilityName, Consistency, Cx, Diagnostic, Error, EvalMode, EvalReply, EvalRequest, Expr,
5 ObjectCompat, ReadPolicy, Result, Severity, Symbol, Value,
6};
7
8use crate::helpers::parse_optional_duration;
9use crate::{FrameKind, ServerFrame};
10
11pub fn server_frame_from_request(
16 cx: &mut Cx,
17 codec: &Symbol,
18 request: EvalRequest,
19) -> Result<ServerFrame> {
20 let expr = request.as_expr(cx)?;
21 let mut frame = ServerFrame::from_expr(
22 cx,
23 codec.clone(),
24 FrameKind::Request,
25 &expr,
26 request.consistency,
27 request.required_capabilities.clone(),
28 request.trace,
29 )?;
30 frame.envelope.deadline = request.deadline;
31 Ok(frame)
32}
33
34pub fn server_frame_from_reply(
39 cx: &mut Cx,
40 codec: &Symbol,
41 reply: EvalReply,
42 consistency: Consistency,
43) -> Result<ServerFrame> {
44 let expr = reply.as_expr(cx)?;
45 let mut frame = ServerFrame::from_expr(
46 cx,
47 codec.clone(),
48 FrameKind::Response,
49 &expr,
50 consistency,
51 Vec::new(),
52 reply.trace.is_some(),
53 )?;
54 if let Some(trace) = reply.trace {
55 frame.envelope.trace = !matches!(trace.object().as_expr(cx)?, Expr::Nil);
56 }
57 Ok(frame)
58}
59
60pub fn eval_request_from_frame(cx: &mut Cx, frame: &ServerFrame) -> Result<EvalRequest> {
64 if frame.kind != FrameKind::Request {
65 return Err(Error::Eval(format!(
66 "expected request frame, found {}",
67 frame.kind.as_symbol()
68 )));
69 }
70 let expr = frame.decode_expr(cx, ReadPolicy::default())?;
71 eval_request_from_expr(cx, expr)
72}
73
74pub fn eval_reply_from_frame(cx: &mut Cx, frame: &ServerFrame) -> Result<EvalReply> {
78 if frame.kind != FrameKind::Response {
79 return Err(Error::Eval(format!(
80 "expected response frame, found {}",
81 frame.kind.as_symbol()
82 )));
83 }
84 let expr = frame.decode_expr(cx, ReadPolicy::default())?;
85 eval_reply_from_expr(cx, expr)
86}
87
88fn eval_request_from_expr(cx: &mut Cx, expr: Expr) -> Result<EvalRequest> {
89 let request_expr = required_table_field(&expr, "expr")?.clone();
90 let result_shape = parse_result_shape_expr(cx, required_table_field(&expr, "result-shape")?)?;
91 let required_capabilities = parse_capability_expr(required_table_field(&expr, "requires")?)?;
92 let deadline = parse_deadline_expr(required_table_field(&expr, "deadline")?)?;
93 let consistency = parse_consistency_expr(required_table_field(&expr, "consistency")?)?;
94 let mode = optional_table_field(&expr, "mode")
95 .map(parse_mode_expr)
96 .transpose()?
97 .unwrap_or(EvalMode::Eval);
98 let answer_limit = optional_table_field(&expr, "answer-limit")
99 .map(parse_optional_usize_expr)
100 .transpose()?
101 .flatten();
102 let stream_buffer = optional_table_field(&expr, "stream-buffer")
103 .map(parse_optional_usize_expr)
104 .transpose()?
105 .flatten();
106 let stream = optional_table_field(&expr, "stream")
107 .map(parse_bool_expr)
108 .transpose()?
109 .unwrap_or(false);
110 let trace = parse_bool_expr(required_table_field(&expr, "trace")?)?;
111 Ok(EvalRequest {
112 expr: request_expr,
113 result_shape,
114 required_capabilities,
115 deadline,
116 consistency,
117 mode,
118 answer_limit,
119 stream_buffer,
120 stream,
121 trace,
122 })
123}
124
125fn eval_reply_from_expr(cx: &mut Cx, expr: Expr) -> Result<EvalReply> {
126 let value = expr_to_value(cx, required_table_field(&expr, "value")?)?;
127 let diagnostics = parse_diagnostics_expr(required_table_field(&expr, "diagnostics")?)?;
128 let trace = parse_optional_value_expr(cx, required_table_field(&expr, "trace")?)?;
129 Ok(EvalReply {
130 value,
131 diagnostics,
132 trace,
133 })
134}
135
136fn required_table_field<'a>(expr: &'a Expr, key: &str) -> Result<&'a Expr> {
137 let Expr::Map(entries) = expr else {
138 return Err(Error::TypeMismatch {
139 expected: "table expression",
140 found: "non-table",
141 });
142 };
143 entries
144 .iter()
145 .find_map(|(entry_key, entry_value)| match entry_key {
146 Expr::Symbol(symbol) if symbol.name.as_ref() == key => Some(entry_value),
147 _ => None,
148 })
149 .ok_or_else(|| Error::Eval(format!("missing frame field {key}")))
150}
151
152fn optional_table_field<'a>(expr: &'a Expr, key: &str) -> Option<&'a Expr> {
153 let Expr::Map(entries) = expr else {
154 return None;
155 };
156 entries
157 .iter()
158 .find_map(|(entry_key, entry_value)| match entry_key {
159 Expr::Symbol(symbol) if symbol.name.as_ref() == key => Some(entry_value),
160 _ => None,
161 })
162}
163
164fn parse_result_shape_expr(cx: &mut Cx, expr: &Expr) -> Result<Option<sim_kernel::ShapeRef>> {
165 if matches!(expr, Expr::Nil) {
166 return Ok(None);
167 }
168 if let Expr::Symbol(symbol) = expr {
169 if let Ok(shape) = cx.resolve_shape(symbol) {
170 return Ok(Some(shape));
171 }
172 if symbol.name.as_ref() == "instance-shape"
173 && let Some(namespace) = &symbol.namespace
174 {
175 let class_symbol = parse_qualified_symbol(namespace);
176 if let Ok(class_value) = cx.resolve_class(&class_symbol)
177 && let Some(class) = class_value.object().as_class()
178 {
179 return Ok(Some(class.instance_shape(cx)?));
180 }
181 }
182 }
183 let value = cx.eval_expr(expr.clone())?;
184 if let Some(class) = value.object().as_class() {
185 return Ok(Some(class.instance_shape(cx)?));
186 }
187 Err(Error::TypeMismatch {
188 expected: "shape or class",
189 found: "non-shape",
190 })
191}
192
193fn parse_qualified_symbol(text: &str) -> Symbol {
194 match text.rsplit_once('/') {
195 Some((namespace, name)) => Symbol::qualified(namespace.to_owned(), name.to_owned()),
196 None => Symbol::new(text.to_owned()),
197 }
198}
199
200fn parse_capability_expr(expr: &Expr) -> Result<Vec<CapabilityName>> {
201 match expr {
202 Expr::Nil => Ok(Vec::new()),
203 Expr::List(items) | Expr::Vector(items) => {
204 items.iter().cloned().map(capability_from_expr).collect()
205 }
206 Expr::Symbol(_) | Expr::String(_) => Ok(vec![capability_from_expr(expr.clone())?]),
207 _ => Err(Error::TypeMismatch {
208 expected: "capability list",
209 found: "non-list",
210 }),
211 }
212}
213
214fn capability_from_expr(expr: Expr) -> Result<CapabilityName> {
215 match expr {
216 Expr::Symbol(symbol) => Ok(CapabilityName::new(symbol.to_string())),
217 Expr::String(text) => Ok(CapabilityName::new(text)),
218 _ => Err(Error::TypeMismatch {
219 expected: "capability symbol or string",
220 found: "non-capability",
221 }),
222 }
223}
224
225fn parse_deadline_expr(expr: &Expr) -> Result<Option<Duration>> {
226 parse_optional_duration(expr)
227}
228
229fn parse_consistency_expr(expr: &Expr) -> Result<Consistency> {
230 let name = match expr {
231 Expr::Symbol(symbol) => symbol.to_string(),
232 Expr::String(text) => text.clone(),
233 _ => {
234 return Err(Error::TypeMismatch {
235 expected: "consistency symbol or string",
236 found: "non-consistency",
237 });
238 }
239 };
240 match name.as_str() {
241 "local-only" => Ok(Consistency::LocalOnly),
242 "local-first" => Ok(Consistency::LocalFirst),
243 "remote-only" => Ok(Consistency::RemoteOnly),
244 _ => Err(Error::Eval(format!(
245 "unsupported realize consistency {name}"
246 ))),
247 }
248}
249
250fn parse_mode_expr(expr: &Expr) -> Result<EvalMode> {
251 let name = match expr {
252 Expr::Symbol(symbol) => symbol.to_string(),
253 Expr::String(text) => text.clone(),
254 _ => {
255 return Err(Error::TypeMismatch {
256 expected: "mode symbol or string",
257 found: "non-mode",
258 });
259 }
260 };
261 match name.as_str() {
262 "eval" => Ok(EvalMode::Eval),
263 "logic" => Ok(EvalMode::Logic),
264 _ => Err(Error::Eval(format!("unsupported realize mode {name}"))),
265 }
266}
267
268fn parse_optional_usize_expr(expr: &Expr) -> Result<Option<usize>> {
269 match expr {
270 Expr::Nil => Ok(None),
271 Expr::Number(number) => number
272 .canonical
273 .parse::<usize>()
274 .map(Some)
275 .map_err(|_| Error::Eval(format!("expected usize, found {}", number.canonical))),
276 Expr::String(text) => text
277 .parse::<usize>()
278 .map(Some)
279 .map_err(|_| Error::Eval(format!("expected usize, found {text}"))),
280 _ => Err(Error::TypeMismatch {
281 expected: "usize or nil",
282 found: "non-usize",
283 }),
284 }
285}
286
287fn parse_bool_expr(expr: &Expr) -> Result<bool> {
288 match expr {
289 Expr::Bool(value) => Ok(*value),
290 _ => Err(Error::TypeMismatch {
291 expected: "bool",
292 found: "non-bool",
293 }),
294 }
295}
296
297fn parse_diagnostics_expr(expr: &Expr) -> Result<Vec<Diagnostic>> {
298 match expr {
299 Expr::Nil => Ok(Vec::new()),
300 Expr::List(items) | Expr::Vector(items) => {
301 items.iter().map(parse_diagnostic_expr).collect()
302 }
303 _ => Err(Error::TypeMismatch {
304 expected: "diagnostic list",
305 found: "non-list",
306 }),
307 }
308}
309
310fn parse_diagnostic_expr(expr: &Expr) -> Result<Diagnostic> {
311 let severity = match required_table_field(expr, "severity")? {
312 Expr::Symbol(symbol) if symbol.name.as_ref() == "error" => Severity::Error,
313 Expr::Symbol(symbol) if symbol.name.as_ref() == "warning" => Severity::Warning,
314 Expr::Symbol(symbol) if symbol.name.as_ref() == "info" => Severity::Info,
315 Expr::Symbol(symbol) if symbol.name.as_ref() == "note" => Severity::Note,
316 _ => {
317 return Err(Error::TypeMismatch {
318 expected: "diagnostic severity symbol",
319 found: "non-severity",
320 });
321 }
322 };
323 let message = match required_table_field(expr, "message")? {
324 Expr::String(text) => text.clone(),
325 _ => {
326 return Err(Error::TypeMismatch {
327 expected: "diagnostic message string",
328 found: "non-string",
329 });
330 }
331 };
332 let code = match required_table_field(expr, "code")? {
333 Expr::Nil => None,
334 Expr::Symbol(symbol) => Some(symbol.clone()),
335 _ => {
336 return Err(Error::TypeMismatch {
337 expected: "diagnostic code symbol",
338 found: "non-symbol",
339 });
340 }
341 };
342 let related = parse_diagnostics_expr(required_table_field(expr, "related")?)?;
343 Ok(Diagnostic {
344 severity,
345 message,
346 source: None,
347 span: None,
348 code,
349 related,
350 })
351}
352
353fn parse_optional_value_expr(cx: &mut Cx, expr: &Expr) -> Result<Option<Value>> {
354 if matches!(expr, Expr::Nil) {
355 return Ok(None);
356 }
357 expr_to_value(cx, expr).map(Some)
358}
359
360fn expr_to_value(cx: &mut Cx, expr: &Expr) -> Result<Value> {
361 match expr {
362 Expr::Nil => cx.factory().nil(),
363 Expr::Bool(value) => cx.factory().bool(*value),
364 Expr::Number(number) => cx
365 .factory()
366 .number_literal(number.domain.clone(), number.canonical.clone()),
367 Expr::Symbol(symbol) => cx.factory().symbol(symbol.clone()),
368 Expr::String(text) => cx.factory().string(text.clone()),
369 Expr::Bytes(bytes) => cx.factory().bytes(bytes.clone()),
370 Expr::List(items) | Expr::Vector(items) => {
371 let values = items
372 .iter()
373 .map(|item| expr_to_value(cx, item))
374 .collect::<Result<Vec<_>>>()?;
375 cx.factory().list(values)
376 }
377 Expr::Map(entries) => {
378 let values = entries
379 .iter()
380 .map(|(key, value)| {
381 let Expr::Symbol(key) = key else {
382 return Err(Error::TypeMismatch {
383 expected: "symbol table key",
384 found: "non-symbol",
385 });
386 };
387 Ok((key.clone(), expr_to_value(cx, value)?))
388 })
389 .collect::<Result<Vec<_>>>()?;
390 cx.factory().table(values)
391 }
392 _ => cx.factory().expr(expr.clone()),
393 }
394}