use std::time::Duration;
use sim_kernel::{
CapabilityName, Consistency, Cx, Diagnostic, Error, EvalMode, EvalReply, EvalRequest, Expr,
ObjectCompat, ReadPolicy, Result, Severity, Symbol, Value,
};
use crate::helpers::parse_optional_duration;
use crate::{FrameKind, ServerFrame};
pub fn server_frame_from_request(
cx: &mut Cx,
codec: &Symbol,
request: EvalRequest,
) -> Result<ServerFrame> {
let expr = request.as_expr(cx)?;
let mut frame = ServerFrame::from_expr(
cx,
codec.clone(),
FrameKind::Request,
&expr,
request.consistency,
request.required_capabilities.clone(),
request.trace,
)?;
frame.envelope.deadline = request.deadline;
Ok(frame)
}
pub fn server_frame_from_reply(
cx: &mut Cx,
codec: &Symbol,
reply: EvalReply,
consistency: Consistency,
) -> Result<ServerFrame> {
let expr = reply.as_expr(cx)?;
let mut frame = ServerFrame::from_expr(
cx,
codec.clone(),
FrameKind::Response,
&expr,
consistency,
Vec::new(),
reply.trace.is_some(),
)?;
if let Some(trace) = reply.trace {
frame.envelope.trace = !matches!(trace.object().as_expr(cx)?, Expr::Nil);
}
Ok(frame)
}
pub fn eval_request_from_frame(cx: &mut Cx, frame: &ServerFrame) -> Result<EvalRequest> {
if frame.kind != FrameKind::Request {
return Err(Error::Eval(format!(
"expected request frame, found {}",
frame.kind.as_symbol()
)));
}
let expr = frame.decode_expr(cx, ReadPolicy::default())?;
eval_request_from_expr(cx, expr)
}
pub fn eval_reply_from_frame(cx: &mut Cx, frame: &ServerFrame) -> Result<EvalReply> {
if frame.kind != FrameKind::Response {
return Err(Error::Eval(format!(
"expected response frame, found {}",
frame.kind.as_symbol()
)));
}
let expr = frame.decode_expr(cx, ReadPolicy::default())?;
eval_reply_from_expr(cx, expr)
}
fn eval_request_from_expr(cx: &mut Cx, expr: Expr) -> Result<EvalRequest> {
let request_expr = required_table_field(&expr, "expr")?.clone();
let result_shape = parse_result_shape_expr(cx, required_table_field(&expr, "result-shape")?)?;
let required_capabilities = parse_capability_expr(required_table_field(&expr, "requires")?)?;
let deadline = parse_deadline_expr(required_table_field(&expr, "deadline")?)?;
let consistency = parse_consistency_expr(required_table_field(&expr, "consistency")?)?;
let mode = optional_table_field(&expr, "mode")
.map(parse_mode_expr)
.transpose()?
.unwrap_or(EvalMode::Eval);
let answer_limit = optional_table_field(&expr, "answer-limit")
.map(parse_optional_usize_expr)
.transpose()?
.flatten();
let stream_buffer = optional_table_field(&expr, "stream-buffer")
.map(parse_optional_usize_expr)
.transpose()?
.flatten();
let stream = optional_table_field(&expr, "stream")
.map(parse_bool_expr)
.transpose()?
.unwrap_or(false);
let trace = parse_bool_expr(required_table_field(&expr, "trace")?)?;
Ok(EvalRequest {
expr: request_expr,
result_shape,
required_capabilities,
deadline,
consistency,
mode,
answer_limit,
stream_buffer,
stream,
trace,
})
}
fn eval_reply_from_expr(cx: &mut Cx, expr: Expr) -> Result<EvalReply> {
let value = expr_to_value(cx, required_table_field(&expr, "value")?)?;
let diagnostics = parse_diagnostics_expr(required_table_field(&expr, "diagnostics")?)?;
let trace = parse_optional_value_expr(cx, required_table_field(&expr, "trace")?)?;
Ok(EvalReply {
value,
diagnostics,
trace,
})
}
fn required_table_field<'a>(expr: &'a Expr, key: &str) -> Result<&'a Expr> {
let Expr::Map(entries) = expr else {
return Err(Error::TypeMismatch {
expected: "table expression",
found: "non-table",
});
};
entries
.iter()
.find_map(|(entry_key, entry_value)| match entry_key {
Expr::Symbol(symbol) if symbol.name.as_ref() == key => Some(entry_value),
_ => None,
})
.ok_or_else(|| Error::Eval(format!("missing frame field {key}")))
}
fn optional_table_field<'a>(expr: &'a Expr, key: &str) -> Option<&'a Expr> {
let Expr::Map(entries) = expr else {
return None;
};
entries
.iter()
.find_map(|(entry_key, entry_value)| match entry_key {
Expr::Symbol(symbol) if symbol.name.as_ref() == key => Some(entry_value),
_ => None,
})
}
fn parse_result_shape_expr(cx: &mut Cx, expr: &Expr) -> Result<Option<sim_kernel::ShapeRef>> {
if matches!(expr, Expr::Nil) {
return Ok(None);
}
if let Expr::Symbol(symbol) = expr {
if let Ok(shape) = cx.resolve_shape(symbol) {
return Ok(Some(shape));
}
if symbol.name.as_ref() == "instance-shape"
&& let Some(namespace) = &symbol.namespace
{
let class_symbol = parse_qualified_symbol(namespace);
if let Ok(class_value) = cx.resolve_class(&class_symbol)
&& let Some(class) = class_value.object().as_class()
{
return Ok(Some(class.instance_shape(cx)?));
}
}
}
let value = cx.eval_expr(expr.clone())?;
if let Some(class) = value.object().as_class() {
return Ok(Some(class.instance_shape(cx)?));
}
Err(Error::TypeMismatch {
expected: "shape or class",
found: "non-shape",
})
}
fn parse_qualified_symbol(text: &str) -> Symbol {
match text.rsplit_once('/') {
Some((namespace, name)) => Symbol::qualified(namespace.to_owned(), name.to_owned()),
None => Symbol::new(text.to_owned()),
}
}
fn parse_capability_expr(expr: &Expr) -> Result<Vec<CapabilityName>> {
match expr {
Expr::Nil => Ok(Vec::new()),
Expr::List(items) | Expr::Vector(items) => {
items.iter().cloned().map(capability_from_expr).collect()
}
Expr::Symbol(_) | Expr::String(_) => Ok(vec![capability_from_expr(expr.clone())?]),
_ => Err(Error::TypeMismatch {
expected: "capability list",
found: "non-list",
}),
}
}
fn capability_from_expr(expr: Expr) -> Result<CapabilityName> {
match expr {
Expr::Symbol(symbol) => Ok(CapabilityName::new(symbol.to_string())),
Expr::String(text) => Ok(CapabilityName::new(text)),
_ => Err(Error::TypeMismatch {
expected: "capability symbol or string",
found: "non-capability",
}),
}
}
fn parse_deadline_expr(expr: &Expr) -> Result<Option<Duration>> {
parse_optional_duration(expr)
}
fn parse_consistency_expr(expr: &Expr) -> Result<Consistency> {
let name = match expr {
Expr::Symbol(symbol) => symbol.to_string(),
Expr::String(text) => text.clone(),
_ => {
return Err(Error::TypeMismatch {
expected: "consistency symbol or string",
found: "non-consistency",
});
}
};
match name.as_str() {
"local-only" => Ok(Consistency::LocalOnly),
"local-first" => Ok(Consistency::LocalFirst),
"remote-only" => Ok(Consistency::RemoteOnly),
_ => Err(Error::Eval(format!(
"unsupported realize consistency {name}"
))),
}
}
fn parse_mode_expr(expr: &Expr) -> Result<EvalMode> {
let name = match expr {
Expr::Symbol(symbol) => symbol.to_string(),
Expr::String(text) => text.clone(),
_ => {
return Err(Error::TypeMismatch {
expected: "mode symbol or string",
found: "non-mode",
});
}
};
match name.as_str() {
"eval" => Ok(EvalMode::Eval),
"logic" => Ok(EvalMode::Logic),
_ => Err(Error::Eval(format!("unsupported realize mode {name}"))),
}
}
fn parse_optional_usize_expr(expr: &Expr) -> Result<Option<usize>> {
match expr {
Expr::Nil => Ok(None),
Expr::Number(number) => number
.canonical
.parse::<usize>()
.map(Some)
.map_err(|_| Error::Eval(format!("expected usize, found {}", number.canonical))),
Expr::String(text) => text
.parse::<usize>()
.map(Some)
.map_err(|_| Error::Eval(format!("expected usize, found {text}"))),
_ => Err(Error::TypeMismatch {
expected: "usize or nil",
found: "non-usize",
}),
}
}
fn parse_bool_expr(expr: &Expr) -> Result<bool> {
match expr {
Expr::Bool(value) => Ok(*value),
_ => Err(Error::TypeMismatch {
expected: "bool",
found: "non-bool",
}),
}
}
fn parse_diagnostics_expr(expr: &Expr) -> Result<Vec<Diagnostic>> {
match expr {
Expr::Nil => Ok(Vec::new()),
Expr::List(items) | Expr::Vector(items) => {
items.iter().map(parse_diagnostic_expr).collect()
}
_ => Err(Error::TypeMismatch {
expected: "diagnostic list",
found: "non-list",
}),
}
}
fn parse_diagnostic_expr(expr: &Expr) -> Result<Diagnostic> {
let severity = match required_table_field(expr, "severity")? {
Expr::Symbol(symbol) if symbol.name.as_ref() == "error" => Severity::Error,
Expr::Symbol(symbol) if symbol.name.as_ref() == "warning" => Severity::Warning,
Expr::Symbol(symbol) if symbol.name.as_ref() == "info" => Severity::Info,
Expr::Symbol(symbol) if symbol.name.as_ref() == "note" => Severity::Note,
_ => {
return Err(Error::TypeMismatch {
expected: "diagnostic severity symbol",
found: "non-severity",
});
}
};
let message = match required_table_field(expr, "message")? {
Expr::String(text) => text.clone(),
_ => {
return Err(Error::TypeMismatch {
expected: "diagnostic message string",
found: "non-string",
});
}
};
let code = match required_table_field(expr, "code")? {
Expr::Nil => None,
Expr::Symbol(symbol) => Some(symbol.clone()),
_ => {
return Err(Error::TypeMismatch {
expected: "diagnostic code symbol",
found: "non-symbol",
});
}
};
let related = parse_diagnostics_expr(required_table_field(expr, "related")?)?;
Ok(Diagnostic {
severity,
message,
source: None,
span: None,
code,
related,
})
}
fn parse_optional_value_expr(cx: &mut Cx, expr: &Expr) -> Result<Option<Value>> {
if matches!(expr, Expr::Nil) {
return Ok(None);
}
expr_to_value(cx, expr).map(Some)
}
fn expr_to_value(cx: &mut Cx, expr: &Expr) -> Result<Value> {
match expr {
Expr::Nil => cx.factory().nil(),
Expr::Bool(value) => cx.factory().bool(*value),
Expr::Number(number) => cx
.factory()
.number_literal(number.domain.clone(), number.canonical.clone()),
Expr::Symbol(symbol) => cx.factory().symbol(symbol.clone()),
Expr::String(text) => cx.factory().string(text.clone()),
Expr::Bytes(bytes) => cx.factory().bytes(bytes.clone()),
Expr::List(items) | Expr::Vector(items) => {
let values = items
.iter()
.map(|item| expr_to_value(cx, item))
.collect::<Result<Vec<_>>>()?;
cx.factory().list(values)
}
Expr::Map(entries) => {
let values = entries
.iter()
.map(|(key, value)| {
let Expr::Symbol(key) = key else {
return Err(Error::TypeMismatch {
expected: "symbol table key",
found: "non-symbol",
});
};
Ok((key.clone(), expr_to_value(cx, value)?))
})
.collect::<Result<Vec<_>>>()?;
cx.factory().table(values)
}
_ => cx.factory().expr(expr.clone()),
}
}