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