use std::cell::RefCell;
use std::collections::HashSet;
use std::rc::Rc;
use sema_core::{
intern, resolve, Env, EvalContext, Macro, MultiMethod, NativeFn, SemaError, Spur, Thunk, Value,
ValueView,
};
use crate::special_forms;
pub enum Trampoline {
Value(Value),
Eval(Value, Env),
}
pub type EvalResult = Result<Value, SemaError>;
pub fn create_module_env(env: &Env) -> Env {
let mut current = env.clone();
loop {
let parent = current.parent.clone();
match parent {
Some(p) => current = (*p).clone(),
None => break,
}
}
Env::with_parent(Rc::new(current))
}
fn collect_native_names(env: &Env) -> HashSet<Spur> {
env.all_names()
.into_iter()
.filter(|&spur| env.get(spur).is_some_and(|v| v.is_native_fn()))
.collect()
}
pub struct Interpreter {
pub global_env: Rc<Env>,
pub ctx: EvalContext,
}
impl Default for Interpreter {
fn default() -> Self {
Self::new()
}
}
impl Interpreter {
pub fn new() -> Self {
let env = Env::new();
let ctx = EvalContext::new();
sema_core::set_eval_callback(&ctx, eval_value_vm);
sema_core::set_call_callback(&ctx, call_value);
sema_stdlib::register_stdlib(&env, &sema_core::Sandbox::allow_all());
#[cfg(not(target_arch = "wasm32"))]
{
sema_llm::builtins::reset_runtime_state();
sema_llm::builtins::register_llm_builtins(&env, &sema_core::Sandbox::allow_all());
sema_llm::builtins::set_eval_callback(eval_value_vm);
}
let global_env = Rc::new(env);
register_vm_delegates(&global_env);
load_prelude(&ctx, &global_env);
Interpreter { global_env, ctx }
}
pub fn new_with_sandbox(sandbox: &sema_core::Sandbox) -> Self {
let env = Env::new();
let ctx = EvalContext::new_with_sandbox(sandbox.clone());
sema_core::set_eval_callback(&ctx, eval_value_vm);
sema_core::set_call_callback(&ctx, call_value);
sema_stdlib::register_stdlib(&env, sandbox);
#[cfg(not(target_arch = "wasm32"))]
{
sema_llm::builtins::reset_runtime_state();
sema_llm::builtins::register_llm_builtins(&env, sandbox);
sema_llm::builtins::set_eval_callback(eval_value_vm);
}
let global_env = Rc::new(env);
register_vm_delegates(&global_env);
load_prelude(&ctx, &global_env);
Interpreter { global_env, ctx }
}
pub fn eval(&self, expr: &Value) -> EvalResult {
self.eval_in_global(expr)
}
pub fn eval_str(&self, input: &str) -> EvalResult {
self.eval_str_in_global(input)
}
pub fn eval_in_global(&self, expr: &Value) -> EvalResult {
self.run_exprs_on_vm(std::slice::from_ref(expr), &self.global_env)
}
pub fn eval_str_in_global(&self, input: &str) -> EvalResult {
let (exprs, spans) = sema_reader::read_many_with_spans(input)?;
self.ctx.merge_span_table(spans);
if exprs.is_empty() {
return Ok(Value::nil());
}
self.run_exprs_on_vm(&exprs, &self.global_env)
}
pub fn eval_str_compiled(&self, input: &str) -> EvalResult {
let (exprs, spans) = sema_reader::read_many_with_spans(input)?;
self.ctx.merge_span_table(spans);
if exprs.is_empty() {
return Ok(Value::nil());
}
self.run_exprs_on_vm(&exprs, &self.global_env)
}
fn run_exprs_on_vm(&self, exprs: &[Value], globals: &Rc<Env>) -> EvalResult {
let mut expanded = Vec::with_capacity(exprs.len());
for expr in exprs {
expanded.push(expand_for_vm_in(&self.ctx, globals, expr)?);
}
let known_natives = collect_native_names(globals);
let prog = sema_vm::compile_program(&expanded, Some(known_natives))?;
let mut vm = sema_vm::VM::new(
globals.clone(),
prog.functions,
&prog.native_table,
prog.main_cache_slots,
)?;
sema_vm::init_scheduler(self.global_env.clone(), prog.native_table.clone());
self.ctx.eval_steps.set(0);
vm.execute(prog.closure, &self.ctx)
}
pub fn compile_to_bytecode(&self, input: &str) -> Result<sema_vm::CompileResult, SemaError> {
let (exprs, spans) = sema_reader::read_many_with_spans(input)?;
self.ctx.merge_span_table(spans);
let mut expanded = Vec::new();
for expr in &exprs {
let exp = self.expand_for_vm(expr)?;
if !exp.is_nil() {
expanded.push(exp);
}
}
if expanded.is_empty() {
expanded.push(Value::nil());
}
let prog = sema_vm::compile_program(&expanded, None)?;
Ok(sema_vm::CompileResult::new(
prog.closure.func.chunk.clone(),
prog.functions.iter().map(|f| (**f).clone()).collect(),
))
}
pub fn expand_for_vm(&self, expr: &Value) -> EvalResult {
expand_for_vm_in(&self.ctx, &self.global_env, expr)
}
}
pub fn expand_for_vm_in(ctx: &EvalContext, env: &Env, expr: &Value) -> EvalResult {
if let Some(items) = expr.as_list() {
if let Some(s) = items.first().and_then(|v| v.as_symbol_spur()) {
let name = resolve(s);
if name == "defmacro" {
register_defmacro(items, env)?;
return Ok(Value::nil());
}
if name == "begin" || name == "progn" {
let mut new_items = vec![Value::symbol_from_spur(s)];
let mut changed = false;
for item in &items[1..] {
let expanded = expand_for_vm_in(ctx, env, item)?;
if expanded.raw_bits() != item.raw_bits() {
changed = true;
}
new_items.push(expanded);
}
if !changed {
return Ok(expr.clone());
}
return Ok(Value::list(new_items));
}
}
}
expand_macros_in(ctx, env, expr)
}
fn expand_macros_in(ctx: &EvalContext, env: &Env, expr: &Value) -> EvalResult {
if let Some(items) = expr.as_list() {
if !items.is_empty() {
if let Some(s) = items.first().and_then(|v| v.as_symbol_spur()) {
let name = resolve(s);
if name == "quote" {
return Ok(expr.clone());
}
if let Some(mac_val) = env.get(s) {
if let Some(mac) = mac_val.as_macro_rc() {
let expanded = apply_macro_vm(ctx, &mac, &items[1..], env)?;
return expand_macros_in(ctx, env, &expanded);
}
}
}
let expanded: Vec<Value> = items
.iter()
.map(|v| expand_macros_in(ctx, env, v))
.collect::<Result<_, _>>()?;
let changed = expanded
.iter()
.zip(items.iter())
.any(|(a, b)| a.raw_bits() != b.raw_bits());
if !changed {
return Ok(expr.clone());
}
return Ok(Value::list(expanded));
}
}
Ok(expr.clone())
}
pub fn eval_module_body_vm(
ctx: &EvalContext,
env: &Env,
exprs: &[Value],
span_map: &sema_core::SpanMap,
source_file: Option<std::path::PathBuf>,
) -> EvalResult {
let mut result = Value::nil();
for expr in exprs {
let expanded = expand_for_vm_in(ctx, env, expr)?;
if expanded.is_nil() {
continue;
}
let prog = sema_vm::compile_program_with_spans(
std::slice::from_ref(&expanded),
span_map,
source_file.clone(),
)?;
let globals = Rc::new(env.clone());
let mut vm = sema_vm::VM::new(
globals,
prog.functions,
&prog.native_table,
prog.main_cache_slots,
)?;
result = vm.execute(prog.closure, ctx)?;
}
env.bump_version();
Ok(result)
}
pub fn eval_value_vm(ctx: &EvalContext, expr: &Value, env: &Env) -> EvalResult {
let env_rc = Rc::new(env.clone());
let expanded = expand_for_vm_in(ctx, &env_rc, expr)?;
if expanded.is_nil() {
return Ok(Value::nil());
}
let prog = sema_vm::compile_program(std::slice::from_ref(&expanded), None)?;
let mut vm = sema_vm::VM::new(env_rc, prog.functions, &[], prog.main_cache_slots)?;
vm.execute(prog.closure, ctx)
}
pub fn call_value(ctx: &EvalContext, func: &Value, args: &[Value]) -> EvalResult {
match func.view() {
ValueView::NativeFn(native) => (native.func)(ctx, args),
ValueView::Lambda(_) => {
Err(SemaError::eval(
"internal: raw lambda value reached call_value (VM closures are native-fn-wrapped)"
.to_string(),
))
}
ValueView::Keyword(spur) => {
if args.len() != 1 {
let name = resolve(spur);
return Err(SemaError::arity(format!(":{name}"), "1", args.len()));
}
let key = Value::keyword_from_spur(spur);
match args[0].view() {
ValueView::Map(map) => Ok(map.get(&key).cloned().unwrap_or(Value::nil())),
ValueView::HashMap(map) => Ok(map.get(&key).cloned().unwrap_or(Value::nil())),
_ => Err(SemaError::type_error_with_value(
"map",
args[0].type_name(),
&args[0],
)),
}
}
ValueView::MultiMethod(mm) => call_multimethod(ctx, &mm, args),
_ => Err(
SemaError::eval(format!("not callable: {} ({})", func, func.type_name()))
.with_hint("expected a function, lambda, or keyword"),
),
}
}
fn call_multimethod(ctx: &EvalContext, mm: &Rc<MultiMethod>, args: &[Value]) -> EvalResult {
let dispatch_val = call_value(ctx, &mm.dispatch_fn, args)?;
let methods = mm.methods.borrow();
if let Some(handler) = methods.get(&dispatch_val) {
let handler = handler.clone();
drop(methods);
call_value(ctx, &handler, args)
} else {
drop(methods);
let default = mm.default.borrow().clone();
if let Some(handler) = default {
call_value(ctx, &handler, args)
} else {
Err(SemaError::eval(format!(
"no method in multimethod '{}' for dispatch value: {}",
resolve(mm.name),
dispatch_val
))
.with_hint("add a (defmethod name :default handler) to handle unmatched values"))
}
}
}
pub fn apply_macro_vm(
ctx: &EvalContext,
mac: &sema_core::Macro,
args: &[Value],
caller_env: &Env,
) -> Result<Value, SemaError> {
let env = Rc::new(Env::with_parent(Rc::new(caller_env.clone())));
if let Some(rest) = mac.rest_param {
if args.len() < mac.params.len() {
return Err(SemaError::arity(
resolve(mac.name),
format!("{}+", mac.params.len()),
args.len(),
));
}
for (param, arg) in mac.params.iter().zip(args.iter()) {
env.set(*param, arg.clone());
}
env.set(rest, Value::list(args[mac.params.len()..].to_vec()));
} else {
if args.len() != mac.params.len() {
return Err(SemaError::arity(
resolve(mac.name),
mac.params.len().to_string(),
args.len(),
));
}
for (param, arg) in mac.params.iter().zip(args.iter()) {
env.set(*param, arg.clone());
}
}
let mut result = Value::nil();
for expr in &mac.body {
let prog = sema_vm::compile_program(std::slice::from_ref(expr), None)?;
let mut vm = sema_vm::VM::new(env.clone(), prog.functions, &[], prog.main_cache_slots)?;
result = vm.execute(prog.closure, ctx)?;
}
Ok(result)
}
fn register_defmacro(items: &[Value], env: &Env) -> Result<(), SemaError> {
let args = &items[1..];
if args.len() < 3 {
return Err(SemaError::arity("defmacro", "3+", args.len()));
}
let name_spur = args[0]
.as_symbol_spur()
.ok_or_else(|| SemaError::eval("defmacro: name must be a symbol"))?;
let param_list = args[1]
.as_list()
.ok_or_else(|| SemaError::eval("defmacro: params must be a list"))?;
let param_names: Vec<sema_core::Spur> = param_list
.iter()
.map(|v| {
v.as_symbol_spur()
.ok_or_else(|| SemaError::eval("defmacro: parameter must be a symbol"))
})
.collect::<Result<_, _>>()?;
let (params, rest_param) = special_forms::parse_params(¶m_names);
let body = args[2..].to_vec();
env.set(
name_spur,
Value::macro_val(Macro {
params,
rest_param,
body,
name: name_spur,
}),
);
Ok(())
}
pub fn load_prelude(ctx: &EvalContext, env: &Rc<Env>) {
let exprs = sema_reader::read_many(crate::prelude::PRELUDE)
.unwrap_or_else(|e| panic!("internal: prelude failed to parse: {e}"));
for expr in &exprs {
expand_for_vm_in(ctx, env, expr)
.unwrap_or_else(|e| panic!("internal: prelude failed to load: {e}"));
}
}
pub fn register_vm_delegates(env: &Rc<Env>) {
let eval_env = env.clone();
env.set(
intern("__vm-eval"),
Value::native_fn(NativeFn::with_ctx("__vm-eval", move |ctx, args| {
if args.len() != 1 {
return Err(SemaError::arity("eval", "1", args.len()));
}
let expanded = expand_for_vm_in(ctx, &eval_env, &args[0])?;
if expanded.is_nil() {
return Ok(Value::nil());
}
let prog = sema_vm::compile_program(std::slice::from_ref(&expanded), None)?;
let mut vm =
sema_vm::VM::new(eval_env.clone(), prog.functions, &[], prog.main_cache_slots)?;
vm.execute(prog.closure, ctx)
})),
);
env.set(
intern("__vm-module-exports"),
Value::native_fn(NativeFn::with_ctx(
"__vm-module-exports",
move |ctx, args| {
if args.len() != 1 {
return Err(SemaError::arity("module-exports", "1", args.len()));
}
let names: Vec<String> = match args[0].as_list() {
Some(items) => items
.iter()
.map(|v| {
v.as_symbol().map(|s| s.to_string()).ok_or_else(|| {
SemaError::eval("module: export names must be symbols")
})
})
.collect::<Result<_, _>>()?,
None => return Err(SemaError::type_error("list", args[0].type_name())),
};
ctx.set_module_exports(names);
Ok(Value::nil())
},
)),
);
let load_env = env.clone();
env.set(
intern("__vm-load"),
Value::native_fn(NativeFn::with_ctx("__vm-load", move |ctx, args| {
if args.len() != 1 {
return Err(SemaError::arity("load", "1", args.len()));
}
let target = sema_vm::current_vm_globals().unwrap_or_else(|| load_env.clone());
match special_forms::eval_load(std::slice::from_ref(&args[0]), &target, ctx)? {
Trampoline::Value(v) => Ok(v),
Trampoline::Eval(..) => Ok(Value::nil()),
}
})),
);
let import_env = env.clone();
env.set(
intern("__vm-import"),
Value::native_fn(NativeFn::with_ctx("__vm-import", move |ctx, args| {
if args.len() != 2 {
return Err(SemaError::arity("import", "2", args.len()));
}
ctx.sandbox.check(sema_core::Caps::FS_READ, "import")?;
let mut imp_args = vec![args[0].clone()];
if let Some(items) = args[1].as_list() {
imp_args.extend(items.iter().cloned());
}
let target = sema_vm::current_vm_globals().unwrap_or_else(|| import_env.clone());
match special_forms::eval_import(&imp_args, &target, ctx)? {
Trampoline::Value(v) => Ok(v),
Trampoline::Eval(..) => Ok(Value::nil()),
}
})),
);
let macro_env = env.clone();
env.set(
intern("__vm-defmacro"),
Value::native_fn(NativeFn::simple("__vm-defmacro", move |args| {
if args.len() != 4 {
return Err(SemaError::arity("defmacro", "4", args.len()));
}
let name = match args[0].as_symbol_spur() {
Some(s) => s,
None => return Err(SemaError::type_error("symbol", args[0].type_name())),
};
let params = match args[1].as_list() {
Some(items) => items
.iter()
.map(|v| match v.as_symbol_spur() {
Some(s) => Ok(s),
None => Err(SemaError::type_error("symbol", v.type_name())),
})
.collect::<Result<Vec<_>, _>>()?,
None => return Err(SemaError::type_error("list", args[1].type_name())),
};
let rest_param = if let Some(s) = args[2].as_symbol_spur() {
Some(s)
} else if args[2].is_nil() {
None
} else {
return Err(SemaError::type_error("symbol or nil", args[2].type_name()));
};
let body = vec![args[3].clone()];
macro_env.set(
name,
Value::macro_val(Macro {
params,
rest_param,
body,
name,
}),
);
Ok(Value::nil())
})),
);
let dmf_env = env.clone();
env.set(
intern("__vm-defmacro-form"),
Value::native_fn(NativeFn::simple("__vm-defmacro-form", move |args| {
if args.len() != 1 {
return Err(SemaError::arity("defmacro-form", "1", args.len()));
}
let items = args[0]
.as_list()
.ok_or_else(|| SemaError::type_error("list", args[0].type_name()))?;
register_defmacro(items, &dmf_env)?;
Ok(Value::nil())
})),
);
let drt_env = env.clone();
env.set(
intern("__vm-define-record-type"),
Value::native_fn(NativeFn::simple("__vm-define-record-type", move |args| {
if args.len() != 5 {
return Err(SemaError::arity("define-record-type", "5", args.len()));
}
let mut ctor_form = vec![args[1].clone()];
if let Some(fields) = args[3].as_list() {
ctor_form.extend(fields.iter().cloned());
}
let mut dr_args = vec![args[0].clone(), Value::list(ctor_form), args[2].clone()];
if let Some(specs) = args[4].as_list() {
for spec in specs.iter() {
dr_args.push(spec.clone());
}
}
match special_forms::eval_define_record_type(&dr_args, &drt_env)? {
Trampoline::Value(v) => Ok(v),
Trampoline::Eval(..) => Ok(Value::nil()),
}
})),
);
env.set(
intern("__vm-delay"),
Value::native_fn(NativeFn::simple("__vm-delay", |args| {
if args.len() != 1 {
return Err(SemaError::arity("delay", "1", args.len()));
}
Ok(Value::thunk(Thunk {
body: args[0].clone(),
forced: RefCell::new(None),
}))
})),
);
let force_env = env.clone();
env.set(
intern("__vm-force"),
Value::native_fn(NativeFn::with_ctx("__vm-force", move |ctx, args| {
if args.len() != 1 {
return Err(SemaError::arity("force", "1", args.len()));
}
if let Some(thunk) = args[0].as_thunk_rc() {
if let Some(val) = thunk.forced.borrow().as_ref() {
return Ok(val.clone());
}
let val = if thunk.body.as_native_fn_rc().is_some()
|| thunk.body.as_lambda_rc().is_some()
{
sema_core::call_callback(ctx, &thunk.body, &[])?
} else {
eval_value_vm(ctx, &thunk.body, &force_env)?
};
*thunk.forced.borrow_mut() = Some(val.clone());
Ok(val)
} else {
Err(SemaError::type_error("thunk", args[0].type_name())
.with_hint("force: argument must be a (delay ...) or promise — non-promise values are an error"))
}
})),
);
let me_env = env.clone();
env.set(
intern("__vm-macroexpand"),
Value::native_fn(NativeFn::with_ctx("__vm-macroexpand", move |ctx, args| {
if args.len() != 1 {
return Err(SemaError::arity("macroexpand", "1", args.len()));
}
if let Some(items) = args[0].as_list() {
if !items.is_empty() {
if let Some(spur) = items[0].as_symbol_spur() {
if let Some(mac_val) = me_env.get(spur) {
if let Some(mac) = mac_val.as_macro_rc() {
return apply_macro_vm(ctx, &mac, &items[1..], &me_env);
}
}
}
}
}
Ok(args[0].clone())
})),
);
env.set(
intern("__vm-prompt"),
Value::native_fn(NativeFn::simple("__vm-prompt", |args| {
use sema_core::{Message, Prompt, Role};
if args.len() != 1 {
return Err(SemaError::arity("__vm-prompt", "1", args.len()));
}
let entries = args[0]
.as_list()
.ok_or_else(|| SemaError::type_error("list", args[0].type_name()))?;
let mut messages = Vec::new();
for entry in entries {
if let Some(msg) = entry.as_message_rc() {
messages.push((*msg).clone());
} else if let Some(pair) = entry.as_list() {
if pair.len() == 2 {
let role_str = pair[0]
.as_str()
.ok_or_else(|| SemaError::eval("prompt: expected role string"))?;
let role = match role_str {
"system" => Role::System,
"user" => Role::User,
"assistant" => Role::Assistant,
"tool" => Role::Tool,
other => {
return Err(SemaError::eval(format!(
"prompt: unknown role '{other}'"
)))
}
};
let parts = pair[1]
.as_list()
.ok_or_else(|| SemaError::type_error("list", pair[1].type_name()))?;
let mut content = String::new();
for part in parts {
if let Some(s) = part.as_str() {
content.push_str(s);
} else {
content.push_str(&part.to_string());
}
}
messages.push(Message {
role,
content,
images: Vec::new(),
});
} else {
return Err(SemaError::eval(
"prompt: expected (role parts) pair or message value",
));
}
} else {
return Err(SemaError::eval(
"prompt: expected (role parts) pair or message value",
));
}
}
Ok(Value::prompt(Prompt { messages }))
})),
);
env.set(
intern("__vm-message"),
Value::native_fn(NativeFn::simple("__vm-message", |args| {
use sema_core::{Message, Role};
if args.len() != 2 {
return Err(SemaError::arity("__vm-message", "2", args.len()));
}
let role = if let Some(spur) = args[0].as_keyword_spur() {
let s = resolve(spur);
match s.as_str() {
"system" => Role::System,
"user" => Role::User,
"assistant" => Role::Assistant,
"tool" => Role::Tool,
other => {
return Err(SemaError::eval(format!("message: unknown role '{other}'")))
}
}
} else {
return Err(SemaError::type_error("keyword", args[0].type_name()));
};
let parts = args[1]
.as_list()
.ok_or_else(|| SemaError::type_error("list", args[1].type_name()))?;
let mut content = String::new();
for part in parts {
if let Some(s) = part.as_str() {
content.push_str(s);
} else {
content.push_str(&part.to_string());
}
}
Ok(Value::message(Message {
role,
content,
images: Vec::new(),
}))
})),
);
let tool_env = env.clone();
env.set(
intern("__vm-deftool"),
Value::native_fn(NativeFn::simple("__vm-deftool", move |args| {
if args.len() != 4 {
return Err(SemaError::arity("deftool", "4", args.len()));
}
let name = args[0]
.as_symbol()
.ok_or_else(|| SemaError::eval("deftool: name must be a symbol"))?;
special_forms::register_tool(
&name,
args[1].clone(),
args[2].clone(),
args[3].clone(),
&tool_env,
)
})),
);
let agent_env = env.clone();
env.set(
intern("__vm-defagent"),
Value::native_fn(NativeFn::simple("__vm-defagent", move |args| {
if args.len() != 2 {
return Err(SemaError::arity("defagent", "2", args.len()));
}
let name = args[0]
.as_symbol()
.ok_or_else(|| SemaError::eval("defagent: name must be a symbol"))?;
special_forms::register_agent(&name, args[1].clone(), &agent_env)
})),
);
env.set(
intern("__vm-destructure"),
Value::native_fn(NativeFn::simple("__vm-destructure", |args| {
if args.len() != 2 {
return Err(SemaError::arity("__vm-destructure", "2", args.len()));
}
let bindings = crate::destructure::destructure(&args[0], &args[1])?;
let mut map = std::collections::BTreeMap::new();
for (spur, val) in bindings {
map.insert(Value::symbol_from_spur(spur), val);
}
Ok(Value::map(map))
})),
);
env.set(
intern("__vm-try-match"),
Value::native_fn(NativeFn::simple("__vm-try-match", |args| {
if args.len() != 2 {
return Err(SemaError::arity("__vm-try-match", "2", args.len()));
}
match crate::destructure::try_match(&args[0], &args[1])? {
Some(bindings) => {
let mut map = std::collections::BTreeMap::new();
for (spur, val) in bindings {
map.insert(Value::symbol_from_spur(spur), val);
}
Ok(Value::map(map))
}
None => Ok(Value::nil()),
}
})),
);
env.set(
intern("__vm-match-failed"),
Value::native_fn(NativeFn::simple("__vm-match-failed", |args| {
let val = args.first().cloned().unwrap_or_else(Value::nil);
Err(
SemaError::eval(format!("match: no clause matched value: {val}")).with_hint(
"add a catch-all `(_ ...)` clause, or use `match*` to return nil on no match",
),
)
})),
);
env.set(
intern("__vm-make-multi"),
Value::native_fn(NativeFn::simple("__vm-make-multi", |args| {
if args.len() != 2 {
return Err(SemaError::arity("__vm-make-multi", "2", args.len()));
}
let name_spur = args[0]
.as_symbol_spur()
.ok_or_else(|| SemaError::eval("__vm-make-multi: expected symbol"))?;
Ok(Value::multimethod(MultiMethod {
name: name_spur,
dispatch_fn: args[1].clone(),
methods: RefCell::new(std::collections::BTreeMap::new()),
default: RefCell::new(None),
}))
})),
);
env.set(
intern("__vm-defmethod"),
Value::native_fn(NativeFn::simple("__vm-defmethod", |args| {
if args.len() != 3 {
return Err(SemaError::arity("__vm-defmethod", "3", args.len()));
}
let mm = args[0]
.as_multimethod_rc()
.ok_or_else(|| SemaError::eval("defmethod: first argument is not a multimethod"))?;
let dispatch_val = &args[1];
let handler = &args[2];
if let Some(kw) = dispatch_val.as_keyword_spur() {
if resolve(kw) == "default" {
*mm.default.borrow_mut() = Some(handler.clone());
return Ok(Value::nil());
}
}
mm.methods
.borrow_mut()
.insert(dispatch_val.clone(), handler.clone());
Ok(Value::nil())
})),
);
}