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 let mut result = Value::nil();
229 for expr in &exprs {
230 result = eval_value(ctx, expr, env)?;
231 }
232 Ok(result)
233}
234
235pub fn eval(ctx: &EvalContext, expr: &Value, env: &Env) -> EvalResult {
237 eval_value(ctx, expr, env)
238}
239
240#[cfg(target_arch = "wasm32")]
245const MAX_EVAL_DEPTH: usize = 256;
246#[cfg(not(target_arch = "wasm32"))]
247const MAX_EVAL_DEPTH: usize = 1024;
248
249pub fn eval_value(ctx: &EvalContext, expr: &Value, env: &Env) -> EvalResult {
250 match expr.view() {
252 ValueView::Nil
253 | ValueView::Bool(_)
254 | ValueView::Int(_)
255 | ValueView::Float(_)
256 | ValueView::String(_)
257 | ValueView::Char(_)
258 | ValueView::Keyword(_)
259 | ValueView::Thunk(_)
260 | ValueView::Bytevector(_)
261 | ValueView::NativeFn(_)
262 | ValueView::Lambda(_)
263 | ValueView::HashMap(_) => return Ok(expr.clone()),
264 ValueView::Symbol(spur) => {
265 if let Some(val) = env.get(spur) {
266 return Ok(val);
267 }
268 return Err(SemaError::Unbound(resolve(spur)));
269 }
270 _ => {}
271 }
272
273 let depth = ctx.eval_depth.get();
274 ctx.eval_depth.set(depth + 1);
275 if depth == 0 {
276 ctx.eval_steps.set(0);
277 }
278 if depth > MAX_EVAL_DEPTH {
279 ctx.eval_depth.set(ctx.eval_depth.get().saturating_sub(1));
280 return Err(SemaError::eval(format!(
281 "maximum eval depth exceeded ({MAX_EVAL_DEPTH})"
282 )));
283 }
284
285 let result = eval_value_inner(ctx, expr, env);
286
287 ctx.eval_depth.set(ctx.eval_depth.get().saturating_sub(1));
288 result
289}
290
291pub fn call_value(ctx: &EvalContext, func: &Value, args: &[Value]) -> EvalResult {
294 match func.view() {
295 ValueView::NativeFn(native) => (native.func)(ctx, args),
296 ValueView::Lambda(lambda) => {
297 let new_env = Env::with_parent(Rc::new(lambda.env.clone()));
298
299 if let Some(rest) = lambda.rest_param {
300 if args.len() < lambda.params.len() {
301 return Err(SemaError::arity(
302 lambda
303 .name
304 .map(resolve)
305 .unwrap_or_else(|| "lambda".to_string()),
306 format!("{}+", lambda.params.len()),
307 args.len(),
308 ));
309 }
310 for (param, arg) in lambda.params.iter().zip(args.iter()) {
311 new_env.set(*param, arg.clone());
312 }
313 let rest_args = args[lambda.params.len()..].to_vec();
314 new_env.set(rest, Value::list(rest_args));
315 } else {
316 if args.len() != lambda.params.len() {
317 return Err(SemaError::arity(
318 lambda
319 .name
320 .map(resolve)
321 .unwrap_or_else(|| "lambda".to_string()),
322 lambda.params.len().to_string(),
323 args.len(),
324 ));
325 }
326 for (param, arg) in lambda.params.iter().zip(args.iter()) {
327 new_env.set(*param, arg.clone());
328 }
329 }
330
331 if let Some(name) = lambda.name {
332 new_env.set(name, Value::lambda_from_rc(Rc::clone(&lambda)));
333 }
334
335 let mut result = Value::nil();
336 for expr in &lambda.body {
337 result = eval_value(ctx, expr, &new_env)?;
338 }
339 Ok(result)
340 }
341 ValueView::Keyword(spur) => {
342 if args.len() != 1 {
343 let name = resolve(spur);
344 return Err(SemaError::arity(format!(":{name}"), "1", args.len()));
345 }
346 let key = Value::keyword_from_spur(spur);
347 match args[0].view() {
348 ValueView::Map(map) => Ok(map.get(&key).cloned().unwrap_or(Value::nil())),
349 ValueView::HashMap(map) => Ok(map.get(&key).cloned().unwrap_or(Value::nil())),
350 _ => Err(SemaError::type_error("map", args[0].type_name())),
351 }
352 }
353 _ => Err(
354 SemaError::eval(format!("not callable: {} ({})", func, func.type_name()))
355 .with_hint("expected a function, lambda, or keyword"),
356 ),
357 }
358}
359
360fn eval_value_inner(ctx: &EvalContext, expr: &Value, env: &Env) -> EvalResult {
361 let entry_depth = ctx.call_stack_depth();
362 let guard = CallStackGuard { ctx, entry_depth };
363 let limit = ctx.eval_step_limit.get();
364
365 if limit > 0 {
367 let v = ctx.eval_steps.get() + 1;
368 ctx.eval_steps.set(v);
369 if v > limit {
370 return Err(SemaError::eval("eval step limit exceeded".to_string()));
371 }
372 }
373
374 match eval_step(ctx, expr, env) {
375 Ok(Trampoline::Value(v)) => {
376 drop(guard);
377 Ok(v)
378 }
379 Ok(Trampoline::Eval(next_expr, next_env)) => {
380 let mut current_expr = next_expr;
382 let mut current_env = next_env;
383
384 {
386 let mut stack = ctx.call_stack.borrow_mut();
387 if stack.len() > entry_depth + 1 {
388 let top = stack.last().cloned();
389 stack.truncate(entry_depth);
390 if let Some(frame) = top {
391 stack.push(frame);
392 }
393 }
394 }
395
396 loop {
397 if limit > 0 {
398 let v = ctx.eval_steps.get() + 1;
399 ctx.eval_steps.set(v);
400 if v > limit {
401 return Err(SemaError::eval("eval step limit exceeded".to_string()));
402 }
403 }
404
405 match eval_step(ctx, ¤t_expr, ¤t_env) {
406 Ok(Trampoline::Value(v)) => {
407 drop(guard);
408 return Ok(v);
409 }
410 Ok(Trampoline::Eval(next_expr, next_env)) => {
411 {
412 let mut stack = ctx.call_stack.borrow_mut();
413 if stack.len() > entry_depth + 1 {
414 let top = stack.last().cloned();
415 stack.truncate(entry_depth);
416 if let Some(frame) = top {
417 stack.push(frame);
418 }
419 }
420 }
421 current_expr = next_expr;
422 current_env = next_env;
423 }
424 Err(e) => {
425 if e.stack_trace().is_none() {
426 let trace = ctx.capture_stack_trace();
427 drop(guard);
428 return Err(e.with_stack_trace(trace));
429 }
430 drop(guard);
431 return Err(e);
432 }
433 }
434 }
435 }
436 Err(e) => {
437 if e.stack_trace().is_none() {
438 let trace = ctx.capture_stack_trace();
439 drop(guard);
440 return Err(e.with_stack_trace(trace));
441 }
442 drop(guard);
443 Err(e)
444 }
445 }
446}
447
448fn eval_step(ctx: &EvalContext, expr: &Value, env: &Env) -> Result<Trampoline, SemaError> {
449 match expr.view() {
450 ValueView::Nil
452 | ValueView::Bool(_)
453 | ValueView::Int(_)
454 | ValueView::Float(_)
455 | ValueView::String(_)
456 | ValueView::Char(_)
457 | ValueView::Thunk(_)
458 | ValueView::Bytevector(_) => Ok(Trampoline::Value(expr.clone())),
459 ValueView::Keyword(_) => Ok(Trampoline::Value(expr.clone())),
460 ValueView::Vector(items) => {
461 let mut result = Vec::with_capacity(items.len());
462 for item in items.iter() {
463 result.push(eval_value(ctx, item, env)?);
464 }
465 Ok(Trampoline::Value(Value::vector(result)))
466 }
467 ValueView::Map(map) => {
468 let mut result = std::collections::BTreeMap::new();
469 for (k, v) in map.iter() {
470 let ek = eval_value(ctx, k, env)?;
471 let ev = eval_value(ctx, v, env)?;
472 result.insert(ek, ev);
473 }
474 Ok(Trampoline::Value(Value::map(result)))
475 }
476 ValueView::HashMap(_) => Ok(Trampoline::Value(expr.clone())),
477
478 ValueView::Symbol(spur) => env
480 .get(spur)
481 .map(Trampoline::Value)
482 .ok_or_else(|| SemaError::Unbound(resolve(spur))),
483
484 ValueView::List(items) => {
486 if items.is_empty() {
487 return Ok(Trampoline::Value(Value::nil()));
488 }
489
490 let head = &items[0];
491 let args = &items[1..];
492
493 if let Some(spur) = head.as_symbol_spur() {
496 if let Some(result) = special_forms::try_eval_special(spur, args, env, ctx) {
497 return result;
498 }
499 }
500
501 let func = eval_value(ctx, head, env)?;
503
504 let call_span = span_of_expr(ctx, expr);
506
507 match func.view() {
508 ValueView::NativeFn(native) => {
509 let mut eval_args = Vec::with_capacity(args.len());
511 for arg in args {
512 eval_args.push(eval_value(ctx, arg, env)?);
513 }
514 let frame = CallFrame {
516 name: native.name.to_string(),
517 file: ctx.current_file_path(),
518 span: call_span,
519 };
520 ctx.push_call_frame(frame);
521 match (native.func)(ctx, &eval_args) {
522 Ok(v) => {
523 ctx.truncate_call_stack(ctx.call_stack_depth().saturating_sub(1));
525 Ok(Trampoline::Value(v))
526 }
527 Err(e) => Err(e),
529 }
530 }
531 ValueView::Lambda(lambda) => {
532 let mut eval_args = Vec::with_capacity(args.len());
534 for arg in args {
535 eval_args.push(eval_value(ctx, arg, env)?);
536 }
537 let frame = CallFrame {
539 name: lambda
540 .name
541 .map(resolve)
542 .unwrap_or_else(|| "<lambda>".to_string()),
543 file: ctx.current_file_path(),
544 span: call_span,
545 };
546 ctx.push_call_frame(frame);
547 apply_lambda(ctx, &lambda, &eval_args)
548 }
549 ValueView::Macro(mac) => {
550 let expanded = apply_macro(ctx, &mac, args, env)?;
552 Ok(Trampoline::Eval(expanded, env.clone()))
554 }
555 ValueView::Keyword(spur) => {
556 if args.len() != 1 {
558 let name = resolve(spur);
559 return Err(SemaError::arity(format!(":{name}"), "1", args.len()));
560 }
561 let map_val = eval_value(ctx, &args[0], env)?;
562 let key = Value::keyword_from_spur(spur);
563 match map_val.view() {
564 ValueView::Map(map) => Ok(Trampoline::Value(
565 map.get(&key).cloned().unwrap_or(Value::nil()),
566 )),
567 ValueView::HashMap(map) => Ok(Trampoline::Value(
568 map.get(&key).cloned().unwrap_or(Value::nil()),
569 )),
570 _ => Err(SemaError::type_error("map", map_val.type_name())),
571 }
572 }
573 _ => Err(
574 SemaError::eval(format!("not callable: {} ({})", func, func.type_name()))
575 .with_hint("the first element of a list must be a function or macro"),
576 ),
577 }
578 }
579
580 _other => Ok(Trampoline::Value(expr.clone())),
581 }
582}
583
584fn apply_lambda(
586 ctx: &EvalContext,
587 lambda: &Rc<Lambda>,
588 args: &[Value],
589) -> Result<Trampoline, SemaError> {
590 let new_env = Env::with_parent(Rc::new(lambda.env.clone()));
591
592 if let Some(rest) = lambda.rest_param {
594 if args.len() < lambda.params.len() {
595 return Err(SemaError::arity(
596 lambda
597 .name
598 .map(resolve)
599 .unwrap_or_else(|| "lambda".to_string()),
600 format!("{}+", lambda.params.len()),
601 args.len(),
602 ));
603 }
604 for (param, arg) in lambda.params.iter().zip(args.iter()) {
605 new_env.set(*param, arg.clone());
606 }
607 let rest_args = args[lambda.params.len()..].to_vec();
608 new_env.set(rest, Value::list(rest_args));
609 } else {
610 if args.len() != lambda.params.len() {
611 return Err(SemaError::arity(
612 lambda
613 .name
614 .map(resolve)
615 .unwrap_or_else(|| "lambda".to_string()),
616 lambda.params.len().to_string(),
617 args.len(),
618 ));
619 }
620 for (param, arg) in lambda.params.iter().zip(args.iter()) {
621 new_env.set(*param, arg.clone());
622 }
623 }
624
625 if let Some(name) = lambda.name {
627 new_env.set(name, Value::lambda_from_rc(Rc::clone(lambda)));
628 }
629
630 if lambda.body.is_empty() {
632 return Ok(Trampoline::Value(Value::nil()));
633 }
634 for expr in &lambda.body[..lambda.body.len() - 1] {
635 eval_value(ctx, expr, &new_env)?;
636 }
637 Ok(Trampoline::Eval(
638 lambda.body.last().unwrap().clone(),
639 new_env,
640 ))
641}
642
643pub fn apply_macro(
645 ctx: &EvalContext,
646 mac: &sema_core::Macro,
647 args: &[Value],
648 caller_env: &Env,
649) -> Result<Value, SemaError> {
650 let env = Env::with_parent(Rc::new(caller_env.clone()));
651
652 if let Some(rest) = mac.rest_param {
654 if args.len() < mac.params.len() {
655 return Err(SemaError::arity(
656 resolve(mac.name),
657 format!("{}+", mac.params.len()),
658 args.len(),
659 ));
660 }
661 for (param, arg) in mac.params.iter().zip(args.iter()) {
662 env.set(*param, arg.clone());
663 }
664 let rest_args = args[mac.params.len()..].to_vec();
665 env.set(rest, Value::list(rest_args));
666 } else {
667 if args.len() != mac.params.len() {
668 return Err(SemaError::arity(
669 resolve(mac.name),
670 mac.params.len().to_string(),
671 args.len(),
672 ));
673 }
674 for (param, arg) in mac.params.iter().zip(args.iter()) {
675 env.set(*param, arg.clone());
676 }
677 }
678
679 let mut result = Value::nil();
681 for expr in &mac.body {
682 result = eval_value(ctx, expr, &env)?;
683 }
684 Ok(result)
685}
686
687fn register_vm_delegates(env: &Rc<Env>) {
690 let eval_env = env.clone();
692 env.set(
693 intern("__vm-eval"),
694 Value::native_fn(NativeFn::with_ctx("__vm-eval", move |ctx, args| {
695 if args.len() != 1 {
696 return Err(SemaError::arity("eval", "1", args.len()));
697 }
698 sema_core::eval_callback(ctx, &args[0], &eval_env)
699 })),
700 );
701
702 let load_env = env.clone();
704 env.set(
705 intern("__vm-load"),
706 Value::native_fn(NativeFn::with_ctx("__vm-load", move |ctx, args| {
707 if args.len() != 1 {
708 return Err(SemaError::arity("load", "1", args.len()));
709 }
710 ctx.sandbox.check(sema_core::Caps::FS_READ, "load")?;
711 let path = match args[0].as_str() {
712 Some(s) => s.to_string(),
713 None => return Err(SemaError::type_error("string", args[0].type_name())),
714 };
715 let full_path = if let Some(dir) = ctx.current_file_dir() {
716 dir.join(&path)
717 } else {
718 std::path::PathBuf::from(&path)
719 };
720 let content = std::fs::read_to_string(&full_path).map_err(|e| {
721 SemaError::eval(format!("load: cannot read {}: {}", full_path.display(), e))
722 })?;
723 ctx.push_file_path(full_path);
724 let result = eval_string(ctx, &content, &load_env);
725 ctx.pop_file_path();
726 result
727 })),
728 );
729
730 let import_env = env.clone();
732 env.set(
733 intern("__vm-import"),
734 Value::native_fn(NativeFn::with_ctx("__vm-import", move |ctx, args| {
735 if args.len() != 2 {
736 return Err(SemaError::arity("import", "2", args.len()));
737 }
738 ctx.sandbox.check(sema_core::Caps::FS_READ, "import")?;
739 let mut form = vec![Value::symbol("import"), args[0].clone()];
740 if let Some(items) = args[1].as_list() {
741 if !items.is_empty() {
742 for item in items.iter() {
743 form.push(item.clone());
744 }
745 }
746 }
747 let import_expr = Value::list(form);
748 sema_core::eval_callback(ctx, &import_expr, &import_env)
749 })),
750 );
751
752 let macro_env = env.clone();
754 env.set(
755 intern("__vm-defmacro"),
756 Value::native_fn(NativeFn::simple("__vm-defmacro", move |args| {
757 if args.len() != 4 {
758 return Err(SemaError::arity("defmacro", "4", args.len()));
759 }
760 let name = match args[0].as_symbol_spur() {
761 Some(s) => s,
762 None => return Err(SemaError::type_error("symbol", args[0].type_name())),
763 };
764 let params = match args[1].as_list() {
765 Some(items) => items
766 .iter()
767 .map(|v| match v.as_symbol_spur() {
768 Some(s) => Ok(s),
769 None => Err(SemaError::type_error("symbol", v.type_name())),
770 })
771 .collect::<Result<Vec<_>, _>>()?,
772 None => return Err(SemaError::type_error("list", args[1].type_name())),
773 };
774 let rest_param = if let Some(s) = args[2].as_symbol_spur() {
775 Some(s)
776 } else if args[2].is_nil() {
777 None
778 } else {
779 return Err(SemaError::type_error("symbol or nil", args[2].type_name()));
780 };
781 let body = vec![args[3].clone()];
782 macro_env.set(
783 name,
784 Value::macro_val(Macro {
785 params,
786 rest_param,
787 body,
788 name,
789 }),
790 );
791 Ok(Value::nil())
792 })),
793 );
794
795 let dmf_env = env.clone();
797 env.set(
798 intern("__vm-defmacro-form"),
799 Value::native_fn(NativeFn::with_ctx(
800 "__vm-defmacro-form",
801 move |ctx, args| {
802 if args.len() != 1 {
803 return Err(SemaError::arity("defmacro-form", "1", args.len()));
804 }
805 sema_core::eval_callback(ctx, &args[0], &dmf_env)
806 },
807 )),
808 );
809
810 let drt_env = env.clone();
812 env.set(
813 intern("__vm-define-record-type"),
814 Value::native_fn(NativeFn::with_ctx(
815 "__vm-define-record-type",
816 move |ctx, args| {
817 if args.len() != 5 {
818 return Err(SemaError::arity("define-record-type", "5", args.len()));
819 }
820 let mut ctor_form = vec![args[1].clone()];
821 if let Some(fields) = args[3].as_list() {
822 ctor_form.extend(fields.iter().cloned());
823 }
824 let mut form = vec![
825 Value::symbol("define-record-type"),
826 args[0].clone(),
827 Value::list(ctor_form),
828 args[2].clone(),
829 ];
830 if let Some(specs) = args[4].as_list() {
831 for spec in specs.iter() {
832 form.push(spec.clone());
833 }
834 }
835 sema_core::eval_callback(ctx, &Value::list(form), &drt_env)
836 },
837 )),
838 );
839
840 env.set(
842 intern("__vm-delay"),
843 Value::native_fn(NativeFn::simple("__vm-delay", |args| {
844 if args.len() != 1 {
845 return Err(SemaError::arity("delay", "1", args.len()));
846 }
847 Ok(Value::thunk(Thunk {
849 body: args[0].clone(),
850 forced: RefCell::new(None),
851 }))
852 })),
853 );
854
855 let force_env = env.clone();
857 env.set(
858 intern("__vm-force"),
859 Value::native_fn(NativeFn::with_ctx("__vm-force", move |ctx, args| {
860 if args.len() != 1 {
861 return Err(SemaError::arity("force", "1", args.len()));
862 }
863 if let Some(thunk) = args[0].as_thunk_rc() {
864 if let Some(val) = thunk.forced.borrow().as_ref() {
865 return Ok(val.clone());
866 }
867 let val = if thunk.body.as_native_fn_rc().is_some()
868 || thunk.body.as_lambda_rc().is_some()
869 {
870 sema_core::call_callback(ctx, &thunk.body, &[])?
871 } else {
872 sema_core::eval_callback(ctx, &thunk.body, &force_env)?
873 };
874 *thunk.forced.borrow_mut() = Some(val.clone());
875 Ok(val)
876 } else {
877 Ok(args[0].clone())
878 }
879 })),
880 );
881
882 let me_env = env.clone();
884 env.set(
885 intern("__vm-macroexpand"),
886 Value::native_fn(NativeFn::with_ctx("__vm-macroexpand", move |ctx, args| {
887 if args.len() != 1 {
888 return Err(SemaError::arity("macroexpand", "1", args.len()));
889 }
890 if let Some(items) = args[0].as_list() {
891 if !items.is_empty() {
892 if let Some(spur) = items[0].as_symbol_spur() {
893 if let Some(mac_val) = me_env.get(spur) {
894 if let Some(mac) = mac_val.as_macro_rc() {
895 return apply_macro(ctx, &mac, &items[1..], &me_env);
896 }
897 }
898 }
899 }
900 }
901 Ok(args[0].clone())
902 })),
903 );
904
905 let prompt_env = env.clone();
907 env.set(
908 intern("__vm-prompt"),
909 Value::native_fn(NativeFn::with_ctx("__vm-prompt", move |ctx, args| {
910 let mut form = vec![Value::symbol("prompt")];
911 form.extend(args.iter().cloned());
912 sema_core::eval_callback(ctx, &Value::list(form), &prompt_env)
913 })),
914 );
915
916 let msg_env = env.clone();
918 env.set(
919 intern("__vm-message"),
920 Value::native_fn(NativeFn::with_ctx("__vm-message", move |ctx, args| {
921 if args.len() != 2 {
922 return Err(SemaError::arity("message", "2", args.len()));
923 }
924 let form = Value::list(vec![
925 Value::symbol("message"),
926 args[0].clone(),
927 args[1].clone(),
928 ]);
929 sema_core::eval_callback(ctx, &form, &msg_env)
930 })),
931 );
932
933 let tool_env = env.clone();
935 env.set(
936 intern("__vm-deftool"),
937 Value::native_fn(NativeFn::with_ctx("__vm-deftool", move |ctx, args| {
938 if args.len() != 4 {
939 return Err(SemaError::arity("deftool", "4", args.len()));
940 }
941 let form = Value::list(vec![
942 Value::symbol("deftool"),
943 args[0].clone(),
944 args[1].clone(),
945 args[2].clone(),
946 args[3].clone(),
947 ]);
948 sema_core::eval_callback(ctx, &form, &tool_env)
949 })),
950 );
951
952 let agent_env = env.clone();
954 env.set(
955 intern("__vm-defagent"),
956 Value::native_fn(NativeFn::with_ctx("__vm-defagent", move |ctx, args| {
957 if args.len() != 2 {
958 return Err(SemaError::arity("defagent", "2", args.len()));
959 }
960 let form = Value::list(vec![
961 Value::symbol("defagent"),
962 args[0].clone(),
963 args[1].clone(),
964 ]);
965 sema_core::eval_callback(ctx, &form, &agent_env)
966 })),
967 );
968}