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