1use std::cell::RefCell;
2use std::collections::HashSet;
3use std::rc::Rc;
4
5use sema_core::{
6 intern, resolve, CallFrame, Env, EvalContext, Lambda, Macro, MultiMethod, NativeFn, SemaError,
7 Span, Spur, Thunk, Value, ValueView,
8};
9
10use crate::special_forms;
11
12pub enum Trampoline {
14 Value(Value),
15 Eval(Value, Env),
16}
17
18pub type EvalResult = Result<Value, SemaError>;
19
20pub fn create_module_env(env: &Env) -> Env {
22 let mut current = env.clone();
24 loop {
25 let parent = current.parent.clone();
26 match parent {
27 Some(p) => current = (*p).clone(),
28 None => break,
29 }
30 }
31 Env::with_parent(Rc::new(current))
32}
33
34fn span_of_expr(ctx: &EvalContext, expr: &Value) -> Option<Span> {
36 if let Some(items) = expr.as_list_rc() {
37 let ptr = Rc::as_ptr(&items) as usize;
38 ctx.lookup_span(ptr)
39 } else {
40 None
41 }
42}
43
44struct CallStackGuard<'a> {
46 ctx: &'a EvalContext,
47 entry_depth: usize,
48}
49
50impl Drop for CallStackGuard<'_> {
51 fn drop(&mut self) {
52 self.ctx.truncate_call_stack(self.entry_depth);
53 }
54}
55
56fn collect_native_names(env: &Env) -> HashSet<Spur> {
59 env.all_names()
60 .into_iter()
61 .filter(|&spur| env.get(spur).is_some_and(|v| v.is_native_fn()))
62 .collect()
63}
64
65pub struct Interpreter {
67 pub global_env: Rc<Env>,
68 pub ctx: EvalContext,
69}
70
71impl Default for Interpreter {
72 fn default() -> Self {
73 Self::new()
74 }
75}
76
77impl Interpreter {
78 pub fn new() -> Self {
79 let env = Env::new();
80 let ctx = EvalContext::new();
81 sema_core::set_eval_callback(&ctx, eval_value);
83 sema_core::set_call_callback(&ctx, call_value);
84 sema_stdlib::register_stdlib(&env, &sema_core::Sandbox::allow_all());
86 #[cfg(not(target_arch = "wasm32"))]
88 {
89 sema_llm::builtins::reset_runtime_state();
90 sema_llm::builtins::register_llm_builtins(&env, &sema_core::Sandbox::allow_all());
91 sema_llm::builtins::set_eval_callback(eval_value);
92 }
93 let global_env = Rc::new(env);
94 register_vm_delegates(&global_env);
95 load_prelude(&ctx, &global_env);
96 Interpreter { global_env, ctx }
97 }
98
99 pub fn new_with_sandbox(sandbox: &sema_core::Sandbox) -> Self {
100 let env = Env::new();
101 let ctx = EvalContext::new_with_sandbox(sandbox.clone());
102 sema_core::set_eval_callback(&ctx, eval_value);
103 sema_core::set_call_callback(&ctx, call_value);
104 sema_stdlib::register_stdlib(&env, sandbox);
105 #[cfg(not(target_arch = "wasm32"))]
106 {
107 sema_llm::builtins::reset_runtime_state();
108 sema_llm::builtins::register_llm_builtins(&env, sandbox);
109 sema_llm::builtins::set_eval_callback(eval_value);
110 }
111 let global_env = Rc::new(env);
112 register_vm_delegates(&global_env);
113 load_prelude(&ctx, &global_env);
114 Interpreter { global_env, ctx }
115 }
116
117 pub fn eval(&self, expr: &Value) -> EvalResult {
118 eval_value(&self.ctx, expr, &Env::with_parent(self.global_env.clone()))
119 }
120
121 pub fn eval_str(&self, input: &str) -> EvalResult {
122 eval_string(&self.ctx, input, &Env::with_parent(self.global_env.clone()))
123 }
124
125 pub fn eval_in_global(&self, expr: &Value) -> EvalResult {
127 eval_value(&self.ctx, expr, &self.global_env)
128 }
129
130 pub fn eval_str_in_global(&self, input: &str) -> EvalResult {
132 eval_string(&self.ctx, input, &self.global_env)
133 }
134
135 pub fn eval_str_compiled(&self, input: &str) -> EvalResult {
137 let (exprs, spans) = sema_reader::read_many_with_spans(input)?;
138 self.ctx.merge_span_table(spans);
139 if exprs.is_empty() {
140 return Ok(Value::nil());
141 }
142
143 let mut expanded = Vec::new();
144 for expr in &exprs {
145 let exp = self.expand_for_vm(expr)?;
146 expanded.push(exp);
147 }
148
149 let known_natives = collect_native_names(&self.global_env);
150 let prog = sema_vm::compile_program(&expanded, Some(known_natives))?;
151 let mut vm = sema_vm::VM::new(
152 self.global_env.clone(),
153 prog.functions,
154 &prog.native_table,
155 prog.main_cache_slots,
156 )?;
157 vm.execute(prog.closure, &self.ctx)
158 }
159
160 pub fn compile_to_bytecode(&self, input: &str) -> Result<sema_vm::CompileResult, SemaError> {
163 let (exprs, spans) = sema_reader::read_many_with_spans(input)?;
164 self.ctx.merge_span_table(spans);
165
166 let mut expanded = Vec::new();
167 for expr in &exprs {
168 let exp = self.expand_for_vm(expr)?;
169 if !exp.is_nil() {
170 expanded.push(exp);
171 }
172 }
173
174 if expanded.is_empty() {
175 expanded.push(Value::nil());
176 }
177
178 let prog = sema_vm::compile_program(&expanded, None)?;
179 Ok(sema_vm::CompileResult::new(
180 prog.closure.func.chunk.clone(),
181 prog.functions.iter().map(|f| (**f).clone()).collect(),
182 ))
183 }
184
185 pub fn expand_for_vm(&self, expr: &Value) -> EvalResult {
189 if let Some(items) = expr.as_list() {
190 if let Some(s) = items.first().and_then(|v| v.as_symbol_spur()) {
191 let name = resolve(s);
192 if name == "defmacro" {
193 eval_value(&self.ctx, expr, &self.global_env)?;
194 return Ok(Value::nil());
195 }
196 if name == "begin" || name == "progn" {
197 let mut new_items = vec![Value::symbol_from_spur(s)];
198 let mut changed = false;
199 for item in &items[1..] {
200 let expanded = self.expand_for_vm(item)?;
201 if expanded.raw_bits() != item.raw_bits() {
202 changed = true;
203 }
204 new_items.push(expanded);
205 }
206 if !changed {
207 return Ok(expr.clone());
208 }
209 return Ok(Value::list(new_items));
210 }
211 }
212 }
213 self.expand_macros(expr)
214 }
215
216 fn expand_macros(&self, expr: &Value) -> EvalResult {
220 if let Some(items) = expr.as_list() {
221 if !items.is_empty() {
222 if let Some(s) = items.first().and_then(|v| v.as_symbol_spur()) {
223 let name = resolve(s);
224 if name == "quote" {
225 return Ok(expr.clone());
226 }
227 if let Some(mac_val) = self.global_env.get(s) {
228 if let Some(mac) = mac_val.as_macro_rc() {
229 let expanded =
230 apply_macro(&self.ctx, &mac, &items[1..], &self.global_env)?;
231 return self.expand_macros(&expanded);
232 }
233 }
234 }
235 let expanded: Vec<Value> = items
236 .iter()
237 .map(|v| self.expand_macros(v))
238 .collect::<Result<_, _>>()?;
239 let changed = expanded
241 .iter()
242 .zip(items.iter())
243 .any(|(a, b)| a.raw_bits() != b.raw_bits());
244 if !changed {
245 return Ok(expr.clone());
246 }
247 return Ok(Value::list(expanded));
248 }
249 }
250 Ok(expr.clone())
251 }
252}
253
254pub fn eval_string(ctx: &EvalContext, input: &str, env: &Env) -> EvalResult {
256 let (exprs, spans) = sema_reader::read_many_with_spans(input)?;
257 ctx.merge_span_table(spans);
258 ctx.max_eval_depth.set(0);
259 let mut result = Value::nil();
260 for expr in &exprs {
261 result = eval_value(ctx, expr, env)?;
262 }
263 Ok(result)
264}
265
266pub fn eval(ctx: &EvalContext, expr: &Value, env: &Env) -> EvalResult {
268 eval_value(ctx, expr, env)
269}
270
271#[cfg(target_arch = "wasm32")]
276const MAX_EVAL_DEPTH: usize = 256;
277#[cfg(not(target_arch = "wasm32"))]
278const MAX_EVAL_DEPTH: usize = 1024;
279
280pub fn eval_value(ctx: &EvalContext, expr: &Value, env: &Env) -> EvalResult {
281 match expr.view() {
283 ValueView::Nil
284 | ValueView::Bool(_)
285 | ValueView::Int(_)
286 | ValueView::Float(_)
287 | ValueView::String(_)
288 | ValueView::Char(_)
289 | ValueView::Keyword(_)
290 | ValueView::Thunk(_)
291 | ValueView::Bytevector(_)
292 | ValueView::NativeFn(_)
293 | ValueView::Lambda(_)
294 | ValueView::HashMap(_) => return Ok(expr.clone()),
295 ValueView::Symbol(spur) => {
296 if let Some(val) = env.get(spur) {
297 return Ok(val);
298 }
299 let name = resolve(spur);
300 let mut err = SemaError::Unbound(name.clone());
301 if let Some(hint) = sema_core::error::veteran_hint(&name) {
303 err = err.with_hint(hint);
304 } else {
305 let all_names: Vec<String> = env.all_names().iter().map(|s| resolve(*s)).collect();
307 let candidates: Vec<&str> = all_names.iter().map(|s| s.as_str()).collect();
308 if let Some(suggestion) = sema_core::error::suggest_similar(&name, &candidates) {
309 err = err.with_hint(format!("Did you mean '{suggestion}'?"));
310 }
311 }
312 let trace = ctx.capture_stack_trace();
313 return Err(err.with_stack_trace(trace));
314 }
315 _ => {}
316 }
317
318 let depth = ctx.eval_depth.get();
319 ctx.eval_depth.set(depth + 1);
320 if depth + 1 > ctx.max_eval_depth.get() {
321 ctx.max_eval_depth.set(depth + 1);
322 }
323 if depth == 0 {
324 ctx.eval_steps.set(0);
325 }
326 if depth > MAX_EVAL_DEPTH {
327 ctx.eval_depth.set(ctx.eval_depth.get().saturating_sub(1));
328 return Err(SemaError::eval(format!(
329 "maximum eval depth exceeded ({MAX_EVAL_DEPTH})"
330 )).with_hint("this usually means infinite recursion; ensure recursive calls are in tail position for TCO, or use 'do' for iteration"));
331 }
332
333 let result = eval_value_inner(ctx, expr, env);
334
335 ctx.eval_depth.set(ctx.eval_depth.get().saturating_sub(1));
336 result
337}
338
339pub fn call_value(ctx: &EvalContext, func: &Value, args: &[Value]) -> EvalResult {
346 match func.view() {
347 ValueView::NativeFn(native) => (native.func)(ctx, args),
348 ValueView::Lambda(lambda) => {
349 let trampoline = apply_lambda(ctx, &lambda, args)?;
350 run_trampoline(ctx, trampoline)
351 }
352 ValueView::Keyword(spur) => {
353 if args.len() != 1 {
354 let name = resolve(spur);
355 return Err(SemaError::arity(format!(":{name}"), "1", args.len()));
356 }
357 let key = Value::keyword_from_spur(spur);
358 match args[0].view() {
359 ValueView::Map(map) => Ok(map.get(&key).cloned().unwrap_or(Value::nil())),
360 ValueView::HashMap(map) => Ok(map.get(&key).cloned().unwrap_or(Value::nil())),
361 _ => Err(SemaError::type_error_with_value(
362 "map",
363 args[0].type_name(),
364 &args[0],
365 )),
366 }
367 }
368 ValueView::MultiMethod(mm) => call_multimethod(ctx, &mm, args),
369 _ => Err(
370 SemaError::eval(format!("not callable: {} ({})", func, func.type_name()))
371 .with_hint("expected a function, lambda, or keyword"),
372 ),
373 }
374}
375
376fn call_multimethod(ctx: &EvalContext, mm: &Rc<MultiMethod>, args: &[Value]) -> EvalResult {
378 let dispatch_val = call_value(ctx, &mm.dispatch_fn, args)?;
379 let methods = mm.methods.borrow();
380 if let Some(handler) = methods.get(&dispatch_val) {
381 let handler = handler.clone();
382 drop(methods);
383 call_value(ctx, &handler, args)
384 } else {
385 drop(methods);
386 let default = mm.default.borrow().clone();
387 if let Some(handler) = default {
388 call_value(ctx, &handler, args)
389 } else {
390 Err(SemaError::eval(format!(
391 "no method in multimethod '{}' for dispatch value: {}",
392 resolve(mm.name),
393 dispatch_val
394 ))
395 .with_hint("add a (defmethod name :default handler) to handle unmatched values"))
396 }
397 }
398}
399
400fn run_trampoline(ctx: &EvalContext, trampoline: Trampoline) -> EvalResult {
404 let limit = ctx.eval_step_limit.get();
405 let mut current = trampoline;
406 loop {
407 match current {
408 Trampoline::Value(v) => return Ok(v),
409 Trampoline::Eval(expr, env) => {
410 if limit > 0 {
411 let v = ctx.eval_steps.get() + 1;
412 ctx.eval_steps.set(v);
413 if v > limit {
414 return Err(SemaError::eval("eval step limit exceeded".to_string()));
415 }
416 }
417 match eval_step(ctx, &expr, &env) {
418 Ok(t) => current = t,
419 Err(e) => {
420 if e.stack_trace().is_none() {
421 let trace = ctx.capture_stack_trace();
422 return Err(e.with_stack_trace(trace));
423 }
424 return Err(e);
425 }
426 }
427 }
428 }
429 }
430}
431
432fn eval_value_inner(ctx: &EvalContext, expr: &Value, env: &Env) -> EvalResult {
433 let entry_depth = ctx.call_stack_depth();
434 let guard = CallStackGuard { ctx, entry_depth };
435 let limit = ctx.eval_step_limit.get();
436
437 if limit > 0 {
439 let v = ctx.eval_steps.get() + 1;
440 ctx.eval_steps.set(v);
441 if v > limit {
442 return Err(SemaError::eval("eval step limit exceeded".to_string()));
443 }
444 }
445
446 match eval_step(ctx, expr, env) {
447 Ok(Trampoline::Value(v)) => {
448 drop(guard);
449 Ok(v)
450 }
451 Ok(Trampoline::Eval(next_expr, next_env)) => {
452 let mut current_expr = next_expr;
454 let mut current_env = next_env;
455
456 {
458 let mut stack = ctx.call_stack.borrow_mut();
459 if stack.len() > entry_depth + 1 {
460 let top = stack.last().cloned();
461 stack.truncate(entry_depth);
462 if let Some(frame) = top {
463 stack.push(frame);
464 }
465 }
466 }
467
468 loop {
469 if limit > 0 {
470 let v = ctx.eval_steps.get() + 1;
471 ctx.eval_steps.set(v);
472 if v > limit {
473 return Err(SemaError::eval("eval step limit exceeded".to_string()));
474 }
475 }
476
477 match eval_step(ctx, ¤t_expr, ¤t_env) {
478 Ok(Trampoline::Value(v)) => {
479 drop(guard);
480 return Ok(v);
481 }
482 Ok(Trampoline::Eval(next_expr, next_env)) => {
483 {
484 let mut stack = ctx.call_stack.borrow_mut();
485 if stack.len() > entry_depth + 1 {
486 let top = stack.last().cloned();
487 stack.truncate(entry_depth);
488 if let Some(frame) = top {
489 stack.push(frame);
490 }
491 }
492 }
493 current_expr = next_expr;
494 current_env = next_env;
495 }
496 Err(e) => {
497 if e.stack_trace().is_none() {
498 let trace = ctx.capture_stack_trace();
499 drop(guard);
500 return Err(e.with_stack_trace(trace));
501 }
502 drop(guard);
503 return Err(e);
504 }
505 }
506 }
507 }
508 Err(e) => {
509 if e.stack_trace().is_none() {
510 let trace = ctx.capture_stack_trace();
511 drop(guard);
512 return Err(e.with_stack_trace(trace));
513 }
514 drop(guard);
515 Err(e)
516 }
517 }
518}
519
520fn eval_step(ctx: &EvalContext, expr: &Value, env: &Env) -> Result<Trampoline, SemaError> {
521 match expr.view() {
522 ValueView::Nil
524 | ValueView::Bool(_)
525 | ValueView::Int(_)
526 | ValueView::Float(_)
527 | ValueView::String(_)
528 | ValueView::Char(_)
529 | ValueView::Thunk(_)
530 | ValueView::Bytevector(_) => Ok(Trampoline::Value(expr.clone())),
531 ValueView::Keyword(_) => Ok(Trampoline::Value(expr.clone())),
532 ValueView::Vector(items) => {
533 let mut result = Vec::with_capacity(items.len());
534 for item in items.iter() {
535 result.push(eval_value(ctx, item, env)?);
536 }
537 Ok(Trampoline::Value(Value::vector(result)))
538 }
539 ValueView::Map(map) => {
540 let mut result = std::collections::BTreeMap::new();
541 for (k, v) in map.iter() {
542 let ek = eval_value(ctx, k, env)?;
543 let ev = eval_value(ctx, v, env)?;
544 result.insert(ek, ev);
545 }
546 Ok(Trampoline::Value(Value::map(result)))
547 }
548 ValueView::HashMap(_) => Ok(Trampoline::Value(expr.clone())),
549
550 ValueView::Symbol(spur) => env.get(spur).map(Trampoline::Value).ok_or_else(|| {
552 let name = resolve(spur);
553 let mut err = SemaError::Unbound(name.clone());
554 if let Some(hint) = sema_core::error::veteran_hint(&name) {
555 err = err.with_hint(hint);
556 } else {
557 let all_names: Vec<String> = env.all_names().iter().map(|s| resolve(*s)).collect();
558 let candidates: Vec<&str> = all_names.iter().map(|s| s.as_str()).collect();
559 if let Some(suggestion) = sema_core::error::suggest_similar(&name, &candidates) {
560 err = err.with_hint(format!("Did you mean '{suggestion}'?"));
561 }
562 }
563 err
564 }),
565
566 ValueView::List(items) => {
568 if items.is_empty() {
569 return Ok(Trampoline::Value(Value::nil()));
570 }
571
572 let head = &items[0];
573 let args = &items[1..];
574
575 if let Some(spur) = head.as_symbol_spur() {
578 if let Some(result) = special_forms::try_eval_special(spur, args, env, ctx) {
579 return result;
580 }
581 }
582
583 let func = eval_value(ctx, head, env)?;
585
586 let call_span = span_of_expr(ctx, expr);
588
589 match func.view() {
590 ValueView::NativeFn(native) => {
591 let mut eval_args = Vec::with_capacity(args.len());
593 for arg in args {
594 eval_args.push(eval_value(ctx, arg, env)?);
595 }
596 let frame = CallFrame {
598 name: native.name.to_string(),
599 file: ctx.current_file_path(),
600 span: call_span,
601 };
602 ctx.push_call_frame(frame);
603 match (native.func)(ctx, &eval_args) {
604 Ok(v) => {
605 ctx.truncate_call_stack(ctx.call_stack_depth().saturating_sub(1));
607 Ok(Trampoline::Value(v))
608 }
609 Err(e) => Err(annotate_arity_error(e, expr)),
611 }
612 }
613 ValueView::Lambda(lambda) => {
614 let mut eval_args = Vec::with_capacity(args.len());
616 for arg in args {
617 eval_args.push(eval_value(ctx, arg, env)?);
618 }
619 let frame = CallFrame {
621 name: lambda
622 .name
623 .map(resolve)
624 .unwrap_or_else(|| "<lambda>".to_string()),
625 file: ctx.current_file_path(),
626 span: call_span,
627 };
628 ctx.push_call_frame(frame);
629 apply_lambda(ctx, &lambda, &eval_args)
630 .map_err(|e| annotate_arity_error(e, expr))
631 }
632 ValueView::Macro(mac) => {
633 let expanded = apply_macro(ctx, &mac, args, env)?;
635 Ok(Trampoline::Eval(expanded, env.clone()))
637 }
638 ValueView::Keyword(spur) => {
639 if args.len() != 1 {
641 let name = resolve(spur);
642 return Err(SemaError::arity(format!(":{name}"), "1", args.len()));
643 }
644 let map_val = eval_value(ctx, &args[0], env)?;
645 let key = Value::keyword_from_spur(spur);
646 match map_val.view() {
647 ValueView::Map(map) => Ok(Trampoline::Value(
648 map.get(&key).cloned().unwrap_or(Value::nil()),
649 )),
650 ValueView::HashMap(map) => Ok(Trampoline::Value(
651 map.get(&key).cloned().unwrap_or(Value::nil()),
652 )),
653 _ => Err(SemaError::type_error_with_value(
654 "map",
655 map_val.type_name(),
656 &map_val,
657 )),
658 }
659 }
660 ValueView::MultiMethod(mm) => {
661 let mut eval_args = Vec::with_capacity(args.len());
662 for arg in args {
663 eval_args.push(eval_value(ctx, arg, env)?);
664 }
665 let result = call_multimethod(ctx, &mm, &eval_args)?;
666 Ok(Trampoline::Value(result))
667 }
668 _ => Err(
669 SemaError::eval(format!("not callable: {} ({})", func, func.type_name()))
670 .with_hint("the first element of a list must be a function or macro"),
671 ),
672 }
673 }
674
675 _other => Ok(Trampoline::Value(expr.clone())),
676 }
677}
678
679fn annotate_arity_error(err: SemaError, expr: &Value) -> SemaError {
681 if matches!(err.inner(), SemaError::Arity { .. }) && err.note().is_none() {
682 let form_str = format!("{}", expr);
683 let truncated = if form_str.len() > 80 {
684 format!("{}…", &form_str[..79])
685 } else {
686 form_str
687 };
688 err.with_note(format!("in: {truncated}"))
689 } else {
690 err
691 }
692}
693
694fn apply_lambda(
696 ctx: &EvalContext,
697 lambda: &Rc<Lambda>,
698 args: &[Value],
699) -> Result<Trampoline, SemaError> {
700 let new_env = Env::with_parent(Rc::new(lambda.env.clone()));
701
702 if let Some(rest) = lambda.rest_param {
704 if args.len() < lambda.params.len() {
705 return Err(SemaError::arity(
706 lambda
707 .name
708 .map(resolve)
709 .unwrap_or_else(|| "lambda".to_string()),
710 format!("{}+", lambda.params.len()),
711 args.len(),
712 ));
713 }
714 for (param, arg) in lambda.params.iter().zip(args.iter()) {
715 new_env.set(*param, arg.clone());
716 }
717 let rest_args = args[lambda.params.len()..].to_vec();
718 new_env.set(rest, Value::list(rest_args));
719 } else {
720 if args.len() != lambda.params.len() {
721 return Err(SemaError::arity(
722 lambda
723 .name
724 .map(resolve)
725 .unwrap_or_else(|| "lambda".to_string()),
726 lambda.params.len().to_string(),
727 args.len(),
728 ));
729 }
730 for (param, arg) in lambda.params.iter().zip(args.iter()) {
731 new_env.set(*param, arg.clone());
732 }
733 }
734
735 if let Some(name) = lambda.name {
737 new_env.set(name, Value::lambda_from_rc(Rc::clone(lambda)));
738 }
739
740 if lambda.body.is_empty() {
742 return Ok(Trampoline::Value(Value::nil()));
743 }
744 for expr in &lambda.body[..lambda.body.len() - 1] {
745 eval_value(ctx, expr, &new_env)?;
746 }
747 Ok(Trampoline::Eval(
748 lambda.body.last().unwrap().clone(),
749 new_env,
750 ))
751}
752
753pub fn apply_macro(
755 ctx: &EvalContext,
756 mac: &sema_core::Macro,
757 args: &[Value],
758 caller_env: &Env,
759) -> Result<Value, SemaError> {
760 let env = Env::with_parent(Rc::new(caller_env.clone()));
761
762 if let Some(rest) = mac.rest_param {
764 if args.len() < mac.params.len() {
765 return Err(SemaError::arity(
766 resolve(mac.name),
767 format!("{}+", mac.params.len()),
768 args.len(),
769 ));
770 }
771 for (param, arg) in mac.params.iter().zip(args.iter()) {
772 env.set(*param, arg.clone());
773 }
774 let rest_args = args[mac.params.len()..].to_vec();
775 env.set(rest, Value::list(rest_args));
776 } else {
777 if args.len() != mac.params.len() {
778 return Err(SemaError::arity(
779 resolve(mac.name),
780 mac.params.len().to_string(),
781 args.len(),
782 ));
783 }
784 for (param, arg) in mac.params.iter().zip(args.iter()) {
785 env.set(*param, arg.clone());
786 }
787 }
788
789 let mut result = Value::nil();
791 for expr in &mac.body {
792 result = eval_value(ctx, expr, &env)?;
793 }
794 Ok(result)
795}
796
797fn load_prelude(ctx: &EvalContext, env: &Rc<Env>) {
801 let exprs = sema_reader::read_many(crate::prelude::PRELUDE).expect("prelude parse error");
802 for expr in &exprs {
803 eval_value(ctx, expr, env).expect("prelude eval error");
804 }
805}
806
807fn register_vm_delegates(env: &Rc<Env>) {
808 let eval_env = env.clone();
810 env.set(
811 intern("__vm-eval"),
812 Value::native_fn(NativeFn::with_ctx("__vm-eval", move |ctx, args| {
813 if args.len() != 1 {
814 return Err(SemaError::arity("eval", "1", args.len()));
815 }
816 sema_core::eval_callback(ctx, &args[0], &eval_env)
817 })),
818 );
819
820 let load_env = env.clone();
825 env.set(
826 intern("__vm-load"),
827 Value::native_fn(NativeFn::with_ctx("__vm-load", move |ctx, args| {
828 if args.len() != 1 {
829 return Err(SemaError::arity("load", "1", args.len()));
830 }
831 let load_expr = Value::list(vec![Value::symbol("load"), args[0].clone()]);
832 sema_core::eval_callback(ctx, &load_expr, &load_env)
833 })),
834 );
835
836 let import_env = env.clone();
838 env.set(
839 intern("__vm-import"),
840 Value::native_fn(NativeFn::with_ctx("__vm-import", move |ctx, args| {
841 if args.len() != 2 {
842 return Err(SemaError::arity("import", "2", args.len()));
843 }
844 ctx.sandbox.check(sema_core::Caps::FS_READ, "import")?;
845 let mut form = vec![Value::symbol("import"), args[0].clone()];
846 if let Some(items) = args[1].as_list() {
847 if !items.is_empty() {
848 for item in items.iter() {
849 form.push(item.clone());
850 }
851 }
852 }
853 let import_expr = Value::list(form);
854 sema_core::eval_callback(ctx, &import_expr, &import_env)
855 })),
856 );
857
858 let macro_env = env.clone();
860 env.set(
861 intern("__vm-defmacro"),
862 Value::native_fn(NativeFn::simple("__vm-defmacro", move |args| {
863 if args.len() != 4 {
864 return Err(SemaError::arity("defmacro", "4", args.len()));
865 }
866 let name = match args[0].as_symbol_spur() {
867 Some(s) => s,
868 None => return Err(SemaError::type_error("symbol", args[0].type_name())),
869 };
870 let params = match args[1].as_list() {
871 Some(items) => items
872 .iter()
873 .map(|v| match v.as_symbol_spur() {
874 Some(s) => Ok(s),
875 None => Err(SemaError::type_error("symbol", v.type_name())),
876 })
877 .collect::<Result<Vec<_>, _>>()?,
878 None => return Err(SemaError::type_error("list", args[1].type_name())),
879 };
880 let rest_param = if let Some(s) = args[2].as_symbol_spur() {
881 Some(s)
882 } else if args[2].is_nil() {
883 None
884 } else {
885 return Err(SemaError::type_error("symbol or nil", args[2].type_name()));
886 };
887 let body = vec![args[3].clone()];
888 macro_env.set(
889 name,
890 Value::macro_val(Macro {
891 params,
892 rest_param,
893 body,
894 name,
895 }),
896 );
897 Ok(Value::nil())
898 })),
899 );
900
901 let dmf_env = env.clone();
903 env.set(
904 intern("__vm-defmacro-form"),
905 Value::native_fn(NativeFn::with_ctx(
906 "__vm-defmacro-form",
907 move |ctx, args| {
908 if args.len() != 1 {
909 return Err(SemaError::arity("defmacro-form", "1", args.len()));
910 }
911 sema_core::eval_callback(ctx, &args[0], &dmf_env)
912 },
913 )),
914 );
915
916 let drt_env = env.clone();
918 env.set(
919 intern("__vm-define-record-type"),
920 Value::native_fn(NativeFn::with_ctx(
921 "__vm-define-record-type",
922 move |ctx, args| {
923 if args.len() != 5 {
924 return Err(SemaError::arity("define-record-type", "5", args.len()));
925 }
926 let mut ctor_form = vec![args[1].clone()];
927 if let Some(fields) = args[3].as_list() {
928 ctor_form.extend(fields.iter().cloned());
929 }
930 let mut form = vec![
931 Value::symbol("define-record-type"),
932 args[0].clone(),
933 Value::list(ctor_form),
934 args[2].clone(),
935 ];
936 if let Some(specs) = args[4].as_list() {
937 for spec in specs.iter() {
938 form.push(spec.clone());
939 }
940 }
941 sema_core::eval_callback(ctx, &Value::list(form), &drt_env)
942 },
943 )),
944 );
945
946 env.set(
948 intern("__vm-delay"),
949 Value::native_fn(NativeFn::simple("__vm-delay", |args| {
950 if args.len() != 1 {
951 return Err(SemaError::arity("delay", "1", args.len()));
952 }
953 Ok(Value::thunk(Thunk {
955 body: args[0].clone(),
956 forced: RefCell::new(None),
957 }))
958 })),
959 );
960
961 let force_env = env.clone();
963 env.set(
964 intern("__vm-force"),
965 Value::native_fn(NativeFn::with_ctx("__vm-force", move |ctx, args| {
966 if args.len() != 1 {
967 return Err(SemaError::arity("force", "1", args.len()));
968 }
969 if let Some(thunk) = args[0].as_thunk_rc() {
970 if let Some(val) = thunk.forced.borrow().as_ref() {
971 return Ok(val.clone());
972 }
973 let val = if thunk.body.as_native_fn_rc().is_some()
974 || thunk.body.as_lambda_rc().is_some()
975 {
976 sema_core::call_callback(ctx, &thunk.body, &[])?
977 } else {
978 sema_core::eval_callback(ctx, &thunk.body, &force_env)?
979 };
980 *thunk.forced.borrow_mut() = Some(val.clone());
981 Ok(val)
982 } else {
983 Ok(args[0].clone())
984 }
985 })),
986 );
987
988 let me_env = env.clone();
990 env.set(
991 intern("__vm-macroexpand"),
992 Value::native_fn(NativeFn::with_ctx("__vm-macroexpand", move |ctx, args| {
993 if args.len() != 1 {
994 return Err(SemaError::arity("macroexpand", "1", args.len()));
995 }
996 if let Some(items) = args[0].as_list() {
997 if !items.is_empty() {
998 if let Some(spur) = items[0].as_symbol_spur() {
999 if let Some(mac_val) = me_env.get(spur) {
1000 if let Some(mac) = mac_val.as_macro_rc() {
1001 return apply_macro(ctx, &mac, &items[1..], &me_env);
1002 }
1003 }
1004 }
1005 }
1006 }
1007 Ok(args[0].clone())
1008 })),
1009 );
1010
1011 env.set(
1013 intern("__vm-prompt"),
1014 Value::native_fn(NativeFn::simple("__vm-prompt", |args| {
1015 use sema_core::{Message, Prompt, Role};
1016 if args.len() != 1 {
1017 return Err(SemaError::arity("__vm-prompt", "1", args.len()));
1018 }
1019 let entries = args[0]
1020 .as_list()
1021 .ok_or_else(|| SemaError::type_error("list", args[0].type_name()))?;
1022 let mut messages = Vec::new();
1023 for entry in entries {
1024 if let Some(msg) = entry.as_message_rc() {
1025 messages.push((*msg).clone());
1026 } else if let Some(pair) = entry.as_list() {
1027 if pair.len() == 2 {
1028 let role_str = pair[0]
1029 .as_str()
1030 .ok_or_else(|| SemaError::eval("prompt: expected role string"))?;
1031 let role = match role_str {
1032 "system" => Role::System,
1033 "user" => Role::User,
1034 "assistant" => Role::Assistant,
1035 "tool" => Role::Tool,
1036 other => {
1037 return Err(SemaError::eval(format!(
1038 "prompt: unknown role '{other}'"
1039 )))
1040 }
1041 };
1042 let parts = pair[1]
1043 .as_list()
1044 .ok_or_else(|| SemaError::type_error("list", pair[1].type_name()))?;
1045 let mut content = String::new();
1046 for part in parts {
1047 if let Some(s) = part.as_str() {
1048 content.push_str(s);
1049 } else {
1050 content.push_str(&part.to_string());
1051 }
1052 }
1053 messages.push(Message {
1054 role,
1055 content,
1056 images: Vec::new(),
1057 });
1058 } else {
1059 return Err(SemaError::eval(
1060 "prompt: expected (role parts) pair or message value",
1061 ));
1062 }
1063 } else {
1064 return Err(SemaError::eval(
1065 "prompt: expected (role parts) pair or message value",
1066 ));
1067 }
1068 }
1069 Ok(Value::prompt(Prompt { messages }))
1070 })),
1071 );
1072
1073 env.set(
1075 intern("__vm-message"),
1076 Value::native_fn(NativeFn::simple("__vm-message", |args| {
1077 use sema_core::{Message, Role};
1078 if args.len() != 2 {
1079 return Err(SemaError::arity("__vm-message", "2", args.len()));
1080 }
1081 let role = if let Some(spur) = args[0].as_keyword_spur() {
1082 let s = resolve(spur);
1083 match s.as_str() {
1084 "system" => Role::System,
1085 "user" => Role::User,
1086 "assistant" => Role::Assistant,
1087 "tool" => Role::Tool,
1088 other => {
1089 return Err(SemaError::eval(format!("message: unknown role '{other}'")))
1090 }
1091 }
1092 } else {
1093 return Err(SemaError::type_error("keyword", args[0].type_name()));
1094 };
1095 let parts = args[1]
1096 .as_list()
1097 .ok_or_else(|| SemaError::type_error("list", args[1].type_name()))?;
1098 let mut content = String::new();
1099 for part in parts {
1100 if let Some(s) = part.as_str() {
1101 content.push_str(s);
1102 } else {
1103 content.push_str(&part.to_string());
1104 }
1105 }
1106 Ok(Value::message(Message {
1107 role,
1108 content,
1109 images: Vec::new(),
1110 }))
1111 })),
1112 );
1113
1114 let tool_env = env.clone();
1116 env.set(
1117 intern("__vm-deftool"),
1118 Value::native_fn(NativeFn::with_ctx("__vm-deftool", move |ctx, args| {
1119 if args.len() != 4 {
1120 return Err(SemaError::arity("deftool", "4", args.len()));
1121 }
1122 let form = Value::list(vec![
1123 Value::symbol("deftool"),
1124 args[0].clone(),
1125 args[1].clone(),
1126 args[2].clone(),
1127 args[3].clone(),
1128 ]);
1129 sema_core::eval_callback(ctx, &form, &tool_env)
1130 })),
1131 );
1132
1133 let agent_env = env.clone();
1135 env.set(
1136 intern("__vm-defagent"),
1137 Value::native_fn(NativeFn::with_ctx("__vm-defagent", move |ctx, args| {
1138 if args.len() != 2 {
1139 return Err(SemaError::arity("defagent", "2", args.len()));
1140 }
1141 let form = Value::list(vec![
1142 Value::symbol("defagent"),
1143 args[0].clone(),
1144 args[1].clone(),
1145 ]);
1146 sema_core::eval_callback(ctx, &form, &agent_env)
1147 })),
1148 );
1149
1150 env.set(
1153 intern("__vm-destructure"),
1154 Value::native_fn(NativeFn::simple("__vm-destructure", |args| {
1155 if args.len() != 2 {
1156 return Err(SemaError::arity("__vm-destructure", "2", args.len()));
1157 }
1158 let bindings = crate::destructure::destructure(&args[0], &args[1])?;
1159 let mut map = std::collections::BTreeMap::new();
1160 for (spur, val) in bindings {
1161 map.insert(Value::symbol_from_spur(spur), val);
1162 }
1163 Ok(Value::map(map))
1164 })),
1165 );
1166
1167 env.set(
1170 intern("__vm-try-match"),
1171 Value::native_fn(NativeFn::simple("__vm-try-match", |args| {
1172 if args.len() != 2 {
1173 return Err(SemaError::arity("__vm-try-match", "2", args.len()));
1174 }
1175 match crate::destructure::try_match(&args[0], &args[1])? {
1176 Some(bindings) => {
1177 let mut map = std::collections::BTreeMap::new();
1178 for (spur, val) in bindings {
1179 map.insert(Value::symbol_from_spur(spur), val);
1180 }
1181 Ok(Value::map(map))
1182 }
1183 None => Ok(Value::nil()),
1184 }
1185 })),
1186 );
1187
1188 env.set(
1190 intern("__vm-make-multi"),
1191 Value::native_fn(NativeFn::simple("__vm-make-multi", |args| {
1192 if args.len() != 2 {
1193 return Err(SemaError::arity("__vm-make-multi", "2", args.len()));
1194 }
1195 let name_spur = args[0]
1196 .as_symbol_spur()
1197 .ok_or_else(|| SemaError::eval("__vm-make-multi: expected symbol"))?;
1198 Ok(Value::multimethod(MultiMethod {
1199 name: name_spur,
1200 dispatch_fn: args[1].clone(),
1201 methods: RefCell::new(std::collections::BTreeMap::new()),
1202 default: RefCell::new(None),
1203 }))
1204 })),
1205 );
1206
1207 env.set(
1209 intern("__vm-defmethod"),
1210 Value::native_fn(NativeFn::simple("__vm-defmethod", |args| {
1211 if args.len() != 3 {
1212 return Err(SemaError::arity("__vm-defmethod", "3", args.len()));
1213 }
1214 let mm = args[0]
1215 .as_multimethod_rc()
1216 .ok_or_else(|| SemaError::eval("defmethod: first argument is not a multimethod"))?;
1217 let dispatch_val = &args[1];
1218 let handler = &args[2];
1219 if let Some(kw) = dispatch_val.as_keyword_spur() {
1220 if resolve(kw) == "default" {
1221 *mm.default.borrow_mut() = Some(handler.clone());
1222 return Ok(Value::nil());
1223 }
1224 }
1225 mm.methods
1226 .borrow_mut()
1227 .insert(dispatch_val.clone(), handler.clone());
1228 Ok(Value::nil())
1229 })),
1230 );
1231}