Skip to main content

sim_lib_server/frame/
eval.rs

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
11/// Encodes an [`EvalRequest`] into a request [`ServerFrame`] under `codec`.
12///
13/// Carries the request's consistency, required capabilities, trace flag, and
14/// deadline onto the frame envelope.
15pub 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
34/// Encodes an [`EvalReply`] into a response [`ServerFrame`] under `codec`.
35///
36/// Applies the given `consistency` and sets the envelope trace flag from the
37/// reply's trace value.
38pub 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
60/// Decodes a request [`ServerFrame`] back into an [`EvalRequest`].
61///
62/// Returns an error when the frame is not a request frame.
63pub 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
74/// Decodes a response [`ServerFrame`] back into an [`EvalReply`].
75///
76/// Returns an error when the frame is not a response frame.
77pub 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}