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(Box::new(crate::observe::Fault::from(
96 format!("{name} is not a function (it is {})", kind(&other)),
97 ))))
98 }
99 None => {
100 return Err(RunError::Eval(Box::new(
101 format!("no function named {name}").into(),
102 )))
103 }
104 };
105 self.interp
106 .apply_external_value(&callee, args, host, tatara_lisp::Span::synthetic())
107 .map_err(|e| RunError::Eval(Box::new(format!("{name}: {}", e.short_message()).into())))
108 }
109}
110
111#[derive(Debug, thiserror::Error)]
113pub enum FramedError {
114 #[error("{0}")]
116 Run(#[from] RunError),
117 #[error("it names {} outside its frame: {}", if .names.len() == 1 { "a name" } else { "names" }, .names.join(", "))]
119 Escapes { names: Vec<String> },
120}
121
122#[derive(Debug, thiserror::Error)]
124pub enum CallError {
125 #[error("no function named {0}")]
126 Undefined(String),
127 #[error("{name} is not a function (it is {kind})")]
128 NotAFunction { name: String, kind: &'static str },
129 #[error("{name}: {message}")]
131 Exhausted { name: String, message: String },
132 #[error("{name}: {message}")]
134 Raised { name: String, message: String },
135}
136
137pub fn load_hosted_in_frame<H: 'static>(
152 entry: Entry<'_>,
153 loader: &dyn Loader,
154 host: &mut H,
155 install: impl FnOnce(&mut Interpreter<H>),
156 permits: &dyn Fn(&str) -> bool,
157 budget: tatara_lisp_eval::vm::Budget,
158) -> Result<Hosted<H>, FramedError> {
159 let mut interp = crate::interpreter(host);
160 install(&mut interp);
161 let prepared = prepare(entry, loader, None, &builtin_names(&interp))?;
162 let escapes = blue_lang_waku::free_names(prepared.erased(), permits);
163 if !escapes.is_empty() {
164 return Err(FramedError::Escapes {
165 names: escapes.into_iter().collect(),
166 });
167 }
168 for name in interp.reserved_head_names() {
169 if !permits(&name) && interp.lookup_global(&name).is_some() {
170 let refused = name.clone();
171 interp.register_fn(
172 name,
173 tatara_lisp_eval::Arity::Any,
174 move |_: &[Value], _: &mut H, span| {
175 Err(tatara_lisp_eval::EvalError::native_fn(
176 refused.to_string(),
177 "outside this program's frame",
178 span,
179 ))
180 },
181 );
182 }
183 }
184 interp
185 .set_budget(budget)
186 .map_err(|e| RunError::Eval(Box::new(e.to_string().into())))?;
187 eval_prepared(&mut interp, &prepared, host)?;
188 let entry = prepared.entry_namespace();
189 Ok(Hosted { interp, entry })
190}
191
192impl<H: 'static> Hosted<H> {
193 pub fn call_budgeted(
202 &mut self,
203 name: &str,
204 args: Vec<Value>,
205 host: &mut H,
206 budget: tatara_lisp_eval::vm::Budget,
207 ) -> Result<Value, CallError> {
208 let callee = self.callee(name)?;
209 if let Err(e) = self.interp.set_budget(budget) {
210 return Err(CallError::Raised {
211 name: name.to_string(),
212 message: e.to_string(),
213 });
214 }
215 self.apply(name, &callee, args, host)
216 }
217
218 pub fn try_call(
219 &mut self,
220 name: &str,
221 args: Vec<Value>,
222 host: &mut H,
223 ) -> Result<Value, CallError> {
224 let callee = self.callee(name)?;
225 self.apply(name, &callee, args, host)
226 }
227
228 fn callee(&self, name: &str) -> Result<Value, CallError> {
229 match self.lookup(name) {
230 Some(v @ (Value::Closure(_) | Value::NativeFn(_))) => Ok(v),
231 Some(other) => Err(CallError::NotAFunction {
232 name: name.to_string(),
233 kind: kind(&other),
234 }),
235 None => Err(CallError::Undefined(name.to_string())),
236 }
237 }
238
239 fn apply(
240 &mut self,
241 name: &str,
242 callee: &Value,
243 args: Vec<Value>,
244 host: &mut H,
245 ) -> Result<Value, CallError> {
246 self.interp
247 .apply_external_value(callee, args, host, tatara_lisp::Span::synthetic())
248 .map_err(|e| match e {
249 tatara_lisp_eval::EvalError::BudgetExceeded { .. } => CallError::Exhausted {
250 name: name.to_string(),
251 message: e.short_message(),
252 },
253 other => CallError::Raised {
254 name: name.to_string(),
255 message: other.short_message(),
256 },
257 })
258 }
259}
260
261#[must_use]
263pub fn text(s: &str) -> Value {
264 Value::Str(Arc::from(s))
265}
266
267fn kind(v: &Value) -> &'static str {
268 match v {
269 Value::Nil => "nil",
270 Value::Bool(_) => "a boolean",
271 Value::Int(_) | Value::Float(_) => "a number",
272 Value::Str(_) => "a string",
273 Value::List(_) => "a list",
274 Value::Map(_) => "a map",
275 _ => "a value",
276 }
277}
278
279#[cfg(test)]
280mod tests {
281 use super::*;
282 use crate::uses::NoLoader;
283 use tatara_lisp_eval::Arity;
284
285 #[derive(Default)]
287 struct Notes(Vec<String>);
288
289 fn install(interp: &mut Interpreter<Notes>) {
290 interp.register_fn(
291 "note",
292 Arity::Exact(1),
293 |args: &[Value], h: &mut Notes, _span| {
294 if let Value::Str(s) = &args[0] {
295 h.0.push(s.to_string());
296 }
297 Ok(Value::Nil)
298 },
299 );
300 }
301
302 fn load(src: &str, host: &mut Notes) -> Result<Hosted<Notes>, RunError> {
303 load_hosted(Entry::anonymous(src), &NoLoader, host, install)
304 }
305
306 fn as_text(v: &Value) -> String {
307 match v {
308 Value::Str(s) => s.to_string(),
309 other => format!("{other:?}"),
310 }
311 }
312
313 #[test]
314 fn an_empty_program_defines_nothing() {
315 let mut host = Notes::default();
316 let mut p = load("", &mut host).expect("an empty program loads");
317 assert!(!p.defines("run"));
318 let err = p.call("run", vec![], &mut host).unwrap_err().to_string();
319 assert!(err.contains("no function named run"), "{err}");
320 }
321
322 #[test]
323 fn a_call_returns_the_function_value_for_its_arguments() {
324 let mut host = Notes::default();
325 let src = "def greet(who)\n \"hello #{who}\"\nend\n";
326 let mut p = load(src, &mut host).unwrap();
327 assert!(p.defines("greet"));
328 let v = p.call("greet", vec![text("blue")], &mut host).unwrap();
329 assert_eq!(as_text(&v), "hello blue");
330 let again = p.call("greet", vec![text("blue")], &mut host).unwrap();
332 assert_eq!(as_text(&again), as_text(&v));
333 }
334
335 #[test]
336 fn host_primitives_reach_the_host_state() {
337 let mut host = Notes::default();
338 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";
339 let mut p = load(src, &mut host).unwrap();
340 let v = p.call("run", vec![text("abcd")], &mut host).unwrap();
341 assert!(matches!(v, Value::Int(4)), "{v:?}");
342 assert_eq!(host.0, vec!["saw abcd".to_owned(), "done".to_owned()]);
343 }
344
345 #[test]
346 fn a_type_error_stops_the_load_before_anything_runs() {
347 let mut host = Notes::default();
348 let src = "note(\"top level ran\")\ndef f(x: Int) -> Int\n \"no\"\nend\n";
349 let err = load(src, &mut host).err().expect("refused").to_string();
350 assert!(err.contains("error[B0003]"), "{err}");
351 assert!(
352 host.0.is_empty(),
353 "no top-level form may run before the check passes"
354 );
355 }
356
357 #[test]
358 fn a_qualified_name_calls_a_bidamas_definition_and_a_bare_one_does_not() {
359 let mut host = Notes::default();
360 let loader = crate::uses::tests::MemLoader(
361 [("tama", "def exports()\n \"tama rows\"\nend\n")]
362 .into_iter()
363 .collect(),
364 );
365 let mut p = load_hosted(
366 Entry::anonymous("use(\"tama\")\n"),
367 &loader,
368 &mut host,
369 install,
370 )
371 .unwrap();
372 assert!(p.defines("tama::exports"));
373 let v = p.call("tama::exports", vec![], &mut host).unwrap();
374 assert_eq!(as_text(&v), "tama rows");
375 assert!(!p.defines("exports"));
376 assert!(!p.defines("other::exports"));
377 assert!(p.defines("blue::length"));
378 let err = p
379 .call("tama::absent", vec![], &mut host)
380 .unwrap_err()
381 .to_string();
382 assert!(err.contains("no function named tama::absent"), "{err}");
383 }
384
385 #[test]
386 fn a_raise_and_a_non_function_are_typed_errors_naming_the_function() {
387 let mut host = Notes::default();
388 let src = "LIMIT = 3\ndef boom()\n throw(error(\"bad\"))\nend\n";
389 let mut p = load(src, &mut host).unwrap();
390 let raised = p.call("boom", vec![], &mut host).unwrap_err().to_string();
391 assert!(raised.contains("boom"), "{raised}");
392 let not_fn = p.call("LIMIT", vec![], &mut host).unwrap_err().to_string();
393 assert!(not_fn.contains("not a function"), "{not_fn}");
394 }
395}