1use std::cell::RefCell;
2use std::rc::Rc;
3
4use sema_core::{
5 intern, resolve, CallFrame, Env, EvalContext, Lambda, Macro, NativeFn, SemaError, Span, Thunk,
6 Value, ValueView,
7};
8
9use crate::special_forms;
10
11pub enum Trampoline {
13 Value(Value),
14 Eval(Value, Env),
15}
16
17pub type EvalResult = Result<Value, SemaError>;
18
19pub fn create_module_env(env: &Env) -> Env {
21 let mut current = env.clone();
23 loop {
24 let parent = current.parent.clone();
25 match parent {
26 Some(p) => current = (*p).clone(),
27 None => break,
28 }
29 }
30 Env::with_parent(Rc::new(current))
31}
32
33fn span_of_expr(ctx: &EvalContext, expr: &Value) -> Option<Span> {
35 if let Some(items) = expr.as_list_rc() {
36 let ptr = Rc::as_ptr(&items) as usize;
37 ctx.lookup_span(ptr)
38 } else {
39 None
40 }
41}
42
43struct CallStackGuard<'a> {
45 ctx: &'a EvalContext,
46 entry_depth: usize,
47}
48
49impl Drop for CallStackGuard<'_> {
50 fn drop(&mut self) {
51 self.ctx.truncate_call_stack(self.entry_depth);
52 }
53}
54
55pub struct Interpreter {
57 pub global_env: Rc<Env>,
58 pub ctx: EvalContext,
59}
60
61impl Default for Interpreter {
62 fn default() -> Self {
63 Self::new()
64 }
65}
66
67impl Interpreter {
68 pub fn new() -> Self {
69 let env = Env::new();
70 let ctx = EvalContext::new();
71 sema_core::set_eval_callback(&ctx, eval_value);
73 sema_core::set_call_callback(&ctx, call_value);
74 sema_stdlib::register_stdlib(&env, &sema_core::Sandbox::allow_all());
76 #[cfg(not(target_arch = "wasm32"))]
78 {
79 sema_llm::builtins::reset_runtime_state();
80 sema_llm::builtins::register_llm_builtins(&env, &sema_core::Sandbox::allow_all());
81 sema_llm::builtins::set_eval_callback(eval_value);
82 }
83 let global_env = Rc::new(env);
84 register_vm_delegates(&global_env);
85 Interpreter { global_env, ctx }
86 }
87
88 pub fn new_with_sandbox(sandbox: &sema_core::Sandbox) -> Self {
89 let env = Env::new();
90 let ctx = EvalContext::new_with_sandbox(sandbox.clone());
91 sema_core::set_eval_callback(&ctx, eval_value);
92 sema_core::set_call_callback(&ctx, call_value);
93 sema_stdlib::register_stdlib(&env, sandbox);
94 #[cfg(not(target_arch = "wasm32"))]
95 {
96 sema_llm::builtins::reset_runtime_state();
97 sema_llm::builtins::register_llm_builtins(&env, sandbox);
98 sema_llm::builtins::set_eval_callback(eval_value);
99 }
100 let global_env = Rc::new(env);
101 register_vm_delegates(&global_env);
102 Interpreter { global_env, ctx }
103 }
104
105 pub fn eval(&self, expr: &Value) -> EvalResult {
106 eval_value(&self.ctx, expr, &Env::with_parent(self.global_env.clone()))
107 }
108
109 pub fn eval_str(&self, input: &str) -> EvalResult {
110 eval_string(&self.ctx, input, &Env::with_parent(self.global_env.clone()))
111 }
112
113 pub fn eval_in_global(&self, expr: &Value) -> EvalResult {
115 eval_value(&self.ctx, expr, &self.global_env)
116 }
117
118 pub fn eval_str_in_global(&self, input: &str) -> EvalResult {
120 eval_string(&self.ctx, input, &self.global_env)
121 }
122
123 pub fn eval_str_compiled(&self, input: &str) -> EvalResult {
125 let (exprs, spans) = sema_reader::read_many_with_spans(input)?;
126 self.ctx.merge_span_table(spans);
127 if exprs.is_empty() {
128 return Ok(Value::nil());
129 }
130
131 let mut expanded = Vec::new();
132 for expr in &exprs {
133 let exp = self.expand_for_vm(expr)?;
134 expanded.push(exp);
135 }
136
137 let (closure, functions) = sema_vm::compile_program(&expanded)?;
138 let mut vm = sema_vm::VM::new(self.global_env.clone(), functions);
139 vm.execute(closure, &self.ctx)
140 }
141
142 pub fn eval_compiled(&self, expr: &Value) -> EvalResult {
144 let expanded = self.expand_for_vm(expr)?;
145 let (closure, functions) = sema_vm::compile_program(std::slice::from_ref(&expanded))?;
146 let mut vm = sema_vm::VM::new(self.global_env.clone(), functions);
147 vm.execute(closure, &self.ctx)
148 }
149
150 pub fn compile_to_bytecode(&self, input: &str) -> Result<sema_vm::CompileResult, SemaError> {
153 let (exprs, spans) = sema_reader::read_many_with_spans(input)?;
154 self.ctx.merge_span_table(spans);
155
156 let mut expanded = Vec::new();
157 for expr in &exprs {
158 let exp = self.expand_for_vm(expr)?;
159 if !exp.is_nil() {
160 expanded.push(exp);
161 }
162 }
163
164 if expanded.is_empty() {
165 expanded.push(Value::nil());
166 }
167
168 let (closure, functions) = sema_vm::compile_program(&expanded)?;
169 Ok(sema_vm::CompileResult {
170 chunk: closure.func.chunk.clone(),
171 functions: functions.iter().map(|f| (**f).clone()).collect(),
172 })
173 }
174
175 fn expand_for_vm(&self, expr: &Value) -> EvalResult {
179 if let Some(items) = expr.as_list() {
180 if let Some(s) = items.first().and_then(|v| v.as_symbol_spur()) {
181 let name = resolve(s);
182 if name == "defmacro" {
183 eval_value(&self.ctx, expr, &self.global_env)?;
184 return Ok(Value::nil());
185 }
186 if name == "begin" {
187 let mut new_items = vec![Value::symbol_from_spur(s)];
188 for item in &items[1..] {
189 new_items.push(self.expand_for_vm(item)?);
190 }
191 return Ok(Value::list(new_items));
192 }
193 }
194 }
195 self.expand_macros(expr)
196 }
197
198 fn expand_macros(&self, expr: &Value) -> EvalResult {
200 if let Some(items) = expr.as_list() {
201 if !items.is_empty() {
202 if let Some(s) = items.first().and_then(|v| v.as_symbol_spur()) {
203 let name = resolve(s);
204 if name == "quote" {
205 return Ok(expr.clone());
206 }
207 if let Some(mac_val) = self.global_env.get(s) {
208 if let Some(mac) = mac_val.as_macro_rc() {
209 let expanded =
210 apply_macro(&self.ctx, &mac, &items[1..], &self.global_env)?;
211 return self.expand_macros(&expanded);
212 }
213 }
214 }
215 let expanded: Result<Vec<Value>, SemaError> =
216 items.iter().map(|v| self.expand_macros(v)).collect();
217 return Ok(Value::list(expanded?));
218 }
219 }
220 Ok(expr.clone())
221 }
222}
223
224pub fn eval_string(ctx: &EvalContext, input: &str, env: &Env) -> EvalResult {
226 let (exprs, spans) = sema_reader::read_many_with_spans(input)?;
227 ctx.merge_span_table(spans);
228 ctx.max_eval_depth.set(0);
229 let mut result = Value::nil();
230 for expr in &exprs {
231 result = eval_value(ctx, expr, env)?;
232 }
233 eprintln!(
234 "[debug] max eval_depth reached: {}",
235 ctx.max_eval_depth.get()
236 );
237 Ok(result)
238}
239
240pub fn eval(ctx: &EvalContext, expr: &Value, env: &Env) -> EvalResult {
242 eval_value(ctx, expr, env)
243}
244
245#[cfg(target_arch = "wasm32")]
250const MAX_EVAL_DEPTH: usize = 256;
251#[cfg(not(target_arch = "wasm32"))]
252const MAX_EVAL_DEPTH: usize = 1024;
253
254pub fn eval_value(ctx: &EvalContext, expr: &Value, env: &Env) -> EvalResult {
255 match expr.view() {
257 ValueView::Nil
258 | ValueView::Bool(_)
259 | ValueView::Int(_)
260 | ValueView::Float(_)
261 | ValueView::String(_)
262 | ValueView::Char(_)
263 | ValueView::Keyword(_)
264 | ValueView::Thunk(_)
265 | ValueView::Bytevector(_)
266 | ValueView::NativeFn(_)
267 | ValueView::Lambda(_)
268 | ValueView::HashMap(_) => return Ok(expr.clone()),
269 ValueView::Symbol(spur) => {
270 if let Some(val) = env.get(spur) {
271 return Ok(val);
272 }
273 let err = SemaError::Unbound(resolve(spur));
274 let trace = ctx.capture_stack_trace();
275 return Err(err.with_stack_trace(trace));
276 }
277 _ => {}
278 }
279
280 let depth = ctx.eval_depth.get();
281 ctx.eval_depth.set(depth + 1);
282 if depth + 1 > ctx.max_eval_depth.get() {
283 ctx.max_eval_depth.set(depth + 1);
284 }
285 if depth == 0 {
286 ctx.eval_steps.set(0);
287 }
288 if depth > MAX_EVAL_DEPTH {
289 ctx.eval_depth.set(ctx.eval_depth.get().saturating_sub(1));
290 return Err(SemaError::eval(format!(
291 "maximum eval depth exceeded ({MAX_EVAL_DEPTH})"
292 )));
293 }
294
295 let result = eval_value_inner(ctx, expr, env);
296
297 ctx.eval_depth.set(ctx.eval_depth.get().saturating_sub(1));
298 result
299}
300
301pub fn call_value(ctx: &EvalContext, func: &Value, args: &[Value]) -> EvalResult {
308 match func.view() {
309 ValueView::NativeFn(native) => (native.func)(ctx, args),
310 ValueView::Lambda(lambda) => {
311 let trampoline = apply_lambda(ctx, &lambda, args)?;
312 run_trampoline(ctx, trampoline)
313 }
314 ValueView::Keyword(spur) => {
315 if args.len() != 1 {
316 let name = resolve(spur);
317 return Err(SemaError::arity(format!(":{name}"), "1", args.len()));
318 }
319 let key = Value::keyword_from_spur(spur);
320 match args[0].view() {
321 ValueView::Map(map) => Ok(map.get(&key).cloned().unwrap_or(Value::nil())),
322 ValueView::HashMap(map) => Ok(map.get(&key).cloned().unwrap_or(Value::nil())),
323 _ => Err(SemaError::type_error("map", args[0].type_name())),
324 }
325 }
326 _ => Err(
327 SemaError::eval(format!("not callable: {} ({})", func, func.type_name()))
328 .with_hint("expected a function, lambda, or keyword"),
329 ),
330 }
331}
332
333fn run_trampoline(ctx: &EvalContext, trampoline: Trampoline) -> EvalResult {
337 let limit = ctx.eval_step_limit.get();
338 let mut current = trampoline;
339 loop {
340 match current {
341 Trampoline::Value(v) => return Ok(v),
342 Trampoline::Eval(expr, env) => {
343 if limit > 0 {
344 let v = ctx.eval_steps.get() + 1;
345 ctx.eval_steps.set(v);
346 if v > limit {
347 return Err(SemaError::eval("eval step limit exceeded".to_string()));
348 }
349 }
350 match eval_step(ctx, &expr, &env) {
351 Ok(t) => current = t,
352 Err(e) => {
353 if e.stack_trace().is_none() {
354 let trace = ctx.capture_stack_trace();
355 return Err(e.with_stack_trace(trace));
356 }
357 return Err(e);
358 }
359 }
360 }
361 }
362 }
363}
364
365fn eval_value_inner(ctx: &EvalContext, expr: &Value, env: &Env) -> EvalResult {
366 let entry_depth = ctx.call_stack_depth();
367 let guard = CallStackGuard { ctx, entry_depth };
368 let limit = ctx.eval_step_limit.get();
369
370 if limit > 0 {
372 let v = ctx.eval_steps.get() + 1;
373 ctx.eval_steps.set(v);
374 if v > limit {
375 return Err(SemaError::eval("eval step limit exceeded".to_string()));
376 }
377 }
378
379 match eval_step(ctx, expr, env) {
380 Ok(Trampoline::Value(v)) => {
381 drop(guard);
382 Ok(v)
383 }
384 Ok(Trampoline::Eval(next_expr, next_env)) => {
385 let mut current_expr = next_expr;
387 let mut current_env = next_env;
388
389 {
391 let mut stack = ctx.call_stack.borrow_mut();
392 if stack.len() > entry_depth + 1 {
393 let top = stack.last().cloned();
394 stack.truncate(entry_depth);
395 if let Some(frame) = top {
396 stack.push(frame);
397 }
398 }
399 }
400
401 loop {
402 if limit > 0 {
403 let v = ctx.eval_steps.get() + 1;
404 ctx.eval_steps.set(v);
405 if v > limit {
406 return Err(SemaError::eval("eval step limit exceeded".to_string()));
407 }
408 }
409
410 match eval_step(ctx, ¤t_expr, ¤t_env) {
411 Ok(Trampoline::Value(v)) => {
412 drop(guard);
413 return Ok(v);
414 }
415 Ok(Trampoline::Eval(next_expr, next_env)) => {
416 {
417 let mut stack = ctx.call_stack.borrow_mut();
418 if stack.len() > entry_depth + 1 {
419 let top = stack.last().cloned();
420 stack.truncate(entry_depth);
421 if let Some(frame) = top {
422 stack.push(frame);
423 }
424 }
425 }
426 current_expr = next_expr;
427 current_env = next_env;
428 }
429 Err(e) => {
430 if e.stack_trace().is_none() {
431 let trace = ctx.capture_stack_trace();
432 drop(guard);
433 return Err(e.with_stack_trace(trace));
434 }
435 drop(guard);
436 return Err(e);
437 }
438 }
439 }
440 }
441 Err(e) => {
442 if e.stack_trace().is_none() {
443 let trace = ctx.capture_stack_trace();
444 drop(guard);
445 return Err(e.with_stack_trace(trace));
446 }
447 drop(guard);
448 Err(e)
449 }
450 }
451}
452
453fn eval_step(ctx: &EvalContext, expr: &Value, env: &Env) -> Result<Trampoline, SemaError> {
454 match expr.view() {
455 ValueView::Nil
457 | ValueView::Bool(_)
458 | ValueView::Int(_)
459 | ValueView::Float(_)
460 | ValueView::String(_)
461 | ValueView::Char(_)
462 | ValueView::Thunk(_)
463 | ValueView::Bytevector(_) => Ok(Trampoline::Value(expr.clone())),
464 ValueView::Keyword(_) => Ok(Trampoline::Value(expr.clone())),
465 ValueView::Vector(items) => {
466 let mut result = Vec::with_capacity(items.len());
467 for item in items.iter() {
468 result.push(eval_value(ctx, item, env)?);
469 }
470 Ok(Trampoline::Value(Value::vector(result)))
471 }
472 ValueView::Map(map) => {
473 let mut result = std::collections::BTreeMap::new();
474 for (k, v) in map.iter() {
475 let ek = eval_value(ctx, k, env)?;
476 let ev = eval_value(ctx, v, env)?;
477 result.insert(ek, ev);
478 }
479 Ok(Trampoline::Value(Value::map(result)))
480 }
481 ValueView::HashMap(_) => Ok(Trampoline::Value(expr.clone())),
482
483 ValueView::Symbol(spur) => env
485 .get(spur)
486 .map(Trampoline::Value)
487 .ok_or_else(|| SemaError::Unbound(resolve(spur))),
488
489 ValueView::List(items) => {
491 if items.is_empty() {
492 return Ok(Trampoline::Value(Value::nil()));
493 }
494
495 let head = &items[0];
496 let args = &items[1..];
497
498 if let Some(spur) = head.as_symbol_spur() {
501 if let Some(result) = special_forms::try_eval_special(spur, args, env, ctx) {
502 return result;
503 }
504 }
505
506 let func = eval_value(ctx, head, env)?;
508
509 let call_span = span_of_expr(ctx, expr);
511
512 match func.view() {
513 ValueView::NativeFn(native) => {
514 let mut eval_args = Vec::with_capacity(args.len());
516 for arg in args {
517 eval_args.push(eval_value(ctx, arg, env)?);
518 }
519 let frame = CallFrame {
521 name: native.name.to_string(),
522 file: ctx.current_file_path(),
523 span: call_span,
524 };
525 ctx.push_call_frame(frame);
526 match (native.func)(ctx, &eval_args) {
527 Ok(v) => {
528 ctx.truncate_call_stack(ctx.call_stack_depth().saturating_sub(1));
530 Ok(Trampoline::Value(v))
531 }
532 Err(e) => Err(e),
534 }
535 }
536 ValueView::Lambda(lambda) => {
537 let mut eval_args = Vec::with_capacity(args.len());
539 for arg in args {
540 eval_args.push(eval_value(ctx, arg, env)?);
541 }
542 let frame = CallFrame {
544 name: lambda
545 .name
546 .map(resolve)
547 .unwrap_or_else(|| "<lambda>".to_string()),
548 file: ctx.current_file_path(),
549 span: call_span,
550 };
551 ctx.push_call_frame(frame);
552 apply_lambda(ctx, &lambda, &eval_args)
553 }
554 ValueView::Macro(mac) => {
555 let expanded = apply_macro(ctx, &mac, args, env)?;
557 Ok(Trampoline::Eval(expanded, env.clone()))
559 }
560 ValueView::Keyword(spur) => {
561 if args.len() != 1 {
563 let name = resolve(spur);
564 return Err(SemaError::arity(format!(":{name}"), "1", args.len()));
565 }
566 let map_val = eval_value(ctx, &args[0], env)?;
567 let key = Value::keyword_from_spur(spur);
568 match map_val.view() {
569 ValueView::Map(map) => Ok(Trampoline::Value(
570 map.get(&key).cloned().unwrap_or(Value::nil()),
571 )),
572 ValueView::HashMap(map) => Ok(Trampoline::Value(
573 map.get(&key).cloned().unwrap_or(Value::nil()),
574 )),
575 _ => Err(SemaError::type_error("map", map_val.type_name())),
576 }
577 }
578 _ => Err(
579 SemaError::eval(format!("not callable: {} ({})", func, func.type_name()))
580 .with_hint("the first element of a list must be a function or macro"),
581 ),
582 }
583 }
584
585 _other => Ok(Trampoline::Value(expr.clone())),
586 }
587}
588
589fn apply_lambda(
591 ctx: &EvalContext,
592 lambda: &Rc<Lambda>,
593 args: &[Value],
594) -> Result<Trampoline, SemaError> {
595 let new_env = Env::with_parent(Rc::new(lambda.env.clone()));
596
597 if let Some(rest) = lambda.rest_param {
599 if args.len() < lambda.params.len() {
600 return Err(SemaError::arity(
601 lambda
602 .name
603 .map(resolve)
604 .unwrap_or_else(|| "lambda".to_string()),
605 format!("{}+", lambda.params.len()),
606 args.len(),
607 ));
608 }
609 for (param, arg) in lambda.params.iter().zip(args.iter()) {
610 new_env.set(*param, arg.clone());
611 }
612 let rest_args = args[lambda.params.len()..].to_vec();
613 new_env.set(rest, Value::list(rest_args));
614 } else {
615 if args.len() != lambda.params.len() {
616 return Err(SemaError::arity(
617 lambda
618 .name
619 .map(resolve)
620 .unwrap_or_else(|| "lambda".to_string()),
621 lambda.params.len().to_string(),
622 args.len(),
623 ));
624 }
625 for (param, arg) in lambda.params.iter().zip(args.iter()) {
626 new_env.set(*param, arg.clone());
627 }
628 }
629
630 if let Some(name) = lambda.name {
632 new_env.set(name, Value::lambda_from_rc(Rc::clone(lambda)));
633 }
634
635 if lambda.body.is_empty() {
637 return Ok(Trampoline::Value(Value::nil()));
638 }
639 for expr in &lambda.body[..lambda.body.len() - 1] {
640 eval_value(ctx, expr, &new_env)?;
641 }
642 Ok(Trampoline::Eval(
643 lambda.body.last().unwrap().clone(),
644 new_env,
645 ))
646}
647
648pub fn apply_macro(
650 ctx: &EvalContext,
651 mac: &sema_core::Macro,
652 args: &[Value],
653 caller_env: &Env,
654) -> Result<Value, SemaError> {
655 let env = Env::with_parent(Rc::new(caller_env.clone()));
656
657 if let Some(rest) = mac.rest_param {
659 if args.len() < mac.params.len() {
660 return Err(SemaError::arity(
661 resolve(mac.name),
662 format!("{}+", mac.params.len()),
663 args.len(),
664 ));
665 }
666 for (param, arg) in mac.params.iter().zip(args.iter()) {
667 env.set(*param, arg.clone());
668 }
669 let rest_args = args[mac.params.len()..].to_vec();
670 env.set(rest, Value::list(rest_args));
671 } else {
672 if args.len() != mac.params.len() {
673 return Err(SemaError::arity(
674 resolve(mac.name),
675 mac.params.len().to_string(),
676 args.len(),
677 ));
678 }
679 for (param, arg) in mac.params.iter().zip(args.iter()) {
680 env.set(*param, arg.clone());
681 }
682 }
683
684 let mut result = Value::nil();
686 for expr in &mac.body {
687 result = eval_value(ctx, expr, &env)?;
688 }
689 Ok(result)
690}
691
692fn register_vm_delegates(env: &Rc<Env>) {
695 let eval_env = env.clone();
697 env.set(
698 intern("__vm-eval"),
699 Value::native_fn(NativeFn::with_ctx("__vm-eval", move |ctx, args| {
700 if args.len() != 1 {
701 return Err(SemaError::arity("eval", "1", args.len()));
702 }
703 sema_core::eval_callback(ctx, &args[0], &eval_env)
704 })),
705 );
706
707 let load_env = env.clone();
709 env.set(
710 intern("__vm-load"),
711 Value::native_fn(NativeFn::with_ctx("__vm-load", move |ctx, args| {
712 if args.len() != 1 {
713 return Err(SemaError::arity("load", "1", args.len()));
714 }
715 ctx.sandbox.check(sema_core::Caps::FS_READ, "load")?;
716 let path = match args[0].as_str() {
717 Some(s) => s.to_string(),
718 None => return Err(SemaError::type_error("string", args[0].type_name())),
719 };
720 let full_path = if let Some(dir) = ctx.current_file_dir() {
721 dir.join(&path)
722 } else {
723 std::path::PathBuf::from(&path)
724 };
725 let content = std::fs::read_to_string(&full_path).map_err(|e| {
726 SemaError::eval(format!("load: cannot read {}: {}", full_path.display(), e))
727 })?;
728 ctx.push_file_path(full_path);
729 let result = eval_string(ctx, &content, &load_env);
730 ctx.pop_file_path();
731 result
732 })),
733 );
734
735 let import_env = env.clone();
737 env.set(
738 intern("__vm-import"),
739 Value::native_fn(NativeFn::with_ctx("__vm-import", move |ctx, args| {
740 if args.len() != 2 {
741 return Err(SemaError::arity("import", "2", args.len()));
742 }
743 ctx.sandbox.check(sema_core::Caps::FS_READ, "import")?;
744 let mut form = vec![Value::symbol("import"), args[0].clone()];
745 if let Some(items) = args[1].as_list() {
746 if !items.is_empty() {
747 for item in items.iter() {
748 form.push(item.clone());
749 }
750 }
751 }
752 let import_expr = Value::list(form);
753 sema_core::eval_callback(ctx, &import_expr, &import_env)
754 })),
755 );
756
757 let macro_env = env.clone();
759 env.set(
760 intern("__vm-defmacro"),
761 Value::native_fn(NativeFn::simple("__vm-defmacro", move |args| {
762 if args.len() != 4 {
763 return Err(SemaError::arity("defmacro", "4", args.len()));
764 }
765 let name = match args[0].as_symbol_spur() {
766 Some(s) => s,
767 None => return Err(SemaError::type_error("symbol", args[0].type_name())),
768 };
769 let params = match args[1].as_list() {
770 Some(items) => items
771 .iter()
772 .map(|v| match v.as_symbol_spur() {
773 Some(s) => Ok(s),
774 None => Err(SemaError::type_error("symbol", v.type_name())),
775 })
776 .collect::<Result<Vec<_>, _>>()?,
777 None => return Err(SemaError::type_error("list", args[1].type_name())),
778 };
779 let rest_param = if let Some(s) = args[2].as_symbol_spur() {
780 Some(s)
781 } else if args[2].is_nil() {
782 None
783 } else {
784 return Err(SemaError::type_error("symbol or nil", args[2].type_name()));
785 };
786 let body = vec![args[3].clone()];
787 macro_env.set(
788 name,
789 Value::macro_val(Macro {
790 params,
791 rest_param,
792 body,
793 name,
794 }),
795 );
796 Ok(Value::nil())
797 })),
798 );
799
800 let dmf_env = env.clone();
802 env.set(
803 intern("__vm-defmacro-form"),
804 Value::native_fn(NativeFn::with_ctx(
805 "__vm-defmacro-form",
806 move |ctx, args| {
807 if args.len() != 1 {
808 return Err(SemaError::arity("defmacro-form", "1", args.len()));
809 }
810 sema_core::eval_callback(ctx, &args[0], &dmf_env)
811 },
812 )),
813 );
814
815 let drt_env = env.clone();
817 env.set(
818 intern("__vm-define-record-type"),
819 Value::native_fn(NativeFn::with_ctx(
820 "__vm-define-record-type",
821 move |ctx, args| {
822 if args.len() != 5 {
823 return Err(SemaError::arity("define-record-type", "5", args.len()));
824 }
825 let mut ctor_form = vec![args[1].clone()];
826 if let Some(fields) = args[3].as_list() {
827 ctor_form.extend(fields.iter().cloned());
828 }
829 let mut form = vec![
830 Value::symbol("define-record-type"),
831 args[0].clone(),
832 Value::list(ctor_form),
833 args[2].clone(),
834 ];
835 if let Some(specs) = args[4].as_list() {
836 for spec in specs.iter() {
837 form.push(spec.clone());
838 }
839 }
840 sema_core::eval_callback(ctx, &Value::list(form), &drt_env)
841 },
842 )),
843 );
844
845 env.set(
847 intern("__vm-delay"),
848 Value::native_fn(NativeFn::simple("__vm-delay", |args| {
849 if args.len() != 1 {
850 return Err(SemaError::arity("delay", "1", args.len()));
851 }
852 Ok(Value::thunk(Thunk {
854 body: args[0].clone(),
855 forced: RefCell::new(None),
856 }))
857 })),
858 );
859
860 let force_env = env.clone();
862 env.set(
863 intern("__vm-force"),
864 Value::native_fn(NativeFn::with_ctx("__vm-force", move |ctx, args| {
865 if args.len() != 1 {
866 return Err(SemaError::arity("force", "1", args.len()));
867 }
868 if let Some(thunk) = args[0].as_thunk_rc() {
869 if let Some(val) = thunk.forced.borrow().as_ref() {
870 return Ok(val.clone());
871 }
872 let val = if thunk.body.as_native_fn_rc().is_some()
873 || thunk.body.as_lambda_rc().is_some()
874 {
875 sema_core::call_callback(ctx, &thunk.body, &[])?
876 } else {
877 sema_core::eval_callback(ctx, &thunk.body, &force_env)?
878 };
879 *thunk.forced.borrow_mut() = Some(val.clone());
880 Ok(val)
881 } else {
882 Ok(args[0].clone())
883 }
884 })),
885 );
886
887 let me_env = env.clone();
889 env.set(
890 intern("__vm-macroexpand"),
891 Value::native_fn(NativeFn::with_ctx("__vm-macroexpand", move |ctx, args| {
892 if args.len() != 1 {
893 return Err(SemaError::arity("macroexpand", "1", args.len()));
894 }
895 if let Some(items) = args[0].as_list() {
896 if !items.is_empty() {
897 if let Some(spur) = items[0].as_symbol_spur() {
898 if let Some(mac_val) = me_env.get(spur) {
899 if let Some(mac) = mac_val.as_macro_rc() {
900 return apply_macro(ctx, &mac, &items[1..], &me_env);
901 }
902 }
903 }
904 }
905 }
906 Ok(args[0].clone())
907 })),
908 );
909
910 let prompt_env = env.clone();
912 env.set(
913 intern("__vm-prompt"),
914 Value::native_fn(NativeFn::with_ctx("__vm-prompt", move |ctx, args| {
915 let mut form = vec![Value::symbol("prompt")];
916 form.extend(args.iter().cloned());
917 sema_core::eval_callback(ctx, &Value::list(form), &prompt_env)
918 })),
919 );
920
921 let msg_env = env.clone();
923 env.set(
924 intern("__vm-message"),
925 Value::native_fn(NativeFn::with_ctx("__vm-message", move |ctx, args| {
926 if args.len() != 2 {
927 return Err(SemaError::arity("message", "2", args.len()));
928 }
929 let form = Value::list(vec![
930 Value::symbol("message"),
931 args[0].clone(),
932 args[1].clone(),
933 ]);
934 sema_core::eval_callback(ctx, &form, &msg_env)
935 })),
936 );
937
938 let tool_env = env.clone();
940 env.set(
941 intern("__vm-deftool"),
942 Value::native_fn(NativeFn::with_ctx("__vm-deftool", move |ctx, args| {
943 if args.len() != 4 {
944 return Err(SemaError::arity("deftool", "4", args.len()));
945 }
946 let form = Value::list(vec![
947 Value::symbol("deftool"),
948 args[0].clone(),
949 args[1].clone(),
950 args[2].clone(),
951 args[3].clone(),
952 ]);
953 sema_core::eval_callback(ctx, &form, &tool_env)
954 })),
955 );
956
957 let agent_env = env.clone();
959 env.set(
960 intern("__vm-defagent"),
961 Value::native_fn(NativeFn::with_ctx("__vm-defagent", move |ctx, args| {
962 if args.len() != 2 {
963 return Err(SemaError::arity("defagent", "2", args.len()));
964 }
965 let form = Value::list(vec![
966 Value::symbol("defagent"),
967 args[0].clone(),
968 args[1].clone(),
969 ]);
970 sema_core::eval_callback(ctx, &form, &agent_env)
971 })),
972 );
973}