Skip to main content

sim_lib_server/repl/
runtime.rs

1use sim_codec::{Input, encode_with_codec};
2use sim_kernel::{
3    Cx, EncodeOptions, Expr, ReadPolicy, Result, Symbol, Value, eval_fabric_capability,
4};
5
6use crate::{Connection, ensure_installed_codec};
7
8use super::spec::DriverSpec;
9
10/// Where a REPL run sends its rendered result.
11#[derive(Clone)]
12pub enum ReplOutput {
13    /// Write the result through the configured line driver.
14    Driver,
15    /// Return the rendered result as a string value.
16    String,
17}
18
19/// Configuration for a single [`run_repl`] invocation.
20pub struct ReplOptions {
21    /// Connection used to evaluate each line.
22    pub connection: Connection,
23    /// Codec used to decode input and encode rendered results.
24    pub codec: Symbol,
25    /// Prompt string shown before each interactive read.
26    pub prompt: String,
27    /// Driver that supplies input lines and receives output.
28    pub driver: DriverSpec,
29    /// Optional single input to evaluate once instead of looping.
30    pub input: Option<String>,
31    /// Where to send the rendered result.
32    pub output: ReplOutput,
33}
34
35/// Runs a REPL against `options.connection`: either evaluates a single supplied
36/// input and returns its rendered result, or loops over driver-supplied lines,
37/// decoding, evaluating, and writing each result until input is exhausted.
38pub fn run_repl(cx: &mut Cx, options: ReplOptions) -> Result<Value> {
39    cx.require(&eval_fabric_capability())?;
40    for capability in options.driver.required_capabilities() {
41        cx.require(&capability)?;
42    }
43    ensure_installed_codec(cx, &options.codec)?;
44    if let Some(input) = options.input {
45        let rendered = run_once(cx, &options.connection, &options.codec, &input)?;
46        return match options.output {
47            ReplOutput::String => cx.factory().string(rendered),
48            ReplOutput::Driver => cx.factory().nil(),
49        };
50    }
51
52    let mut driver = options.driver.create_driver(cx)?;
53    while let Some(input) = driver.read_line(cx, &options.prompt)? {
54        if input.trim().is_empty() {
55            continue;
56        }
57        match run_once(cx, &options.connection, &options.codec, &input) {
58            Ok(rendered) => {
59                driver.write_output(cx, &(rendered + "\n"))?;
60            }
61            Err(err) => {
62                driver.write_output(cx, &format!("error: {err}\n"))?;
63            }
64        }
65    }
66    cx.factory().nil()
67}
68
69fn run_once(cx: &mut Cx, connection: &Connection, codec: &Symbol, input: &str) -> Result<String> {
70    let expr = lower_repl_expr(sim_codec::decode_with_codec(
71        cx,
72        codec,
73        Input::Text(input.to_owned()),
74        ReadPolicy::default(),
75    )?);
76    let value = connection.request(cx, expr, None, Vec::new())?;
77    render_value(cx, codec, &value)
78}
79
80pub(crate) fn lower_repl_expr(expr: Expr) -> Expr {
81    match expr {
82        Expr::List(items) => {
83            let mut items = items.into_iter();
84            let Some(operator) = items.next() else {
85                return Expr::List(Vec::new());
86            };
87            Expr::Call {
88                operator: Box::new(lower_repl_operator(operator)),
89                args: items.map(lower_repl_expr).collect(),
90            }
91        }
92        Expr::Vector(items) => Expr::Vector(items.into_iter().map(lower_repl_expr).collect()),
93        Expr::Map(entries) => Expr::Map(
94            entries
95                .into_iter()
96                .map(|(key, value)| (key, lower_repl_expr(value)))
97                .collect(),
98        ),
99        Expr::Set(items) => Expr::Set(items.into_iter().map(lower_repl_expr).collect()),
100        Expr::Block(items) => Expr::Block(items.into_iter().map(lower_repl_expr).collect()),
101        Expr::Annotated { expr, annotations } => Expr::Annotated {
102            expr: Box::new(lower_repl_expr(*expr)),
103            annotations,
104        },
105        other => other,
106    }
107}
108
109fn lower_repl_operator(expr: Expr) -> Expr {
110    match expr {
111        Expr::Symbol(symbol) => Expr::Symbol(match symbol.name.as_ref() {
112            "+" if symbol.namespace.is_none() => Symbol::qualified("math", "add"),
113            "-" if symbol.namespace.is_none() => Symbol::qualified("math", "sub"),
114            "*" if symbol.namespace.is_none() => Symbol::qualified("math", "mul"),
115            "/" if symbol.namespace.is_none() => Symbol::qualified("math", "div"),
116            _ => symbol,
117        }),
118        other => lower_repl_expr(other),
119    }
120}
121
122fn render_value(cx: &mut Cx, codec: &Symbol, value: &Value) -> Result<String> {
123    let expr = value.object().as_expr(cx)?;
124    encode_with_codec(cx, codec, &expr, EncodeOptions::default())?.into_text()
125}