1use std::sync::Arc;
18
19use tatara_lisp_eval::{Interpreter, Value};
20
21use crate::pipeline::{builtin_names, eval_prepared, prepare, RunError};
22use crate::uses::{Entry, Loader};
23
24pub struct Hosted<H: 'static> {
26 interp: Interpreter<H>,
27 entry: blue_lang_check::Namespace,
30}
31
32pub fn load_hosted<H: 'static>(
41 entry: Entry<'_>,
42 loader: &dyn Loader,
43 host: &mut H,
44 install: impl FnOnce(&mut Interpreter<H>),
45) -> Result<Hosted<H>, RunError> {
46 let mut interp = crate::interpreter(host);
50 install(&mut interp);
51 let prepared = prepare(entry, loader, None, &builtin_names(&interp))?;
52 eval_prepared(&mut interp, &prepared, host)?;
53 let entry = prepared.entry_namespace();
54 Ok(Hosted { interp, entry })
55}
56
57impl<H: 'static> Hosted<H> {
58 fn lookup(&self, name: &str) -> Option<Value> {
63 match name.split_once("::") {
64 Some((blue_lang_syntax::BUILTIN_QUALIFIER, bare)) => self.interp.lookup_global(bare),
65 Some((pkg, bare)) => self.interp.lookup_global(&blue_lang_check::names::key(
66 &blue_lang_check::Namespace::Bidama(pkg.to_owned()),
67 bare,
68 )),
69 None => self
70 .interp
71 .lookup_global(&blue_lang_check::names::key(&self.entry, name))
72 .or_else(|| self.interp.lookup_global(name)),
73 }
74 }
75
76 #[must_use]
78 pub fn defines(&self, name: &str) -> bool {
79 matches!(
80 self.lookup(name),
81 Some(Value::Closure(_) | Value::NativeFn(_))
82 )
83 }
84
85 pub fn call(&mut self, name: &str, args: Vec<Value>, host: &mut H) -> Result<Value, RunError> {
92 let callee = match self.lookup(name) {
93 Some(v @ (Value::Closure(_) | Value::NativeFn(_))) => v,
94 Some(other) => {
95 return Err(RunError::Eval(format!(
96 "{name} is not a function (it is {})",
97 kind(&other)
98 )))
99 }
100 None => return Err(RunError::Eval(format!("no function named {name}"))),
101 };
102 self.interp
103 .apply_external_value(&callee, args, host, tatara_lisp::Span::synthetic())
104 .map_err(|e| RunError::Eval(format!("{name}: {}", e.short_message())))
105 }
106}
107
108#[must_use]
110pub fn text(s: &str) -> Value {
111 Value::Str(Arc::from(s))
112}
113
114fn kind(v: &Value) -> &'static str {
115 match v {
116 Value::Nil => "nil",
117 Value::Bool(_) => "a boolean",
118 Value::Int(_) | Value::Float(_) => "a number",
119 Value::Str(_) => "a string",
120 Value::List(_) => "a list",
121 Value::Map(_) => "a map",
122 _ => "a value",
123 }
124}
125
126#[cfg(test)]
127mod tests {
128 use super::*;
129 use crate::uses::NoLoader;
130 use tatara_lisp_eval::Arity;
131
132 #[derive(Default)]
134 struct Notes(Vec<String>);
135
136 fn install(interp: &mut Interpreter<Notes>) {
137 interp.register_fn(
138 "note",
139 Arity::Exact(1),
140 |args: &[Value], h: &mut Notes, _span| {
141 if let Value::Str(s) = &args[0] {
142 h.0.push(s.to_string());
143 }
144 Ok(Value::Nil)
145 },
146 );
147 }
148
149 fn load(src: &str, host: &mut Notes) -> Result<Hosted<Notes>, RunError> {
150 load_hosted(Entry::anonymous(src), &NoLoader, host, install)
151 }
152
153 fn as_text(v: &Value) -> String {
154 match v {
155 Value::Str(s) => s.to_string(),
156 other => format!("{other:?}"),
157 }
158 }
159
160 #[test]
161 fn an_empty_program_defines_nothing() {
162 let mut host = Notes::default();
163 let mut p = load("", &mut host).expect("an empty program loads");
164 assert!(!p.defines("run"));
165 let err = p.call("run", vec![], &mut host).unwrap_err().to_string();
166 assert!(err.contains("no function named run"), "{err}");
167 }
168
169 #[test]
170 fn a_call_returns_the_function_value_for_its_arguments() {
171 let mut host = Notes::default();
172 let src = "def greet(who)\n \"hello #{who}\"\nend\n";
173 let mut p = load(src, &mut host).unwrap();
174 assert!(p.defines("greet"));
175 let v = p.call("greet", vec![text("blue")], &mut host).unwrap();
176 assert_eq!(as_text(&v), "hello blue");
177 let again = p.call("greet", vec![text("blue")], &mut host).unwrap();
179 assert_eq!(as_text(&again), as_text(&v));
180 }
181
182 #[test]
183 fn host_primitives_reach_the_host_state() {
184 let mut host = Notes::default();
185 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";
186 let mut p = load(src, &mut host).unwrap();
187 let v = p.call("run", vec![text("abcd")], &mut host).unwrap();
188 assert!(matches!(v, Value::Int(4)), "{v:?}");
189 assert_eq!(host.0, vec!["saw abcd".to_owned(), "done".to_owned()]);
190 }
191
192 #[test]
193 fn a_type_error_stops_the_load_before_anything_runs() {
194 let mut host = Notes::default();
195 let src = "note(\"top level ran\")\ndef f(x: Int) -> Int\n \"no\"\nend\n";
196 let err = load(src, &mut host).err().expect("refused").to_string();
197 assert!(err.contains("error[B0003]"), "{err}");
198 assert!(
199 host.0.is_empty(),
200 "no top-level form may run before the check passes"
201 );
202 }
203
204 #[test]
205 fn a_qualified_name_calls_a_bidamas_definition_and_a_bare_one_does_not() {
206 let mut host = Notes::default();
207 let loader = crate::uses::tests::MemLoader(
208 [("tama", "def exports()\n \"tama rows\"\nend\n")]
209 .into_iter()
210 .collect(),
211 );
212 let mut p = load_hosted(
213 Entry::anonymous("use(\"tama\")\n"),
214 &loader,
215 &mut host,
216 install,
217 )
218 .unwrap();
219 assert!(p.defines("tama::exports"));
220 let v = p.call("tama::exports", vec![], &mut host).unwrap();
221 assert_eq!(as_text(&v), "tama rows");
222 assert!(!p.defines("exports"));
223 assert!(!p.defines("other::exports"));
224 assert!(p.defines("blue::length"));
225 let err = p
226 .call("tama::absent", vec![], &mut host)
227 .unwrap_err()
228 .to_string();
229 assert!(err.contains("no function named tama::absent"), "{err}");
230 }
231
232 #[test]
233 fn a_raise_and_a_non_function_are_typed_errors_naming_the_function() {
234 let mut host = Notes::default();
235 let src = "LIMIT = 3\ndef boom()\n throw(error(\"bad\"))\nend\n";
236 let mut p = load(src, &mut host).unwrap();
237 let raised = p.call("boom", vec![], &mut host).unwrap_err().to_string();
238 assert!(raised.contains("boom"), "{raised}");
239 let not_fn = p.call("LIMIT", vec![], &mut host).unwrap_err().to_string();
240 assert!(not_fn.contains("not a function"), "{not_fn}");
241 }
242}