outl_exec/runtimes/
lisp.rs1use std::sync::{Arc, Mutex};
11use std::time::Instant;
12
13use steel::steel_vm::engine::Engine;
14use steel::steel_vm::register_fn::RegisterFn;
15use steel::SteelVal;
16
17use crate::runtime::{ExecContext, ExecError, ExecOutput, ExitStatus, OutputFormat, Runtime};
18
19pub struct LispRuntime;
21
22impl Runtime for LispRuntime {
23 fn language(&self) -> &'static str {
24 "lisp"
25 }
26
27 fn execute(&self, source: &str, _ctx: &ExecContext) -> Result<ExecOutput, ExecError> {
28 let start = Instant::now();
29
30 let sink: Arc<Mutex<String>> = Arc::new(Mutex::new(String::new()));
31 let mut engine = Engine::new();
32
33 install_printers(&mut engine, sink.clone());
39
40 match engine.run(source.to_string()) {
41 Ok(values) => {
42 let mut stdout = sink.lock().unwrap().clone();
43 if stdout.is_empty() {
44 if let Some(last) = values.last() {
45 stdout.push_str(&steel_value_to_string(last));
46 }
47 }
48 Ok(ExecOutput {
49 stdout,
50 stderr: String::new(),
51 duration: start.elapsed(),
52 exit: ExitStatus::Ok,
53 format: OutputFormat::Text,
54 })
55 }
56 Err(e) => Ok(ExecOutput {
57 stdout: sink.lock().unwrap().clone(),
58 stderr: format!("{e}"),
59 duration: start.elapsed(),
60 exit: ExitStatus::Trap("steel-error".into()),
61 format: OutputFormat::Text,
62 }),
63 }
64 }
65}
66
67fn install_printers(engine: &mut Engine, sink: Arc<Mutex<String>>) {
68 let s1 = sink.clone();
69 engine.register_fn("display", move |v: SteelVal| {
70 s1.lock().unwrap().push_str(&steel_value_to_string(&v));
71 });
72 let s2 = sink.clone();
73 engine.register_fn("displayln", move |v: SteelVal| {
74 let mut s = s2.lock().unwrap();
75 s.push_str(&steel_value_to_string(&v));
76 s.push('\n');
77 });
78 let s3 = sink.clone();
79 engine.register_fn("print", move |v: SteelVal| {
80 s3.lock().unwrap().push_str(&steel_value_to_string(&v));
81 });
82 let s4 = sink.clone();
83 engine.register_fn("println", move |v: SteelVal| {
84 let mut s = s4.lock().unwrap();
85 s.push_str(&steel_value_to_string(&v));
86 s.push('\n');
87 });
88 let s5 = sink;
89 engine.register_fn("newline", move || {
90 s5.lock().unwrap().push('\n');
91 });
92}
93
94fn steel_value_to_string(v: &SteelVal) -> String {
96 match v {
97 SteelVal::StringV(s) => s.to_string(),
98 other => other.to_string(),
99 }
100}
101
102#[cfg(test)]
103mod tests {
104 use super::*;
105
106 fn run(src: &str) -> String {
107 LispRuntime
108 .execute(src, &ExecContext::default())
109 .unwrap()
110 .stdout
111 }
112
113 #[test]
114 fn simple_addition() {
115 assert_eq!(run("(+ 1 2)"), "3");
116 }
117
118 #[test]
119 fn nested_arithmetic() {
120 assert_eq!(run("(* (+ 1 2) (- 10 4))"), "18");
121 }
122
123 #[test]
124 fn explicit_display() {
125 assert_eq!(run("(display \"hello\")"), "hello");
126 }
127
128 #[test]
129 fn displayln_appends_newline() {
130 assert_eq!(run("(displayln \"hi\")"), "hi\n");
131 }
132
133 #[test]
134 fn list_operations() {
135 let out = run("(map (lambda (x) (* x x)) (list 1 2 3))");
138 assert!(out.contains("1") && out.contains("4") && out.contains("9"));
139 }
140
141 #[test]
142 fn syntax_error_returns_trap() {
143 let out = LispRuntime
144 .execute("(+ 1", &ExecContext::default())
145 .unwrap();
146 assert!(matches!(out.exit, ExitStatus::Trap(_)));
147 assert!(!out.stderr.is_empty());
148 }
149}