1use std::cell::Cell;
2use std::rc::Rc;
3
4use sema_core::{
5 intern, resolve, Agent, Env, EvalContext, Lambda, Macro, Record, SemaError, Spur, Thunk,
6 ToolDefinition, Value,
7};
8
9use crate::eval::{self, Trampoline};
10
11struct SpecialFormSpurs {
18 and: Spur,
20 begin: Spur,
21 case: Spur,
22 catch: Spur,
23 cond: Spur,
24 define: Spur,
25 define_record_type: Spur,
26 defmacro: Spur,
27 defun: Spur,
28 delay: Spur,
29 do_: Spur,
30 else_: Spur,
31 eval: Spur,
32 export: Spur,
33 fn_: Spur,
34 force: Spur,
35 if_: Spur,
36 lambda: Spur,
37 let_: Spur,
38 let_star: Spur,
39 letrec: Spur,
40 macroexpand: Spur,
41 or: Spur,
42 quasiquote: Spur,
43 quote: Spur,
44 set_bang: Spur,
45 throw: Spur,
46 try_: Spur,
47 unless: Spur,
48 when: Spur,
49
50 import: Spur,
52 load: Spur,
53 module: Spur,
54
55 defagent: Spur,
57 deftool: Spur,
58 message: Spur,
59 prompt: Spur,
60}
61
62impl SpecialFormSpurs {
63 fn init() -> Self {
64 Self {
65 and: intern("and"),
67 begin: intern("begin"),
68 case: intern("case"),
69 catch: intern("catch"),
70 cond: intern("cond"),
71 define: intern("define"),
72 define_record_type: intern("define-record-type"),
73 defmacro: intern("defmacro"),
74 defun: intern("defun"),
75 delay: intern("delay"),
76 do_: intern("do"),
77 else_: intern("else"),
78 eval: intern("eval"),
79 export: intern("export"),
80 fn_: intern("fn"),
81 force: intern("force"),
82 if_: intern("if"),
83 lambda: intern("lambda"),
84 let_: intern("let"),
85 let_star: intern("let*"),
86 letrec: intern("letrec"),
87 macroexpand: intern("macroexpand"),
88 or: intern("or"),
89 quasiquote: intern("quasiquote"),
90 quote: intern("quote"),
91 set_bang: intern("set!"),
92 throw: intern("throw"),
93 try_: intern("try"),
94 unless: intern("unless"),
95 when: intern("when"),
96
97 import: intern("import"),
99 load: intern("load"),
100 module: intern("module"),
101
102 defagent: intern("defagent"),
104 deftool: intern("deftool"),
105 message: intern("message"),
106 prompt: intern("prompt"),
107 }
108 }
109}
110
111thread_local! {
112 static SF: Cell<Option<&'static SpecialFormSpurs>> = const { Cell::new(None) };
113}
114
115fn special_forms() -> &'static SpecialFormSpurs {
116 SF.with(|cell| match cell.get() {
117 Some(sf) => sf,
118 None => {
119 let sf: &'static SpecialFormSpurs = Box::leak(Box::new(SpecialFormSpurs::init()));
120 cell.set(Some(sf));
121 sf
122 }
123 })
124}
125
126pub const SPECIAL_FORM_NAMES: &[&str] = &[
131 "and",
133 "begin",
134 "case",
135 "cond",
136 "define",
137 "define-record-type",
138 "defmacro",
139 "defun",
140 "delay",
141 "do",
142 "eval",
143 "fn",
144 "force",
145 "if",
146 "lambda",
147 "let",
148 "let*",
149 "letrec",
150 "macroexpand",
151 "or",
152 "quasiquote",
153 "quote",
154 "set!",
155 "throw",
156 "try",
157 "unless",
158 "when",
159 "export",
161 "import",
162 "load",
163 "module",
164 "defagent",
166 "deftool",
167 "message",
168 "prompt",
169];
170
171pub fn try_eval_special(
173 head_spur: Spur,
174 args: &[Value],
175 env: &Env,
176 ctx: &EvalContext,
177) -> Option<Result<Trampoline, SemaError>> {
178 let sf = special_forms();
179
180 if head_spur == sf.if_ {
182 Some(eval_if(args, env, ctx))
183 } else if head_spur == sf.define {
184 Some(eval_define(args, env, ctx))
185 } else if head_spur == sf.let_ {
186 Some(eval_let(args, env, ctx))
187 } else if head_spur == sf.begin {
188 Some(eval_begin(args, env, ctx))
189 } else if head_spur == sf.lambda || head_spur == sf.fn_ {
190 Some(eval_lambda(args, env, None))
191 } else if head_spur == sf.and {
192 Some(eval_and(args, env, ctx))
193 } else if head_spur == sf.case {
194 Some(eval_case(args, env, ctx))
195 } else if head_spur == sf.cond {
196 Some(eval_cond(args, env, ctx))
197 } else if head_spur == sf.define_record_type {
198 Some(eval_define_record_type(args, env))
199 } else if head_spur == sf.defmacro {
200 Some(eval_defmacro(args, env))
201 } else if head_spur == sf.defun {
202 Some(eval_defun(args, env, ctx))
203 } else if head_spur == sf.delay {
204 Some(eval_delay(args, env))
205 } else if head_spur == sf.do_ {
206 Some(eval_do(args, env, ctx))
207 } else if head_spur == sf.eval {
208 Some(eval_eval(args, env, ctx))
209 } else if head_spur == sf.force {
210 Some(eval_force(args, env, ctx))
211 } else if head_spur == sf.let_star {
212 Some(eval_let_star(args, env, ctx))
213 } else if head_spur == sf.letrec {
214 Some(eval_letrec(args, env, ctx))
215 } else if head_spur == sf.macroexpand {
216 Some(eval_macroexpand(args, env, ctx))
217 } else if head_spur == sf.or {
218 Some(eval_or(args, env, ctx))
219 } else if head_spur == sf.quasiquote {
220 Some(eval_quasiquote(args, env, ctx))
221 } else if head_spur == sf.quote {
222 Some(eval_quote(args))
223 } else if head_spur == sf.set_bang {
224 Some(eval_set(args, env, ctx))
225 } else if head_spur == sf.throw {
226 Some(eval_throw(args, env, ctx))
227 } else if head_spur == sf.try_ {
228 Some(eval_try(args, env, ctx))
229 } else if head_spur == sf.unless {
230 Some(eval_unless(args, env, ctx))
231 } else if head_spur == sf.when {
232 Some(eval_when(args, env, ctx))
233
234 } else if head_spur == sf.import {
236 Some(eval_import(args, env, ctx))
237 } else if head_spur == sf.load {
238 Some(eval_load(args, env, ctx))
239 } else if head_spur == sf.module {
240 Some(eval_module(args, env, ctx))
241
242 } else if head_spur == sf.defagent {
244 Some(eval_defagent(args, env, ctx))
245 } else if head_spur == sf.deftool {
246 Some(eval_deftool(args, env, ctx))
247 } else if head_spur == sf.message {
248 Some(eval_message(args, env, ctx))
249 } else if head_spur == sf.prompt {
250 Some(eval_prompt(args, env, ctx))
251 } else {
252 None
253 }
254}
255
256fn eval_quote(args: &[Value]) -> Result<Trampoline, SemaError> {
257 if args.len() != 1 {
258 return Err(SemaError::arity("quote", "1", args.len()));
259 }
260 Ok(Trampoline::Value(args[0].clone()))
261}
262
263fn eval_if(args: &[Value], env: &Env, ctx: &EvalContext) -> Result<Trampoline, SemaError> {
264 if args.len() < 2 || args.len() > 3 {
265 return Err(SemaError::arity("if", "2 or 3", args.len()));
266 }
267 let cond = eval::eval_value(ctx, &args[0], env)?;
268 if cond.is_truthy() {
269 Ok(Trampoline::Eval(args[1].clone(), env.clone()))
270 } else if args.len() == 3 {
271 Ok(Trampoline::Eval(args[2].clone(), env.clone()))
272 } else {
273 Ok(Trampoline::Value(Value::nil()))
274 }
275}
276
277fn eval_cond(args: &[Value], env: &Env, ctx: &EvalContext) -> Result<Trampoline, SemaError> {
278 let sf = special_forms();
279 for clause in args {
280 let items = clause
281 .as_list()
282 .ok_or_else(|| SemaError::eval("cond clause must be a list"))?;
283 if items.is_empty() {
284 return Err(SemaError::eval("cond clause must not be empty"));
285 }
286 let is_else = items[0].as_symbol_spur().is_some_and(|s| s == sf.else_);
288 if is_else || eval::eval_value(ctx, &items[0], env)?.is_truthy() {
289 if items.len() == 1 {
290 return Ok(Trampoline::Value(Value::bool(true)));
291 }
292 for expr in &items[1..items.len() - 1] {
294 eval::eval_value(ctx, expr, env)?;
295 }
296 return Ok(Trampoline::Eval(items.last().unwrap().clone(), env.clone()));
297 }
298 }
299 Ok(Trampoline::Value(Value::nil()))
300}
301
302fn eval_define(args: &[Value], env: &Env, ctx: &EvalContext) -> Result<Trampoline, SemaError> {
303 if args.is_empty() {
304 return Err(SemaError::arity("define", "2+", 0));
305 }
306 if let Some(spur) = args[0].as_symbol_spur() {
307 if args.len() != 2 {
309 return Err(SemaError::arity("define", "2", args.len()));
310 }
311 let val = eval::eval_value(ctx, &args[1], env)?;
312 env.set(spur, val);
313 Ok(Trampoline::Value(Value::nil()))
314 } else if let Some(sig) = args[0].as_list() {
315 if sig.is_empty() {
317 return Err(SemaError::eval("define: empty function signature"));
318 }
319 let name_spur = sig[0]
320 .as_symbol_spur()
321 .ok_or_else(|| SemaError::eval("define: function name must be a symbol"))?;
322 let param_names: Vec<Spur> = sig[1..]
323 .iter()
324 .map(|v| {
325 v.as_symbol_spur()
326 .ok_or_else(|| SemaError::eval("define: parameter must be a symbol"))
327 })
328 .collect::<Result<_, _>>()?;
329 let (params, rest_param) = parse_params(¶m_names);
330 let body = args[1..].to_vec();
331 if body.is_empty() {
332 return Err(SemaError::eval("define: function body cannot be empty"));
333 }
334 let lambda = Value::lambda(Lambda {
335 params,
336 rest_param,
337 body,
338 env: env.clone(),
339 name: Some(name_spur),
340 });
341 env.set(name_spur, lambda);
342 Ok(Trampoline::Value(Value::nil()))
343 } else {
344 Err(SemaError::type_error("symbol or list", args[0].type_name()))
345 }
346}
347
348fn eval_defun(args: &[Value], env: &Env, ctx: &EvalContext) -> Result<Trampoline, SemaError> {
350 if args.len() < 3 {
351 return Err(SemaError::arity("defun", "3+", args.len()));
352 }
353 if args[0].as_symbol_spur().is_none() {
354 return Err(SemaError::type_error("symbol", args[0].type_name()));
355 }
356 let name = args[0].clone();
357 let params = args[1]
358 .as_list_rc()
359 .ok_or_else(|| SemaError::type_error("list", args[1].type_name()))?;
360 let mut sig = vec![name];
362 sig.extend(params.iter().cloned());
363 let mut define_args = vec![Value::list(sig)];
365 define_args.extend_from_slice(&args[2..]);
366 eval_define(&define_args, env, ctx)
367}
368
369fn eval_set(args: &[Value], env: &Env, ctx: &EvalContext) -> Result<Trampoline, SemaError> {
370 if args.len() != 2 {
371 return Err(SemaError::arity("set!", "2", args.len()));
372 }
373 let spur = args[0]
374 .as_symbol_spur()
375 .ok_or_else(|| SemaError::eval("set!: first argument must be a symbol"))?;
376 let val = eval::eval_value(ctx, &args[1], env)?;
377 if !env.set_existing(spur, val) {
378 return Err(SemaError::Unbound(resolve(spur)));
379 }
380 Ok(Trampoline::Value(Value::nil()))
381}
382
383fn eval_lambda(args: &[Value], env: &Env, name: Option<Spur>) -> Result<Trampoline, SemaError> {
384 if args.len() < 2 {
385 return Err(SemaError::arity("lambda", "2+", args.len()));
386 }
387 let param_list = if let Some(params) = args[0].as_list() {
388 params.to_vec()
389 } else if let Some(params) = args[0].as_vector() {
390 params.to_vec()
391 } else {
392 return Err(SemaError::type_error("list or vector", args[0].type_name()));
393 };
394 let param_names: Vec<Spur> = param_list
395 .iter()
396 .map(|v| {
397 v.as_symbol_spur()
398 .ok_or_else(|| SemaError::eval("lambda: parameter must be a symbol"))
399 })
400 .collect::<Result<_, _>>()?;
401 let (params, rest_param) = parse_params(¶m_names);
402 let body = args[1..].to_vec();
403 Ok(Trampoline::Value(Value::lambda(Lambda {
404 params,
405 rest_param,
406 body,
407 env: env.clone(),
408 name,
409 })))
410}
411
412fn eval_let(args: &[Value], env: &Env, ctx: &EvalContext) -> Result<Trampoline, SemaError> {
413 if args.len() < 2 {
414 return Err(SemaError::arity("let", "2+", args.len()));
415 }
416
417 if let Some(loop_name_spur) = args[0].as_symbol_spur() {
419 if args.len() < 3 {
420 return Err(SemaError::arity("named let", "3+", args.len()));
421 }
422 let bindings_list = args[1]
423 .as_list()
424 .ok_or_else(|| SemaError::eval("named let: bindings must be a list"))?;
425
426 let mut params: Vec<Spur> = Vec::new();
427 let mut init_vals = Vec::new();
428 for binding in bindings_list {
429 let pair = binding
430 .as_list()
431 .ok_or_else(|| SemaError::eval("named let: each binding must be a list"))?;
432 if pair.len() != 2 {
433 return Err(SemaError::eval(
434 "named let: each binding must have 2 elements",
435 ));
436 }
437 let pname = pair[0]
438 .as_symbol_spur()
439 .ok_or_else(|| SemaError::eval("named let: binding name must be a symbol"))?;
440 let val = eval::eval_value(ctx, &pair[1], env)?;
441 params.push(pname);
442 init_vals.push(val);
443 }
444
445 let body = args[2..].to_vec();
446 let lambda = Lambda {
447 params: params.clone(),
448 rest_param: None,
449 body,
450 env: env.clone(),
451 name: Some(loop_name_spur),
452 };
453
454 let new_env = Env::with_parent(Rc::new(env.clone()));
456 for (p, v) in params.iter().zip(init_vals.iter()) {
457 new_env.set(*p, v.clone());
458 }
459 new_env.set(
460 loop_name_spur,
461 Value::lambda(Lambda {
462 params: lambda.params.clone(),
463 rest_param: None,
464 body: lambda.body.clone(),
465 env: env.clone(),
466 name: lambda.name,
467 }),
468 );
469
470 let body_ref = &args[2..];
472 for expr in &body_ref[..body_ref.len() - 1] {
473 eval::eval_value(ctx, expr, &new_env)?;
474 }
475 return Ok(Trampoline::Eval(body_ref.last().unwrap().clone(), new_env));
476 }
477
478 let bindings = args[0]
480 .as_list()
481 .ok_or_else(|| SemaError::eval("let: bindings must be a list"))?;
482
483 let new_env = Env::with_parent(Rc::new(env.clone()));
484
485 for binding in bindings {
486 let pair = binding
487 .as_list()
488 .ok_or_else(|| SemaError::eval("let: each binding must be a list"))?;
489 if pair.len() != 2 {
490 return Err(SemaError::eval("let: each binding must have 2 elements"));
491 }
492 let name_spur = pair[0]
493 .as_symbol_spur()
494 .ok_or_else(|| SemaError::eval("let: binding name must be a symbol"))?;
495 let val = eval::eval_value(ctx, &pair[1], env)?;
497 new_env.set(name_spur, val);
498 }
499
500 for expr in &args[1..args.len() - 1] {
502 eval::eval_value(ctx, expr, &new_env)?;
503 }
504 Ok(Trampoline::Eval(args.last().unwrap().clone(), new_env))
505}
506
507fn eval_let_star(args: &[Value], env: &Env, ctx: &EvalContext) -> Result<Trampoline, SemaError> {
508 if args.len() < 2 {
509 return Err(SemaError::arity("let*", "2+", args.len()));
510 }
511 let bindings = args[0]
512 .as_list()
513 .ok_or_else(|| SemaError::eval("let*: bindings must be a list"))?;
514
515 let new_env = Env::with_parent(Rc::new(env.clone()));
516
517 for binding in bindings {
518 let pair = binding
519 .as_list()
520 .ok_or_else(|| SemaError::eval("let*: each binding must be a list"))?;
521 if pair.len() != 2 {
522 return Err(SemaError::eval("let*: each binding must have 2 elements"));
523 }
524 let name_spur = pair[0]
525 .as_symbol_spur()
526 .ok_or_else(|| SemaError::eval("let*: binding name must be a symbol"))?;
527 let val = eval::eval_value(ctx, &pair[1], &new_env)?;
529 new_env.set(name_spur, val);
530 }
531
532 for expr in &args[1..args.len() - 1] {
533 eval::eval_value(ctx, expr, &new_env)?;
534 }
535 Ok(Trampoline::Eval(args.last().unwrap().clone(), new_env))
536}
537
538fn eval_letrec(args: &[Value], env: &Env, ctx: &EvalContext) -> Result<Trampoline, SemaError> {
539 if args.len() < 2 {
540 return Err(SemaError::arity("letrec", "2+", args.len()));
541 }
542 let bindings = args[0]
543 .as_list()
544 .ok_or_else(|| SemaError::eval("letrec: bindings must be a list"))?;
545
546 let new_env = Env::with_parent(Rc::new(env.clone()));
547
548 let mut name_spurs = Vec::new();
550 for binding in bindings {
551 let pair = binding
552 .as_list()
553 .ok_or_else(|| SemaError::eval("letrec: each binding must be a list"))?;
554 if pair.len() != 2 {
555 return Err(SemaError::eval("letrec: each binding must have 2 elements"));
556 }
557 let spur = pair[0]
558 .as_symbol_spur()
559 .ok_or_else(|| SemaError::eval("letrec: binding name must be a symbol"))?;
560 name_spurs.push(spur);
561 new_env.set(spur, Value::nil());
562 }
563
564 for (i, binding) in bindings.iter().enumerate() {
566 let pair = binding.as_list().unwrap();
567 let val = eval::eval_value(ctx, &pair[1], &new_env)?;
568 new_env.set(name_spurs[i], val);
569 }
570
571 for expr in &args[1..args.len() - 1] {
573 eval::eval_value(ctx, expr, &new_env)?;
574 }
575 Ok(Trampoline::Eval(args.last().unwrap().clone(), new_env))
576}
577
578fn eval_begin(args: &[Value], env: &Env, ctx: &EvalContext) -> Result<Trampoline, SemaError> {
579 if args.is_empty() {
580 return Ok(Trampoline::Value(Value::nil()));
581 }
582 for expr in &args[..args.len() - 1] {
583 eval::eval_value(ctx, expr, env)?;
584 }
585 Ok(Trampoline::Eval(args.last().unwrap().clone(), env.clone()))
586}
587
588fn eval_and(args: &[Value], env: &Env, ctx: &EvalContext) -> Result<Trampoline, SemaError> {
589 if args.is_empty() {
590 return Ok(Trampoline::Value(Value::bool(true)));
591 }
592 for expr in &args[..args.len() - 1] {
593 let val = eval::eval_value(ctx, expr, env)?;
594 if !val.is_truthy() {
595 return Ok(Trampoline::Value(val));
596 }
597 }
598 Ok(Trampoline::Eval(args.last().unwrap().clone(), env.clone()))
599}
600
601fn eval_or(args: &[Value], env: &Env, ctx: &EvalContext) -> Result<Trampoline, SemaError> {
602 if args.is_empty() {
603 return Ok(Trampoline::Value(Value::bool(false)));
604 }
605 for expr in &args[..args.len() - 1] {
606 let val = eval::eval_value(ctx, expr, env)?;
607 if val.is_truthy() {
608 return Ok(Trampoline::Value(val));
609 }
610 }
611 Ok(Trampoline::Eval(args.last().unwrap().clone(), env.clone()))
612}
613
614fn eval_when(args: &[Value], env: &Env, ctx: &EvalContext) -> Result<Trampoline, SemaError> {
615 if args.len() < 2 {
616 return Err(SemaError::arity("when", "2+", args.len()));
617 }
618 let cond = eval::eval_value(ctx, &args[0], env)?;
619 if cond.is_truthy() {
620 for expr in &args[1..args.len() - 1] {
621 eval::eval_value(ctx, expr, env)?;
622 }
623 Ok(Trampoline::Eval(args.last().unwrap().clone(), env.clone()))
624 } else {
625 Ok(Trampoline::Value(Value::nil()))
626 }
627}
628
629fn eval_unless(args: &[Value], env: &Env, ctx: &EvalContext) -> Result<Trampoline, SemaError> {
630 if args.len() < 2 {
631 return Err(SemaError::arity("unless", "2+", args.len()));
632 }
633 let cond = eval::eval_value(ctx, &args[0], env)?;
634 if !cond.is_truthy() {
635 for expr in &args[1..args.len() - 1] {
636 eval::eval_value(ctx, expr, env)?;
637 }
638 Ok(Trampoline::Eval(args.last().unwrap().clone(), env.clone()))
639 } else {
640 Ok(Trampoline::Value(Value::nil()))
641 }
642}
643
644fn eval_defmacro(args: &[Value], env: &Env) -> Result<Trampoline, SemaError> {
645 if args.len() < 3 {
646 return Err(SemaError::arity("defmacro", "3+", args.len()));
647 }
648 let name_spur = args[0]
649 .as_symbol_spur()
650 .ok_or_else(|| SemaError::eval("defmacro: name must be a symbol"))?;
651 let param_list = args[1]
652 .as_list()
653 .ok_or_else(|| SemaError::eval("defmacro: params must be a list"))?;
654 let param_names: Vec<Spur> = param_list
655 .iter()
656 .map(|v| {
657 v.as_symbol_spur()
658 .ok_or_else(|| SemaError::eval("defmacro: parameter must be a symbol"))
659 })
660 .collect::<Result<_, _>>()?;
661 let (params, rest_param) = parse_params(¶m_names);
662 let body = args[2..].to_vec();
663
664 let mac = Value::macro_val(Macro {
665 params,
666 rest_param,
667 body,
668 name: name_spur,
669 });
670 env.set(name_spur, mac);
671 Ok(Trampoline::Value(Value::nil()))
672}
673
674fn eval_quasiquote(args: &[Value], env: &Env, ctx: &EvalContext) -> Result<Trampoline, SemaError> {
675 if args.len() != 1 {
676 return Err(SemaError::arity("quasiquote", "1", args.len()));
677 }
678 let result = expand_quasiquote(&args[0], env, ctx)?;
679 Ok(Trampoline::Value(result))
680}
681
682fn expand_quasiquote(val: &Value, env: &Env, ctx: &EvalContext) -> Result<Value, SemaError> {
683 if let Some(items) = val.as_list() {
684 if items.is_empty() {
685 return Ok(val.clone());
686 }
687 if let Some(sym) = items[0].as_symbol() {
689 if sym == "unquote" {
690 if items.len() != 2 {
691 return Err(SemaError::arity("unquote", "1", items.len() - 1));
692 }
693 return eval::eval_value(ctx, &items[1], env);
694 }
695 }
696 let mut result = Vec::new();
698 for item in items.iter() {
699 if let Some(inner) = item.as_list() {
700 if !inner.is_empty() {
701 if let Some(sym) = inner[0].as_symbol() {
702 if sym == "unquote-splicing" {
703 if inner.len() != 2 {
704 return Err(SemaError::arity(
705 "unquote-splicing",
706 "1",
707 inner.len() - 1,
708 ));
709 }
710 let splice_val = eval::eval_value(ctx, &inner[1], env)?;
711 if let Some(splice_items) = splice_val.as_list() {
712 result.extend(splice_items.iter().cloned());
713 } else {
714 return Err(SemaError::type_error("list", splice_val.type_name()));
715 }
716 continue;
717 }
718 }
719 }
720 }
721 result.push(expand_quasiquote(item, env, ctx)?);
722 }
723 Ok(Value::list(result))
724 } else if let Some(items) = val.as_vector() {
725 let mut result = Vec::new();
726 for item in items.iter() {
727 result.push(expand_quasiquote(item, env, ctx)?);
728 }
729 Ok(Value::vector(result))
730 } else {
731 Ok(val.clone())
732 }
733}
734
735fn eval_prompt(args: &[Value], env: &Env, ctx: &EvalContext) -> Result<Trampoline, SemaError> {
736 use sema_core::{Message, Prompt, Role};
737
738 let role_names = ["system", "user", "assistant", "tool"];
739
740 let mut messages = Vec::new();
741 for arg in args {
742 if let Some(items) = arg.as_list() {
744 if !items.is_empty() {
745 if let Some(role_str) = items[0].as_symbol() {
746 if role_names.contains(&role_str.as_str()) {
747 let role = match role_str.as_str() {
748 "system" => Role::System,
749 "user" => Role::User,
750 "assistant" => Role::Assistant,
751 "tool" => Role::Tool,
752 _ => unreachable!(),
753 };
754 let mut content = String::new();
756 for part in &items[1..] {
757 let part_val = eval::eval_value(ctx, part, env)?;
758 if let Some(s) = part_val.as_str() {
759 content.push_str(s);
760 } else {
761 content.push_str(&part_val.to_string());
762 }
763 }
764 messages.push(Message {
765 role,
766 content,
767 images: Vec::new(),
768 });
769 continue;
770 }
771 }
772 }
773 }
774 let val = eval::eval_value(ctx, arg, env)?;
776 if let Some(msg) = val.as_message_rc() {
777 messages.push((*msg).clone());
778 } else {
779 return Err(SemaError::eval(
780 "prompt: expected (role content...) or message value",
781 ));
782 }
783 }
784 Ok(Trampoline::Value(Value::prompt(Prompt { messages })))
785}
786
787fn eval_message(args: &[Value], env: &Env, ctx: &EvalContext) -> Result<Trampoline, SemaError> {
788 use sema_core::{Message, Role};
789
790 if args.len() < 2 {
791 return Err(SemaError::arity("message", "2+", args.len()));
792 }
793 let role_val = eval::eval_value(ctx, &args[0], env)?;
794 let role = if let Some(spur) = role_val.as_keyword_spur() {
795 let s = resolve(spur);
796 match s.as_str() {
797 "system" => Role::System,
798 "user" => Role::User,
799 "assistant" => Role::Assistant,
800 "tool" => Role::Tool,
801 other => return Err(SemaError::eval(format!("message: unknown role '{other}'"))),
802 }
803 } else {
804 return Err(SemaError::type_error("keyword", role_val.type_name()));
805 };
806 let mut content = String::new();
807 for part in &args[1..] {
808 let val = eval::eval_value(ctx, part, env)?;
809 if let Some(s) = val.as_str() {
810 content.push_str(s);
811 } else {
812 content.push_str(&val.to_string());
813 }
814 }
815 Ok(Trampoline::Value(Value::message(Message {
816 role,
817 content,
818 images: Vec::new(),
819 })))
820}
821
822fn eval_deftool(args: &[Value], env: &Env, ctx: &EvalContext) -> Result<Trampoline, SemaError> {
824 if args.len() < 4 {
825 return Err(SemaError::arity("deftool", "4", args.len()));
826 }
827 let name = args[0]
828 .as_symbol()
829 .ok_or_else(|| SemaError::eval("deftool: name must be a symbol"))?
830 .to_string();
831 let description = eval::eval_value(ctx, &args[1], env)?;
832 let description = description
833 .as_str()
834 .ok_or_else(|| SemaError::type_error("string", description.type_name()))?
835 .to_string();
836 let parameters = eval::eval_value(ctx, &args[2], env)?;
837 let handler = eval::eval_value(ctx, &args[3], env)?;
838
839 let tool = Value::tool_def(ToolDefinition {
840 name: name.clone(),
841 description,
842 parameters,
843 handler,
844 });
845 env.set(intern(&name), tool.clone());
846 Ok(Trampoline::Value(tool))
847}
848
849fn eval_defagent(args: &[Value], env: &Env, ctx: &EvalContext) -> Result<Trampoline, SemaError> {
851 if args.len() != 2 {
852 return Err(SemaError::arity("defagent", "2", args.len()));
853 }
854 let name = args[0]
855 .as_symbol()
856 .ok_or_else(|| SemaError::eval("defagent: name must be a symbol"))?
857 .to_string();
858 let opts = eval::eval_value(ctx, &args[1], env)?;
859 let opts_map = opts
860 .as_map_rc()
861 .ok_or_else(|| SemaError::type_error("map", opts.type_name()))?;
862
863 let system = opts_map
864 .get(&Value::keyword("system"))
865 .and_then(|v| v.as_str().map(|s| s.to_string()))
866 .unwrap_or_default();
867
868 let tools = opts_map
869 .get(&Value::keyword("tools"))
870 .map(|v| {
871 if let Some(l) = v.as_list() {
872 l.to_vec()
873 } else if let Some(v) = v.as_vector() {
874 v.to_vec()
875 } else {
876 vec![]
877 }
878 })
879 .unwrap_or_default();
880
881 let max_turns = opts_map
882 .get(&Value::keyword("max-turns"))
883 .and_then(|v| v.as_int())
884 .unwrap_or(10) as usize;
885
886 let model = opts_map
887 .get(&Value::keyword("model"))
888 .and_then(|v| v.as_str().map(|s| s.to_string()))
889 .unwrap_or_default();
890
891 let agent = Value::agent(Agent {
892 name: name.clone(),
893 system,
894 tools,
895 max_turns,
896 model,
897 });
898 env.set(intern(&name), agent.clone());
899 Ok(Trampoline::Value(agent))
900}
901
902fn eval_throw(args: &[Value], env: &Env, ctx: &EvalContext) -> Result<Trampoline, SemaError> {
904 if args.len() != 1 {
905 return Err(SemaError::arity("throw", "1", args.len()));
906 }
907 let val = eval::eval_value(ctx, &args[0], env)?;
908 Err(SemaError::UserException(val))
909}
910
911fn eval_try(args: &[Value], env: &Env, ctx: &EvalContext) -> Result<Trampoline, SemaError> {
913 let sf = special_forms();
914 if args.is_empty() {
915 return Err(SemaError::arity("try", "1+", 0));
916 }
917
918 let last = &args[args.len() - 1];
920 let catch_form = last
921 .as_list()
922 .ok_or_else(|| SemaError::eval("try: last argument must be (catch var handler...)"))?;
923 if catch_form.is_empty() {
924 return Err(SemaError::eval("try: catch form is empty"));
925 }
926 let is_catch = catch_form[0]
927 .as_symbol_spur()
928 .is_some_and(|s| s == sf.catch);
929 if !is_catch {
930 return Err(SemaError::eval(
931 "try: last argument must be (catch var handler...)",
932 ));
933 }
934 if catch_form.len() < 3 {
935 return Err(SemaError::eval("try: catch needs (catch var handler...)"));
936 }
937 let catch_var = catch_form[1]
938 .as_symbol()
939 .ok_or_else(|| SemaError::eval("try: catch variable must be a symbol"))?
940 .to_string();
941 let handler_body = &catch_form[2..];
942
943 let body = &args[..args.len() - 1];
945 let body_result: Result<Value, SemaError> = (|| {
946 let mut result = Value::nil();
947 for expr in body {
948 result = eval::eval_value(ctx, expr, env)?;
949 }
950 Ok(result)
951 })();
952
953 match body_result {
954 Ok(val) => Ok(Trampoline::Value(val)),
955 Err(err) => {
956 let err_val = error_to_value(&err);
958 let catch_env = Env::with_parent(Rc::new(env.clone()));
959 catch_env.set(intern(&catch_var), err_val);
960 for expr in &handler_body[..handler_body.len() - 1] {
962 eval::eval_value(ctx, expr, &catch_env)?;
963 }
964 Ok(Trampoline::Eval(
965 handler_body.last().unwrap().clone(),
966 catch_env,
967 ))
968 }
969 }
970}
971
972fn error_to_value(err: &SemaError) -> Value {
974 use std::collections::BTreeMap;
975
976 let trace = err.stack_trace().cloned();
978 let inner = err.inner();
979
980 let mut map = BTreeMap::new();
981 match inner {
982 SemaError::Reader { message, span } => {
983 map.insert(Value::keyword("type"), Value::keyword("reader"));
984 map.insert(
985 Value::keyword("message"),
986 Value::string(&format!("{message} at {span}")),
987 );
988 }
989 SemaError::Eval(msg) => {
990 map.insert(Value::keyword("type"), Value::keyword("eval"));
991 map.insert(Value::keyword("message"), Value::string(msg));
992 }
993 SemaError::Type { expected, got } => {
994 map.insert(Value::keyword("type"), Value::keyword("type-error"));
995 map.insert(
996 Value::keyword("message"),
997 Value::string(&format!("expected {expected}, got {got}")),
998 );
999 map.insert(Value::keyword("expected"), Value::string(expected));
1000 map.insert(Value::keyword("got"), Value::string(got));
1001 }
1002 SemaError::Arity {
1003 name,
1004 expected,
1005 got,
1006 } => {
1007 map.insert(Value::keyword("type"), Value::keyword("arity"));
1008 map.insert(
1009 Value::keyword("message"),
1010 Value::string(&format!("{name} expects {expected} args, got {got}")),
1011 );
1012 }
1013 SemaError::Unbound(name) => {
1014 map.insert(Value::keyword("type"), Value::keyword("unbound"));
1015 map.insert(
1016 Value::keyword("message"),
1017 Value::string(&format!("Unbound variable: {name}")),
1018 );
1019 map.insert(Value::keyword("name"), Value::string(name));
1020 }
1021 SemaError::Llm(msg) => {
1022 map.insert(Value::keyword("type"), Value::keyword("llm"));
1023 map.insert(Value::keyword("message"), Value::string(msg));
1024 }
1025 SemaError::Io(msg) => {
1026 map.insert(Value::keyword("type"), Value::keyword("io"));
1027 map.insert(Value::keyword("message"), Value::string(msg));
1028 }
1029 SemaError::PermissionDenied {
1030 function,
1031 capability,
1032 } => {
1033 map.insert(Value::keyword("type"), Value::keyword("permission-denied"));
1034 map.insert(
1035 Value::keyword("message"),
1036 Value::string(&format!(
1037 "Permission denied: {function} requires '{capability}' capability"
1038 )),
1039 );
1040 map.insert(Value::keyword("function"), Value::string(function));
1041 map.insert(Value::keyword("capability"), Value::string(capability));
1042 }
1043 SemaError::UserException(val) => {
1044 map.insert(Value::keyword("type"), Value::keyword("user"));
1045 map.insert(Value::keyword("message"), Value::string(&val.to_string()));
1046 map.insert(Value::keyword("value"), val.clone());
1047 }
1048 SemaError::PathDenied { function, path } => {
1049 map.insert(Value::keyword("type"), Value::keyword("permission-denied"));
1050 map.insert(
1051 Value::keyword("message"),
1052 Value::string(&format!(
1053 "Permission denied: {function} — path '{path}' is outside allowed directories"
1054 )),
1055 );
1056 map.insert(Value::keyword("function"), Value::string(function));
1057 map.insert(Value::keyword("path"), Value::string(path));
1058 }
1059 SemaError::WithTrace { .. } => unreachable!("inner() already unwraps WithTrace"),
1060 SemaError::WithContext { .. } => unreachable!("inner() already unwraps WithContext"),
1061 }
1062
1063 if let Some(st) = trace {
1065 let frames: Vec<Value> =
1066 st.0.iter()
1067 .map(|frame| {
1068 let mut fm = BTreeMap::new();
1069 fm.insert(Value::keyword("name"), Value::string(&frame.name));
1070 if let Some(ref file) = frame.file {
1071 fm.insert(
1072 Value::keyword("file"),
1073 Value::string(&file.to_string_lossy()),
1074 );
1075 }
1076 if let Some(ref span) = frame.span {
1077 fm.insert(Value::keyword("line"), Value::int(span.line as i64));
1078 fm.insert(Value::keyword("col"), Value::int(span.col as i64));
1079 }
1080 Value::map(fm)
1081 })
1082 .collect();
1083 map.insert(Value::keyword("stack-trace"), Value::list(frames));
1084 }
1085
1086 Value::map(map)
1087}
1088
1089fn eval_module(args: &[Value], env: &Env, ctx: &EvalContext) -> Result<Trampoline, SemaError> {
1091 let sf = special_forms();
1092 if args.len() < 2 {
1093 return Err(SemaError::arity("module", "2+", args.len()));
1094 }
1095 let _name = args[0]
1097 .as_symbol()
1098 .ok_or_else(|| SemaError::eval("module: name must be a symbol"))?;
1099
1100 let export_list = args[1]
1102 .as_list()
1103 .ok_or_else(|| SemaError::eval("module: second argument must be (export sym1 sym2 ...)"))?;
1104 if export_list.is_empty() || export_list[0].as_symbol_spur() != Some(sf.export) {
1105 return Err(SemaError::eval(
1106 "module: second argument must start with 'export'",
1107 ));
1108 }
1109 let export_names: Vec<String> = export_list[1..]
1110 .iter()
1111 .map(|v| {
1112 v.as_symbol()
1113 .map(|s| s.to_string())
1114 .ok_or_else(|| SemaError::eval("module: export names must be symbols"))
1115 })
1116 .collect::<Result<_, _>>()?;
1117
1118 ctx.set_module_exports(export_names);
1119
1120 let body = &args[2..];
1122 if body.is_empty() {
1123 return Ok(Trampoline::Value(Value::nil()));
1124 }
1125 for expr in &body[..body.len() - 1] {
1126 eval::eval_value(ctx, expr, env)?;
1127 }
1128 Ok(Trampoline::Eval(body.last().unwrap().clone(), env.clone()))
1129}
1130
1131fn eval_import(args: &[Value], env: &Env, ctx: &EvalContext) -> Result<Trampoline, SemaError> {
1133 if args.is_empty() {
1134 return Err(SemaError::arity("import", "1+", 0));
1135 }
1136 let path_val = eval::eval_value(ctx, &args[0], env)?;
1137 let path_str = path_val
1138 .as_str()
1139 .ok_or_else(|| SemaError::type_error("string", path_val.type_name()))?;
1140
1141 let resolved = if std::path::Path::new(path_str).is_absolute() {
1143 std::path::PathBuf::from(path_str)
1144 } else if let Some(dir) = ctx.current_file_dir() {
1145 dir.join(path_str)
1146 } else {
1147 std::path::PathBuf::from(path_str)
1148 };
1149
1150 let selective: Vec<String> = args[1..]
1152 .iter()
1153 .map(|v| {
1154 v.as_symbol()
1155 .map(|s| s.to_string())
1156 .ok_or_else(|| SemaError::eval("import: selective names must be symbols"))
1157 })
1158 .collect::<Result<_, _>>()?;
1159
1160 if let Some(cached) = ctx.get_cached_module(&resolved) {
1162 copy_exports_to_env(&cached, &selective, env)?;
1163 return Ok(Trampoline::Value(Value::nil()));
1164 }
1165
1166 let canonical = resolved
1167 .canonicalize()
1168 .map_err(|e| SemaError::Io(format!("import {path_str}: {e}")))?;
1169
1170 if let Some(cached) = ctx.get_cached_module(&canonical) {
1172 copy_exports_to_env(&cached, &selective, env)?;
1173 return Ok(Trampoline::Value(Value::nil()));
1174 }
1175
1176 ctx.begin_module_load(&canonical)?;
1177
1178 let load_result: Result<std::collections::BTreeMap<String, Value>, SemaError> = (|| {
1179 let content = std::fs::read_to_string(&canonical)
1181 .map_err(|e| SemaError::Io(format!("import {path_str}: {e}")))?;
1182 let (exprs, spans) = sema_reader::read_many_with_spans(&content)?;
1183 ctx.merge_span_table(spans);
1184
1185 let module_env = eval::create_module_env(env);
1186 ctx.push_file_path(canonical.clone());
1187 ctx.clear_module_exports();
1188
1189 let eval_result = (|| {
1190 for expr in &exprs {
1191 eval::eval_value(ctx, expr, &module_env)?;
1192 }
1193 Ok(())
1194 })();
1195
1196 ctx.pop_file_path();
1197
1198 let declared = ctx.take_module_exports();
1200 eval_result?;
1201
1202 Ok(collect_module_exports(&module_env, declared.as_deref()))
1203 })();
1204
1205 ctx.end_module_load(&canonical);
1206 let exports = load_result?;
1207
1208 ctx.cache_module(canonical, exports.clone());
1210
1211 copy_exports_to_env(&exports, &selective, env)?;
1213
1214 Ok(Trampoline::Value(Value::nil()))
1215}
1216
1217fn collect_module_exports(
1219 module_env: &Env,
1220 declared: Option<&[String]>,
1221) -> std::collections::BTreeMap<String, Value> {
1222 let bindings = module_env.bindings.borrow();
1223 match declared {
1224 Some(names) => {
1225 let mut exports = std::collections::BTreeMap::new();
1226 for name in names {
1227 let spur = intern(name);
1228 if let Some(val) = bindings.get(&spur) {
1229 exports.insert(name.clone(), val.clone());
1230 }
1231 }
1232 exports
1233 }
1234 None => {
1235 let mut exports = std::collections::BTreeMap::new();
1236 for (spur, val) in bindings.iter() {
1237 exports.insert(resolve(*spur), val.clone());
1238 }
1239 exports
1240 }
1241 }
1242}
1243
1244fn copy_exports_to_env(
1246 exports: &std::collections::BTreeMap<String, Value>,
1247 selective: &[String],
1248 env: &Env,
1249) -> Result<(), SemaError> {
1250 if selective.is_empty() {
1251 for (name, val) in exports {
1252 env.set(intern(name), val.clone());
1253 }
1254 } else {
1255 for name in selective {
1256 let val = exports.get(name).ok_or_else(|| {
1257 SemaError::eval(format!("import: module does not export '{name}'"))
1258 })?;
1259 env.set(intern(name), val.clone());
1260 }
1261 }
1262 Ok(())
1263}
1264
1265fn eval_load(args: &[Value], env: &Env, ctx: &EvalContext) -> Result<Trampoline, SemaError> {
1267 if args.len() != 1 {
1268 return Err(SemaError::arity("load", "1", args.len()));
1269 }
1270 ctx.sandbox.check(sema_core::Caps::FS_READ, "load")?;
1271 let path_val = eval::eval_value(ctx, &args[0], env)?;
1272 let path_str = path_val
1273 .as_str()
1274 .ok_or_else(|| SemaError::type_error("string", path_val.type_name()))?;
1275
1276 let resolved = if std::path::Path::new(path_str).is_absolute() {
1278 std::path::PathBuf::from(path_str)
1279 } else if let Some(dir) = ctx.current_file_dir() {
1280 dir.join(path_str)
1281 } else {
1282 std::path::PathBuf::from(path_str)
1283 };
1284
1285 let content = std::fs::read_to_string(&resolved)
1286 .map_err(|e| SemaError::Io(format!("load {}: {e}", resolved.display())))?;
1287 let (exprs, spans) = sema_reader::read_many_with_spans(&content)?;
1288 ctx.merge_span_table(spans);
1289
1290 let canonical = resolved.canonicalize().ok();
1292 if let Some(path) = canonical.clone() {
1293 ctx.push_file_path(path);
1294 }
1295
1296 let mut result = Value::nil();
1297 let eval_result = (|| {
1298 for expr in &exprs {
1299 result = eval::eval_value(ctx, expr, env)?;
1300 }
1301 Ok(result.clone())
1302 })();
1303
1304 if canonical.is_some() {
1306 ctx.pop_file_path();
1307 }
1308
1309 eval_result?;
1310 Ok(Trampoline::Value(result))
1311}
1312
1313fn eval_case(args: &[Value], env: &Env, ctx: &EvalContext) -> Result<Trampoline, SemaError> {
1315 let sf = special_forms();
1316 if args.len() < 2 {
1317 return Err(SemaError::arity("case", "2+", args.len()));
1318 }
1319 let key = eval::eval_value(ctx, &args[0], env)?;
1320
1321 for clause in &args[1..] {
1322 let items = clause
1323 .as_list()
1324 .ok_or_else(|| SemaError::eval("case: clause must be a list"))?;
1325 if items.is_empty() {
1326 return Err(SemaError::eval("case: clause must not be empty"));
1327 }
1328 let is_else = items[0].as_symbol_spur() == Some(sf.else_);
1330 if is_else {
1331 if items.len() == 1 {
1332 return Ok(Trampoline::Value(Value::nil()));
1333 }
1334 for expr in &items[1..items.len() - 1] {
1335 eval::eval_value(ctx, expr, env)?;
1336 }
1337 return Ok(Trampoline::Eval(items.last().unwrap().clone(), env.clone()));
1338 }
1339 let datums = items[0]
1341 .as_list()
1342 .ok_or_else(|| SemaError::eval("case: datums must be a list"))?;
1343 if datums.contains(&key) {
1344 if items.len() == 1 {
1345 return Ok(Trampoline::Value(Value::nil()));
1346 }
1347 for expr in &items[1..items.len() - 1] {
1348 eval::eval_value(ctx, expr, env)?;
1349 }
1350 return Ok(Trampoline::Eval(items.last().unwrap().clone(), env.clone()));
1351 }
1352 }
1353 Ok(Trampoline::Value(Value::nil()))
1354}
1355
1356fn eval_eval(args: &[Value], env: &Env, ctx: &EvalContext) -> Result<Trampoline, SemaError> {
1358 if args.len() != 1 {
1359 return Err(SemaError::arity("eval", "1", args.len()));
1360 }
1361 let expr = eval::eval_value(ctx, &args[0], env)?;
1362 Ok(Trampoline::Eval(expr, env.clone()))
1363}
1364
1365fn eval_macroexpand(args: &[Value], env: &Env, ctx: &EvalContext) -> Result<Trampoline, SemaError> {
1367 if args.len() != 1 {
1368 return Err(SemaError::arity("macroexpand", "1", args.len()));
1369 }
1370 let form = eval::eval_value(ctx, &args[0], env)?;
1371 if let Some(items) = form.as_list() {
1373 if !items.is_empty() {
1374 if let Some(spur) = items[0].as_symbol_spur() {
1375 if let Some(mac_val) = env.get(spur) {
1376 if let Some(mac) = mac_val.as_macro_rc() {
1377 let expanded = eval::apply_macro(ctx, &mac, &items[1..], env)?;
1378 return Ok(Trampoline::Value(expanded));
1379 }
1380 }
1381 }
1382 }
1383 }
1384 Ok(Trampoline::Value(form))
1386}
1387
1388fn eval_do(args: &[Value], env: &Env, ctx: &EvalContext) -> Result<Trampoline, SemaError> {
1390 if args.len() < 2 {
1391 return Err(SemaError::arity("do", "2+", args.len()));
1392 }
1393
1394 let bindings = args[0]
1395 .as_list()
1396 .ok_or_else(|| SemaError::eval("do: bindings must be a list"))?;
1397 let test_clause = args[1]
1398 .as_list()
1399 .ok_or_else(|| SemaError::eval("do: test clause must be a list"))?;
1400 if test_clause.is_empty() {
1401 return Err(SemaError::eval("do: test clause must not be empty"));
1402 }
1403 let body = &args[2..];
1404
1405 let mut var_names = Vec::new();
1406 let mut step_exprs: Vec<Option<Value>> = Vec::new();
1407
1408 let loop_env = Env::with_parent(Rc::new(env.clone()));
1409
1410 for binding in bindings {
1411 let parts = binding
1412 .as_list()
1413 .ok_or_else(|| SemaError::eval("do: each binding must be a list"))?;
1414 if parts.len() < 2 || parts.len() > 3 {
1415 return Err(SemaError::eval(
1416 "do: binding must be (var init) or (var init step)",
1417 ));
1418 }
1419 let name_spur = parts[0]
1420 .as_symbol_spur()
1421 .ok_or_else(|| SemaError::eval("do: variable name must be a symbol"))?;
1422 let init_val = eval::eval_value(ctx, &parts[1], env)?;
1423 let step = if parts.len() == 3 {
1424 Some(parts[2].clone())
1425 } else {
1426 None
1427 };
1428 loop_env.set(name_spur, init_val);
1429 var_names.push(name_spur);
1430 step_exprs.push(step);
1431 }
1432
1433 let mut new_vals: Vec<Option<Value>> = vec![None; var_names.len()];
1434 loop {
1435 let test_result = eval::eval_value(ctx, &test_clause[0], &loop_env)?;
1436 if test_result.is_truthy() {
1437 if test_clause.len() == 1 {
1438 return Ok(Trampoline::Value(Value::nil()));
1439 }
1440 for expr in &test_clause[1..test_clause.len() - 1] {
1441 eval::eval_value(ctx, expr, &loop_env)?;
1442 }
1443 return Ok(Trampoline::Eval(
1444 test_clause.last().unwrap().clone(),
1445 loop_env,
1446 ));
1447 }
1448
1449 for expr in body {
1450 eval::eval_value(ctx, expr, &loop_env)?;
1451 }
1452
1453 for (i, step) in step_exprs.iter().enumerate() {
1455 new_vals[i] = match step {
1456 Some(expr) => Some(eval::eval_value(ctx, expr, &loop_env)?),
1457 None => None,
1458 };
1459 }
1460
1461 for (i, name) in var_names.iter().enumerate() {
1462 if let Some(val) = new_vals[i].take() {
1463 loop_env.set(*name, val);
1464 }
1465 }
1466 }
1467}
1468
1469fn eval_delay(args: &[Value], env: &Env) -> Result<Trampoline, SemaError> {
1471 if args.len() != 1 {
1472 return Err(SemaError::arity("delay", "1", args.len()));
1473 }
1474 let lambda = Value::lambda(Lambda {
1475 params: vec![],
1476 rest_param: None,
1477 body: vec![args[0].clone()],
1478 env: env.clone(),
1479 name: None,
1480 });
1481 let thunk = Thunk {
1482 body: lambda,
1483 forced: std::cell::RefCell::new(None),
1484 };
1485 Ok(Trampoline::Value(Value::thunk(thunk)))
1486}
1487
1488fn eval_force(args: &[Value], env: &Env, ctx: &EvalContext) -> Result<Trampoline, SemaError> {
1490 if args.len() != 1 {
1491 return Err(SemaError::arity("force", "1", args.len()));
1492 }
1493 let val = eval::eval_value(ctx, &args[0], env)?;
1494 if let Some(thunk) = val.as_thunk_rc() {
1495 if let Some(cached) = &*thunk.forced.borrow() {
1496 return Ok(Trampoline::Value(cached.clone()));
1497 }
1498 let result = if let Some(lambda) = thunk.body.as_lambda_rc() {
1499 let thunk_env = Env::with_parent(Rc::new(lambda.env.clone()));
1500 let mut res = Value::nil();
1501 for expr in &lambda.body {
1502 res = eval::eval_value(ctx, expr, &thunk_env)?;
1503 }
1504 res
1505 } else {
1506 eval::eval_value(ctx, &thunk.body, env)?
1507 };
1508 *thunk.forced.borrow_mut() = Some(result.clone());
1509 Ok(Trampoline::Value(result))
1510 } else {
1511 Ok(Trampoline::Value(val))
1512 }
1513}
1514
1515fn eval_define_record_type(args: &[Value], env: &Env) -> Result<Trampoline, SemaError> {
1517 if args.len() < 3 {
1518 return Err(SemaError::eval(
1519 "define-record-type: requires at least type name, constructor, and predicate",
1520 ));
1521 }
1522
1523 let type_name = args[0]
1524 .as_symbol()
1525 .ok_or_else(|| SemaError::eval("define-record-type: type name must be a symbol"))?;
1526 let type_tag = intern(&type_name);
1527
1528 let ctor_spec = args[1]
1529 .as_list()
1530 .ok_or_else(|| SemaError::eval("define-record-type: constructor spec must be a list"))?;
1531 if ctor_spec.is_empty() {
1532 return Err(SemaError::eval(
1533 "define-record-type: constructor spec must have a name",
1534 ));
1535 }
1536 let ctor_name = ctor_spec[0]
1537 .as_symbol()
1538 .ok_or_else(|| SemaError::eval("define-record-type: constructor name must be a symbol"))?;
1539 let field_names: Vec<String> = ctor_spec[1..]
1540 .iter()
1541 .map(|v| {
1542 v.as_symbol()
1543 .ok_or_else(|| SemaError::eval("define-record-type: field name must be a symbol"))
1544 })
1545 .collect::<Result<_, _>>()?;
1546 let field_count = field_names.len();
1547
1548 let pred_name = args[2]
1549 .as_symbol()
1550 .ok_or_else(|| SemaError::eval("define-record-type: predicate must be a symbol"))?;
1551
1552 let ctor_name_clone = ctor_name.clone();
1553 env.set_str(
1554 &ctor_name,
1555 Value::native_fn(sema_core::NativeFn::simple(
1556 ctor_name.clone(),
1557 move |args: &[Value]| {
1558 if args.len() != field_count {
1559 return Err(SemaError::arity(
1560 &ctor_name_clone,
1561 field_count.to_string(),
1562 args.len(),
1563 ));
1564 }
1565 Ok(Value::record(Record {
1566 type_tag,
1567 fields: args.to_vec(),
1568 }))
1569 },
1570 )),
1571 );
1572
1573 let pred_name_for_closure = pred_name.clone();
1574 let pred_name_for_set = pred_name.clone();
1575 env.set_str(
1576 &pred_name_for_set,
1577 Value::native_fn(sema_core::NativeFn::simple(
1578 pred_name,
1579 move |args: &[Value]| {
1580 if args.len() != 1 {
1581 return Err(SemaError::arity(&pred_name_for_closure, "1", args.len()));
1582 }
1583 Ok(Value::bool(
1584 args[0].as_record().is_some_and(|r| r.type_tag == type_tag),
1585 ))
1586 },
1587 )),
1588 );
1589
1590 for field_spec_val in &args[3..] {
1591 let field_spec = field_spec_val
1592 .as_list()
1593 .ok_or_else(|| SemaError::eval("define-record-type: field spec must be a list"))?;
1594 if field_spec.len() < 2 {
1595 return Err(SemaError::eval(
1596 "define-record-type: field spec must have at least (field-name accessor)",
1597 ));
1598 }
1599
1600 let field_name = field_spec[0]
1601 .as_symbol()
1602 .ok_or_else(|| SemaError::eval("define-record-type: field name must be a symbol"))?;
1603
1604 let field_idx = field_names
1605 .iter()
1606 .position(|n| n == &field_name)
1607 .ok_or_else(|| {
1608 SemaError::eval(format!(
1609 "define-record-type: field '{field_name}' not in constructor"
1610 ))
1611 })?;
1612
1613 let accessor_name = field_spec[1]
1614 .as_symbol()
1615 .ok_or_else(|| SemaError::eval("define-record-type: accessor must be a symbol"))?;
1616
1617 let accessor_name_for_closure = accessor_name.clone();
1618 let accessor_name_for_set = accessor_name.clone();
1619 let type_name_for_err = type_name.clone();
1620 env.set_str(
1621 &accessor_name_for_set,
1622 Value::native_fn(sema_core::NativeFn::simple(
1623 accessor_name,
1624 move |args: &[Value]| {
1625 if args.len() != 1 {
1626 return Err(SemaError::arity(
1627 &accessor_name_for_closure,
1628 "1",
1629 args.len(),
1630 ));
1631 }
1632 match args[0].as_record() {
1633 Some(r) if r.type_tag == type_tag => Ok(r.fields[field_idx].clone()),
1634 _ => Err(SemaError::type_error(
1635 &type_name_for_err,
1636 args[0].type_name(),
1637 )),
1638 }
1639 },
1640 )),
1641 );
1642 }
1643
1644 Ok(Trampoline::Value(Value::nil()))
1645}
1646
1647fn parse_params(names: &[Spur]) -> (Vec<Spur>, Option<Spur>) {
1649 let dot = intern(".");
1650 if let Some(pos) = names.iter().position(|s| *s == dot) {
1651 let params = names[..pos].to_vec();
1652 let rest = if pos + 1 < names.len() {
1653 Some(names[pos + 1])
1654 } else {
1655 None
1656 };
1657 (params, rest)
1658 } else {
1659 (names.to_vec(), None)
1660 }
1661}