blue_lang_runtime/
hosted.rs1use std::sync::Arc;
18
19use tatara_lisp_eval::{Interpreter, Value};
20
21use crate::pipeline::{eval_prepared, prepare, RunError};
22use crate::uses::{Entry, Loader};
23
24pub struct Hosted<H: 'static> {
26 interp: Interpreter<H>,
27}
28
29pub fn load_hosted<H: 'static>(
38 entry: Entry<'_>,
39 loader: &dyn Loader,
40 host: &mut H,
41 install: impl FnOnce(&mut Interpreter<H>),
42) -> Result<Hosted<H>, RunError> {
43 let prepared = prepare(entry, loader, None)?;
44 let mut interp = crate::interpreter(host);
45 install(&mut interp);
46 eval_prepared(&mut interp, &prepared, host)?;
47 Ok(Hosted { interp })
48}
49
50impl<H: 'static> Hosted<H> {
51 #[must_use]
53 pub fn defines(&self, name: &str) -> bool {
54 matches!(
55 self.interp.lookup_global(name),
56 Some(Value::Closure(_) | Value::NativeFn(_))
57 )
58 }
59
60 pub fn call(&mut self, name: &str, args: Vec<Value>, host: &mut H) -> Result<Value, RunError> {
67 let callee = match self.interp.lookup_global(name) {
68 Some(v @ (Value::Closure(_) | Value::NativeFn(_))) => v,
69 Some(other) => {
70 return Err(RunError::Eval(format!(
71 "{name} is not a function (it is {})",
72 kind(&other)
73 )))
74 }
75 None => return Err(RunError::Eval(format!("no function named {name}"))),
76 };
77 self.interp
78 .apply_external_value(&callee, args, host, tatara_lisp::Span::synthetic())
79 .map_err(|e| RunError::Eval(format!("{name}: {}", e.short_message())))
80 }
81}
82
83#[must_use]
85pub fn text(s: &str) -> Value {
86 Value::Str(Arc::from(s))
87}
88
89fn kind(v: &Value) -> &'static str {
90 match v {
91 Value::Nil => "nil",
92 Value::Bool(_) => "a boolean",
93 Value::Int(_) | Value::Float(_) => "a number",
94 Value::Str(_) => "a string",
95 Value::List(_) => "a list",
96 Value::Map(_) => "a map",
97 _ => "a value",
98 }
99}
100
101#[cfg(test)]
102mod tests {
103 use super::*;
104 use crate::uses::NoLoader;
105 use tatara_lisp_eval::Arity;
106
107 #[derive(Default)]
109 struct Notes(Vec<String>);
110
111 fn install(interp: &mut Interpreter<Notes>) {
112 interp.register_fn(
113 "note",
114 Arity::Exact(1),
115 |args: &[Value], h: &mut Notes, _span| {
116 if let Value::Str(s) = &args[0] {
117 h.0.push(s.to_string());
118 }
119 Ok(Value::Nil)
120 },
121 );
122 }
123
124 fn load(src: &str, host: &mut Notes) -> Result<Hosted<Notes>, RunError> {
125 load_hosted(Entry::anonymous(src), &NoLoader, host, install)
126 }
127
128 fn as_text(v: &Value) -> String {
129 match v {
130 Value::Str(s) => s.to_string(),
131 other => format!("{other:?}"),
132 }
133 }
134
135 #[test]
136 fn an_empty_program_defines_nothing() {
137 let mut host = Notes::default();
138 let mut p = load("", &mut host).expect("an empty program loads");
139 assert!(!p.defines("run"));
140 let err = p.call("run", vec![], &mut host).unwrap_err().to_string();
141 assert!(err.contains("no function named run"), "{err}");
142 }
143
144 #[test]
145 fn a_call_returns_the_function_value_for_its_arguments() {
146 let mut host = Notes::default();
147 let src = "def greet(who)\n \"hello #{who}\"\nend\n";
148 let mut p = load(src, &mut host).unwrap();
149 assert!(p.defines("greet"));
150 let v = p.call("greet", vec![text("blue")], &mut host).unwrap();
151 assert_eq!(as_text(&v), "hello blue");
152 let again = p.call("greet", vec![text("blue")], &mut host).unwrap();
154 assert_eq!(as_text(&again), as_text(&v));
155 }
156
157 #[test]
158 fn host_primitives_reach_the_host_state() {
159 let mut host = Notes::default();
160 let src = "def run(x)\n note(\"saw #{x}\")\n note(\"done\")\n size_of(x)\nend\ndef size_of(s)\n length(s)\nend\n";
161 let mut p = load(src, &mut host).unwrap();
162 let v = p.call("run", vec![text("abcd")], &mut host).unwrap();
163 assert!(matches!(v, Value::Int(4)), "{v:?}");
164 assert_eq!(host.0, vec!["saw abcd".to_owned(), "done".to_owned()]);
165 }
166
167 #[test]
168 fn a_type_error_stops_the_load_before_anything_runs() {
169 let mut host = Notes::default();
170 let src = "note(\"top level ran\")\ndef f(x: Int) -> Int\n \"no\"\nend\n";
171 let err = load(src, &mut host).err().expect("refused").to_string();
172 assert!(err.contains("type error"), "{err}");
173 assert!(
174 host.0.is_empty(),
175 "no top-level form may run before the check passes"
176 );
177 }
178
179 #[test]
180 fn a_raise_and_a_non_function_are_typed_errors_naming_the_function() {
181 let mut host = Notes::default();
182 let src = "LIMIT = 3\ndef boom()\n raise(\"bad\")\nend\n";
183 let mut p = load(src, &mut host).unwrap();
184 let raised = p.call("boom", vec![], &mut host).unwrap_err().to_string();
185 assert!(raised.contains("boom"), "{raised}");
186 let not_fn = p.call("LIMIT", vec![], &mut host).unwrap_err().to_string();
187 assert!(not_fn.contains("not a function"), "{not_fn}");
188 }
189}