Skip to main content

sim_lib_repl/
eval.rs

1use std::io::{BufRead, Write};
2
3use sim_codec::{
4    DecodePosition, DecodedForm, Input, Output, decode_default_with_codec, encode_with_codec,
5};
6use sim_kernel::{Cx, EncodeOptions, Expr, ReadPolicy, Symbol};
7
8/// Decodes, evaluates, and re-encodes one source line through `codec`.
9pub fn eval_line(cx: &mut Cx, codec: &Symbol, line: &str) -> Result<String, String> {
10    let decoded = match decode_default_with_codec(
11        cx,
12        codec,
13        Input::Text(line.to_owned()),
14        ReadPolicy::default(),
15        DecodePosition::Eval,
16    )
17    .map_err(|err| format!("{err:?}"))?
18    {
19        DecodedForm::Term(term) => Expr::from(term),
20        DecodedForm::Datum(datum) => Expr::from(datum),
21    };
22    let value = cx.eval_expr(decoded).map_err(|err| format!("{err:?}"))?;
23    let expr = value
24        .object()
25        .as_expr(cx)
26        .map_err(|err| format!("{err:?}"))?;
27    match encode_with_codec(cx, codec, &expr, EncodeOptions::default())
28        .map_err(|err| format!("{err:?}"))?
29    {
30        Output::Text(text) => Ok(text),
31        Output::Bytes(_) => Ok("<bytes>".to_owned()),
32    }
33}
34
35/// Runs read-eval-print over `reader`, writing one output line per non-empty
36/// input line.
37pub fn run_repl_lines<R, W>(
38    cx: &mut Cx,
39    codec: &Symbol,
40    reader: R,
41    writer: &mut W,
42) -> Result<(), String>
43where
44    R: BufRead,
45    W: Write,
46{
47    for line in reader.lines() {
48        let line = line.map_err(|err| format!("read stdin: {err}"))?;
49        let trimmed = line.trim();
50        if trimmed.is_empty() {
51            continue;
52        }
53        match eval_line(cx, codec, trimmed) {
54            Ok(result) => writeln!(writer, "{result}"),
55            Err(err) => writeln!(writer, "error: {err}"),
56        }
57        .map_err(|err| format!("write stdout: {err}"))?;
58    }
59    Ok(())
60}
61
62#[cfg(test)]
63mod tests {
64    use std::sync::Arc;
65
66    use sim::kernel::{Cx, DefaultFactory, EagerPolicy, Symbol};
67
68    use super::{eval_line, run_repl_lines};
69
70    fn boot() -> Cx {
71        let mut cx = Cx::new(Arc::new(EagerPolicy), Arc::new(DefaultFactory));
72        sim::runtime::install_core_runtime(&mut cx);
73        sim::numbers_prelude::NumbersPreludeLib::new()
74            .install_all(&mut cx)
75            .unwrap();
76        let lisp = sim::codec_lisp::LispCodecLib::new(cx.registry_mut().fresh_codec_id()).unwrap();
77        cx.load_lib(&lisp).unwrap();
78        cx
79    }
80
81    #[test]
82    fn eval_line_computes_value() {
83        let mut cx = boot();
84        let codec = Symbol::qualified("codec", "lisp");
85
86        let result = eval_line(&mut cx, &codec, "(math/add (math/mul 6 7) 0)").unwrap();
87
88        assert_eq!(result, "42");
89    }
90
91    #[test]
92    fn run_repl_lines_writes_non_empty_results() {
93        let mut cx = boot();
94        let codec = Symbol::qualified("codec", "lisp");
95        let mut output = Vec::new();
96
97        run_repl_lines(
98            &mut cx,
99            &codec,
100            "\n(math/add 1 2)\n".as_bytes(),
101            &mut output,
102        )
103        .unwrap();
104
105        assert_eq!(String::from_utf8(output).unwrap(), "3\n");
106    }
107}