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