use crate::base::BaseFn; use crate::chunk::Chunk; use crate::js::*; use crate::op::Op; use crate::panic::{Panic, PanicMsg}; use crate::value::{Key, LFn, Value}; use crate::world::Zoo; use imbl::{HashMap, Vector}; use num_traits::FromPrimitive; use std::cell::RefCell; use std::collections::VecDeque; use std::fmt; use std::mem::swap; use std::rc::Rc; const MAX_REDUCTIONS: usize = 1000; #[derive(Debug, Clone, PartialEq)] pub struct CallFrame { pub function: Value, pub arity: u8, pub stack_base: usize, pub ip: usize, } impl CallFrame { pub fn chunk(&self) -> &Chunk { let Value::Fn(ref function) = self.function else { unreachable!() }; function.chunk(self.arity) } } impl fmt::Display for CallFrame { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { let Value::Fn(ref function) = self.function else { unreachable!() }; write!( f, "CallFrame: {}/{} @ {}", function.name(), self.arity, self.ip ) } } fn combine_bytes(high: u8, low: u8) -> usize { let out = ((high as u16) << 8) + low as u16; out as usize } const REGISTER_SIZE: usize = 8; #[derive(Debug, Clone, PartialEq)] pub struct Creature { stack: Vec, call_stack: Vec, frame: CallFrame, ip: usize, register: [Value; REGISTER_SIZE], matches: bool, match_depth: u8, pub result: Option>, debug: bool, last_code: usize, pub pid: &'static str, pub mbx: VecDeque, msg_idx: usize, reductions: usize, zoo: Rc>, r#yield: bool, scrutinee: Option, parents: Vec<&'static str>, siblings: Vec<&'static str>, } impl std::fmt::Display for Creature { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { write!(f, "Creature. {} @{}", self.pid, self.ip) } } impl Creature { pub fn new(chunk: Chunk, zoo: Rc>, debug: bool) -> Creature { let lfn = LFn::Defined { name: "toplevel", doc: None, chunks: vec![chunk], arities: vec![0], splat: 0, closed: RefCell::new(vec![]), }; let base_fn = Value::Fn(Rc::new(lfn)); Creature::spawn(base_fn, zoo, debug) } pub fn spawn(function: Value, zoo: Rc>, debug: bool) -> Creature { let base_frame = CallFrame { function, stack_base: 0, ip: 0, arity: 0, }; Creature { stack: vec![], call_stack: Vec::with_capacity(64), frame: base_frame, ip: 0, register: [const { Value::Nothing }; REGISTER_SIZE], matches: false, match_depth: 0, result: None, debug, last_code: 0, pid: "", zoo, mbx: VecDeque::new(), reductions: 0, r#yield: false, msg_idx: 0, scrutinee: None, parents: vec![], siblings: vec![], } } pub fn reduce(&mut self) { self.reductions += 1; } pub fn reset_reductions(&mut self) { self.reductions = 0; self.r#yield = false; } pub fn receive(&mut self, value: Value) { self.mbx.push_back(value); } pub fn chunk(&self) -> &Chunk { self.frame.chunk() } pub fn push(&mut self, value: Value) { self.stack.push(value); } pub fn pop(&mut self) -> Value { self.stack.pop().unwrap() } pub fn peek(&self) -> &Value { self.stack.last().unwrap() } pub fn print_stack(&self) { let mut inner = vec![]; for (i, value) in self.stack.iter().enumerate() { if i == self.frame.stack_base { inner.push(format!("->{}<-", value.show())) } else { inner.push(value.show()) } } let inner = inner.join("|"); let register = self .register .iter() .map(|val| val.to_string()) .collect::>() .join(","); let mbx = self .mbx .iter() .map(|val| val.show()) .collect::>() .join("/"); println!( "{:04}: [{inner}] ({register}) {} {{{mbx}}}", self.last_code, self.pid ); } fn print_debug(&self) { self.print_stack(); let mut ip = self.last_code; self.chunk().dissasemble_instr(&mut ip); } fn panic(&mut self, msg: PanicMsg) { // first prep the current frame for parsing let mut frame = self.frame.clone(); frame.ip = self.last_code; // add it to our cloned stack let mut call_stack = self.call_stack.clone(); call_stack.push(frame); // console_log!( // "{}", // call_stack // .iter() // .map(|s| s.to_string()) // .collect::>() // .join("\n") // ); //make a panic let panic = Panic { msg, scrutinee: self.scrutinee.clone(), call_stack, }; // and gtfo self.result = Some(Err(panic)); self.r#yield = true; } fn panic_with(&mut self, msg: String) { self.panic(PanicMsg::Generic(msg)); } fn get_value_at(&mut self, idx: u8) -> Value { let idx = idx as usize; let idx = idx + self.frame.stack_base; self.stack[idx].clone() } fn get_scrutinee(&mut self) -> Value { let idx = self.stack.len() - self.match_depth as usize - 1; self.stack[idx].clone() } fn read(&mut self) -> u8 { let code = self.chunk().bytecode[self.ip]; self.ip += 1; code } fn read2(&mut self) -> usize { let high = self.read(); let low = self.read(); combine_bytes(high, low) } fn at_end(&mut self) -> bool { self.ip >= self.chunk().bytecode.len() } fn send_msg(&mut self, pid: Value, msg: Value) { let Value::Keyword(pid) = pid else { return self.panic_with(format!("Ludus expected pid keyword, and instead got {pid}")); }; if self.pid == pid { self.mbx.push_back(msg.clone()); } else { self.zoo.as_ref().borrow_mut().send_msg(pid, msg); } self.push(Value::Keyword("ok")); } // TODO: fix these based on what I decide about `link` & `monitor` fn link_report(&mut self, parent: Value, child: Value) { let (Value::Keyword(parent), Value::Keyword(child)) = (parent, child) else { unreachable!("expected keyword pids in link_report"); }; let child = if child == self.pid { self } else { &mut self.zoo.borrow_mut().catch(child) }; child.parents.push(parent); } fn link_panic(&mut self, left_pid: Value, right_pid: Value) { let (Value::Keyword(left_id), Value::Keyword(right_id)) = (left_pid, right_pid) else { unreachable!("expected keyword pids in link_panic"); }; // if we're linking to ourselves, we don't need to do anything if left_id == self.pid && right_id == self.pid { return; } let mut zoo = self.zoo.borrow_mut(); // fancy footwork w/ cases: we can't catch ourselves in the zoo if left_id == self.pid { self.siblings.push(right_id); let mut right = zoo.catch(right_id); right.siblings.push(self.pid); } else if right_id == self.pid { self.siblings.push(left_id); let mut left = zoo.catch(left_id); left.siblings.push(self.pid); } else { let mut left = zoo.catch(left_id); let mut right = zoo.catch(right_id); left.siblings.push(right_id); right.siblings.push(left_id); } } fn handle_msg(&mut self, args: Vec) { println!("message received by {}: {}", self.pid, args[0]); let Value::Keyword(msg) = args.first().unwrap() else { return self.panic_with("malformed message to Process".to_string()); }; match *msg { "self" => self.push(Value::Keyword(self.pid)), "send" => self.send_msg(args[1].clone(), args[2].clone()), "spawn" => { let f = args[1].clone(); let proc = Creature::spawn(f, self.zoo.clone(), self.debug); let id = self.zoo.as_ref().borrow_mut().put(proc); console_log!("spawning new process {id}!"); self.push(Value::Keyword(id)); } "yield" => { self.r#yield = true; println!("yielding from {}", self.pid); self.push(Value::Keyword("ok")); } "alive" => { let Value::Keyword(pid) = args[1].clone() else { unreachable!(); }; let is_alive = self.zoo.as_ref().borrow().is_alive(pid); if is_alive { self.push(Value::True) } else { self.push(Value::False) } } "link_panic" => self.link_panic(args[1].clone(), args[2].clone()), "link_report" => self.link_report(args[1].clone(), args[2].clone()), "flush" => { let msgs = self.mbx.iter().cloned().collect::>(); let msgs = Vector::from(msgs); println!( "delivering messages: {}", msgs.iter() .map(|x| x.show()) .collect::>() .join(" | ") ); self.mbx = VecDeque::new(); println!("flushing messages in {}", self.pid); self.push(Value::List(Box::new(msgs))); } "sleep" => { println!("sleeping {} for {}", self.pid, args[1]); let Value::Number(ms) = args[1] else { unreachable!() }; self.zoo.as_ref().borrow_mut().sleep(self.pid, ms); self.r#yield = true; self.push(Value::Keyword("ok")); } msg => panic!("Process does not understand message: {msg}"), } } pub fn interpret(&mut self) { console_log!("starting process {}", self.pid); console_log!( "mbx: {}", self.mbx .iter() .map(|x| x.show()) .collect::>() .join(" | ") ); loop { if self.at_end() { let result = self.stack.pop().unwrap(); // println!("process {} has returned {result}", self.pid); self.result = Some(Ok(result)); return; } if self.r#yield { console_log!("yielding from {}", self.pid); return; } if self.reductions >= MAX_REDUCTIONS { // println!( // "process {} is yielding after {MAX_REDUCTIONS} reductions", // self.pid // ); return; } let code = self.read(); if self.debug { self.last_code = self.ip - 1; self.print_debug(); } let op = Op::from_u8(code).unwrap(); use Op::*; match op { Noop => (), Nil => self.push(Value::Nil), Nothing => self.push(Value::Nothing), True => self.push(Value::True), False => self.push(Value::False), Msg => { let _ = self.read(); } Constant => { let const_idx = self.read2(); let value = self.chunk().constants[const_idx].clone(); self.push(value); } Jump => { let jump_len = self.read2(); self.ip += jump_len; } JumpBack => { let jump_len = self.read2(); self.ip -= jump_len + 3; } JumpIfFalse => { let jump_len = self.read2(); let cond = self.pop(); match cond { Value::Nil | Value::False => self.ip += jump_len, _ => (), } } JumpIfTrue => { let jump_len = self.read2(); let cond = self.pop(); match cond { Value::Nil | Value::False => (), _ => self.ip += jump_len, } } JumpIfZero => { let jump_len = self.read2(); let cond = self.pop(); match cond { Value::Number(x) if x <= 0.0 => self.ip += jump_len, Value::Number(..) => (), _ => { return self .panic_with(format!("repeat requires a number, but got {cond}")) } } } Pop => { self.pop(); } PopN => { let n = self.read() as usize; self.stack.truncate(self.stack.len() - n); } PushBinding => { let idx = self.read(); let value = self.get_value_at(idx); self.push(value); } PushGlobal => { let key = self.pop(); let Value::Keyword(name) = key else { unreachable!("internal Ludus error: expected key for global resolution") }; let value = self.chunk().env.get(&Key::Keyword(name)).unwrap(); self.push(value.clone()); } Store => { self.register[0] = self.pop(); } StoreN => { let n = self.read() as usize; for i in (0..n).rev() { self.register[i] = self.pop(); } } Stash => { self.register[0] = self.peek().clone(); } Load => { let mut value = Value::Nothing; swap(&mut self.register[0], &mut value); self.push(value); } LoadN => { let n = self.read() as usize; for i in 0..n { let mut value = Value::Nothing; swap(&mut self.register[i], &mut value); self.push(value); } } ResetMatch => { self.matches = false; self.match_depth = 0; } UnconditionalMatch => { self.matches = true; } MatchType => { let as_type = self.pop(); let Value::Keyword(as_type) = as_type else { unreachable!() }; let value = self.get_scrutinee(); let val_type = value.type_of(); self.matches = val_type == as_type; } MatchNil => { let value = self.get_scrutinee(); self.matches = value == Value::Nil; } MatchTrue => { let value = self.get_scrutinee(); self.matches = value == Value::True; } MatchFalse => { let value = self.get_scrutinee(); self.matches = value == Value::False; } PanicNoMatch => { if !self.matches { return self.panic(PanicMsg::NoMatch); } } PanicNoLetMatch => { if !self.matches { return self.panic(PanicMsg::NoLetMatch); } } PanicNoFnMatch => { if !self.matches { return self.panic(PanicMsg::NoFnMatch); } } MatchConstant => { let const_idx = self.read2(); let scrutinee = self.get_scrutinee(); // let idx = self.stack.len() - self.match_depth as usize - 1; self.matches = scrutinee == self.chunk().constants[const_idx]; } MatchString => { let pattern_idx = self.read(); let scrutinee = self.get_scrutinee(); self.matches = match scrutinee { Value::String(str) => self.chunk().string_patterns[pattern_idx as usize] .re .is_match(str.as_str()), Value::Interned(str) => self.chunk().string_patterns[pattern_idx as usize] .re .is_match(str), _ => false, }; } PushStringMatches => { let pattern_idx = self.read(); let pattern_len = self.chunk().string_patterns[pattern_idx as usize] .words .len(); // let scrutinee_idx = self.stack.len() - self.match_depth as usize - 1; let scrutinee = self.get_scrutinee(); let scrutinee = match scrutinee { Value::String(str) => str.as_ref().clone(), Value::Interned(str) => str.to_string(), _ => unreachable!(), }; let captures = self.chunk().string_patterns[pattern_idx as usize] .re .captures(scrutinee.as_str()) .unwrap(); for cap in 0..pattern_len { self.push(Value::String(Rc::new(captures[cap + 1].to_string()))) } self.match_depth += pattern_len as u8; } MatchTuple => { // let idx = self.stack.len() - self.match_depth as usize - 1; let tuple_len = self.read() as usize; let scrutinee = self.get_scrutinee(); match scrutinee { Value::Tuple(members) => self.matches = members.len() == tuple_len, _ => self.matches = false, }; } MatchSplattedTuple => { let patt_len = self.read() as usize; let scrutinee = self.get_scrutinee(); match scrutinee { Value::Tuple(members) => self.matches = members.len() >= patt_len, _ => self.matches = false, } } PushTuple => { let tuple_len = self.read() as usize; let tuple_members = self.stack.split_off(self.stack.len() - tuple_len); let tuple = Value::Tuple(Rc::new(tuple_members)); self.push(tuple); } LoadTuple => { // let idx = self.stack.len() - self.match_depth as usize - 1; // let tuple = self.stack[idx].clone(); let tuple = self.get_scrutinee(); match tuple { Value::Tuple(members) => { for member in members.iter() { self.push(member.clone()); } } _ => { return self .panic_with(format!("internal error: expected tuple, got {tuple}")) } }; } LoadSplattedTuple => { let load_len = self.read() as usize; let tuple = self.get_scrutinee(); let Value::Tuple(members) = tuple else { return self .panic_with(format!("internal error: expected tuple, got {tuple}")); }; for i in 0..load_len - 1 { self.push(members[i].clone()); } let mut splatted = Vector::new(); for i in load_len - 1..members.len() { splatted.push_back(members[i].clone()); } self.push(Value::List(Box::new(splatted))); } PushList => { self.push(Value::List(Box::new(Vector::new()))); } AppendList => { let value = self.pop(); let list = self.pop(); let Value::List(mut members) = list else { return self.panic_with(format!( "only lists may be splatted into lists, but got {list}" )); }; members.push_back(value); self.push(Value::List(members)); } ConcatList => { let list = self.pop(); let target = self.pop(); let Value::List(mut target) = target else { unreachable!() }; let Value::List(splatted) = list else { return self.panic_with(format!( "only lists may be splatted into lists, but got {list}" )); }; target.append(*splatted); self.push(Value::List(target)); } MatchList => { // let idx = self.stack.len() - self.match_depth as usize - 1; let list_len = self.read() as usize; let scrutinee = self.get_scrutinee(); match scrutinee { Value::List(members) => self.matches = members.len() == list_len, _ => self.matches = false, }; } MatchSplattedList => { // let idx = self.stack.len() - self.match_depth as usize - 1; let patt_len = self.read() as usize; let scrutinee = self.get_scrutinee(); match scrutinee { Value::List(members) => self.matches = members.len() >= patt_len, _ => self.matches = false, } } LoadList => { // let idx = self.stack.len() - self.match_depth as usize - 1; let list = self.get_scrutinee(); match list { Value::List(members) => { for member in members.iter() { self.push(member.clone()); } } _ => { return self .panic_with(format!("internal error: expected list, got {list}")) } }; } LoadSplattedList => { let loaded_len = self.read() as usize; let list = self.get_scrutinee(); let Value::List(members) = list else { return self .panic_with(format!("internal error: expected list, got {list}")); }; for i in 0..loaded_len - 1 { self.push(members[i].clone()); } let splatted = Value::List(Box::new(members.skip(loaded_len - 1))); self.push(splatted); } PushDict => { self.push(Value::Dict(Box::new(HashMap::new()))); } AppendDict => { let value = self.pop(); let key = Key::from_value(self.pop()); let Value::Dict(mut dict) = self.pop() else { unreachable!() }; dict.insert(key, value); self.push(Value::Dict(dict)); } ConcatDict => { let prolly_dict = self.pop(); let Value::Dict(splatted) = prolly_dict else { return self.panic_with(format!( "only dicts may be splatted into dicts, got {prolly_dict}" )); }; let Value::Dict(target) = self.pop() else { unreachable!() }; let union = splatted.union(*target); self.push(Value::Dict(Box::new(union))); } LoadDictValue => { let dict_idx = self.read(); let dict = match self.get_value_at(dict_idx) { Value::Dict(dict) => dict, value => { println!( "internal Ludus error in function {}", self.frame.function.as_fn().name() ); unreachable!("expected dict, got {value}") } }; let key = Key::from_value(self.pop()); let value = dict.get(&key).unwrap_or(&Value::Nil); self.push(value.clone()); } MatchDict => { // let idx = self.stack.len() - self.match_depth as usize - 1; let dict_len = self.read(); let scrutinee = self.get_scrutinee(); match scrutinee { Value::Dict(members) => self.matches = members.len() == dict_len as usize, _ => self.matches = false, }; } MatchSplattedDict => { // let idx = self.stack.len() - self.match_depth as usize - 1; let patt_len = self.read() as usize; let scrutinee = self.get_scrutinee(); match scrutinee { Value::Dict(members) => self.matches = members.len() >= patt_len, _ => self.matches = false, } } DropDictEntry => { let key_to_drop = Key::from_value(self.pop()); let Value::Dict(mut dict) = self.pop() else { unreachable!() }; dict.remove(&key_to_drop); self.push(Value::Dict(dict)); } PushBox => { let val = self.pop(); self.push(Value::Box(Rc::new(RefCell::new(val)))); } GetKey => { let key = Key::from_value(self.pop()); let dict = self.pop(); let value = match dict { Value::Dict(d) => d.get(&key).unwrap_or(&Value::Nil).clone(), _ => Value::Nil, }; self.push(value); } JumpIfNoMatch => { let jump_len = self.read2(); // let jump_len = self.chunk().bytecode[self.ip + 1] as usize; if !self.matches { self.ip += jump_len } } JumpIfMatch => { let jump_len = self.read2(); if self.matches { self.ip += jump_len; } } TypeOf => { let val = self.pop(); let type_of = Value::Keyword(val.type_of()); self.push(type_of); } ToInt => { let val = self.pop(); if let Value::Number(x) = val { self.push(Value::Number(x as usize as f64)); } else { return self.panic_with(format!("repeat requires a number, but got {val}")); } } Decrement => { let val = self.pop(); if let Value::Number(x) = val { self.push(Value::Number(x - 1.0)); } else { return self .panic_with(format!("you may only decrement a number, but got {val}")); } } Duplicate => { self.push(self.peek().clone()); } MatchDepth => { self.match_depth = self.read(); } PanicWhenFallthrough => { return self.panic_with( "when form fallthrough: expected one clause to be truthy".to_string(), ); } Eq => { let first = self.pop(); let second = self.pop(); if first == second { self.push(Value::True) } else { self.push(Value::False) } } Add => { let first = self.pop(); let second = self.pop(); if let (Value::Number(x), Value::Number(y)) = (first.clone(), second.clone()) { self.push(Value::Number(x + y)) } else { return self.panic_with(format!( "`add` requires two numbers, but got {second}, {first}" )); } } Sub => { let first = self.pop(); let second = self.pop(); if let (Value::Number(x), Value::Number(y)) = (first.clone(), second.clone()) { self.push(Value::Number(y - x)) } else { return self.panic_with(format!( "`sub` requires two numbers, but got {second}, {first}" )); } } Mult => { let first = self.pop(); let second = self.pop(); if let (Value::Number(x), Value::Number(y)) = (first.clone(), second.clone()) { self.push(Value::Number(x * y)) } else { return self.panic_with(format!( "`mult` requires two numbers, but got {second}, {first}" )); } } Div => { let first = self.pop(); let second = self.pop(); if let (Value::Number(x), Value::Number(y)) = (first.clone(), second.clone()) { if x == 0.0 { return self.panic_with("division by 0".to_string()); } self.push(Value::Number(y / x)) } else { return self.panic_with(format!( "`div` requires two numbers, but got {second}, {first}" )); } } Unbox => { let the_box = self.pop(); let inner = if let Value::Box(b) = the_box { b.borrow().clone() } else { return self .panic_with(format!("`unbox` requires a box, but got {the_box}")); }; self.push(inner); } BoxStore => { let new_value = self.pop(); let the_box = self.pop(); if let Value::Box(b) = the_box { b.replace(new_value.clone()); } else { return self .panic_with(format!("`store` requires a box, but got {the_box}")); } self.push(new_value); } Assert => { let value = self.stack.last().unwrap(); if let Value::Nil | Value::False = value { return self.panic_with("asserted falsy value".to_string()); } } Get => { let key = self.pop(); if !matches!( key, Value::Keyword(_) | Value::String(_) | Value::Interned(_) ) { return self.panic_with(format!( "dict keys must be keywords or strings, but got {key}" )); } let key = Key::from_value(key); let dict = self.pop(); let value = match dict { Value::Dict(d) => d.get(&key).unwrap_or(&Value::Nil).clone(), _ => Value::Nil.clone(), }; self.push(value); } At => { let idx = self.pop(); let ordered = self.pop(); let value = match (ordered, idx.clone()) { (Value::List(l), Value::Number(i)) => { l.get(i as usize).unwrap_or(&Value::Nil).clone() } (Value::Tuple(t), Value::Number(i)) => { t.get(i as usize).unwrap_or(&Value::Nil).clone() } (_, Value::Number(_)) => Value::Nil, _ => { return self .panic_with(format!("indexes must be numbers, but got {idx}")) } }; self.push(value); } Not => { let value = self.pop(); let negated = match value { Value::Nil | Value::False => Value::True, _ => Value::False, }; self.push(negated); } Panic => { let msg = self.pop().stringify(); return self.panic_with(msg); } EmptyString => { self.push(Value::String(Rc::new("".to_string()))); } //TODO: don't use the schlemiel's algo here ConcatStrings => { let second = self.pop(); let first = self.pop(); let combined = match (first, second) { (Value::String(first), Value::String(second)) => { let mut new = first.as_ref().clone(); new.push_str(second.as_str()); Value::String(Rc::new(new)) } _ => unreachable!(), }; self.push(combined); } Stringify => { let to_stringify = self.pop(); let the_string = to_stringify.stringify(); let stringified = Value::String(Rc::new(the_string)); self.push(stringified); } Partial => { let arity = self.read(); let the_fn = self.pop(); let Value::Fn(ref inner) = the_fn else { return self.panic_with(format!( "only functions may be partially applied, but got {the_fn}" )); }; let args = self.stack.split_off(self.stack.len() - arity as usize); let partial = crate::value::Partial { args, name: inner.name(), function: the_fn, }; self.push(Value::Partial(Rc::new(partial))); } TailCall => { self.reduce(); let arity = self.read(); let called = self.pop(); if self.debug { println!( "=== tail call into {called}/{arity} from {} ===", self.frame.function.as_fn().name() ); } match called { Value::Process => { let args = self.stack.split_off(self.stack.len() - arity as usize); self.handle_msg(args); } Value::Fn(_) => { if !called.as_fn().accepts(arity) { return self.panic_with(format!( "wrong number of arguments to {} passing {arity} args", called.show() )); } // first put the arguments in the register for i in 0..arity as usize { self.register[arity as usize - i - 1] = self.pop(); } // save the arguments as our scrutinee let mut scrutinee = vec![]; for i in 0..arity as usize { scrutinee.push(self.register[i].clone()) } self.scrutinee = Some(Value::tuple(scrutinee)); // then pop everything back to the current stack frame self.stack.truncate(self.frame.stack_base); // then push the arguments back on the stack let mut i = 0; while i < 8 && self.register[i] != Value::Nothing { let mut value = Value::Nothing; swap(&mut self.register[i], &mut value); self.push(value); i += 1; } let splat_arity = called.as_fn().splat_arity(); if splat_arity > 0 && arity >= splat_arity { let splatted_args = self.stack.split_off( self.stack.len() - (arity - splat_arity) as usize - 1, ); let gathered_args = Vector::from(splatted_args); self.push(Value::List(Box::new(gathered_args))); } let arity = if splat_arity > 0 { splat_arity.min(arity) } else { arity }; let mut frame = CallFrame { function: called, arity, stack_base: self.stack.len() - arity as usize, ip: 0, }; swap(&mut self.frame, &mut frame); frame.ip = self.ip; self.ip = 0; } Value::BaseFn(base_fn) => { let value = match (arity, *base_fn) { (0, BaseFn::Nullary(_, f)) => f(), (1, BaseFn::Unary(_, f)) => f(&self.pop()), (2, BaseFn::Binary(_, f)) => { let y = &self.pop(); let x = &self.pop(); f(x, y) } (3, BaseFn::Ternary(_, f)) => { let z = &self.pop(); let y = &self.pop(); let x = &self.pop(); f(x, y, z) } _ => { return self.panic_with( "internal ludus error: bad base fn call".to_string(), ) } }; self.push(value); } Value::Partial(partial) => { let last_arg = self.pop(); let args = &partial.args; for arg in args { if *arg == Value::Nothing { self.push(last_arg.clone()); } else { self.push(arg.clone()); } } let the_fn = partial.function.clone(); let mut frame = CallFrame { function: the_fn, arity: args.len() as u8, stack_base: self.stack.len() - args.len(), ip: 0, }; swap(&mut self.frame, &mut frame); frame.ip = self.ip; self.call_stack.push(frame); self.ip = 0; } Value::Keyword(key) => { let dict = self.pop(); if arity != 1 { return self.panic_with( "called keywords may only take a single argument".to_string(), ); } match dict { Value::Dict(d) => self .push(d.get(&Key::Keyword(key)).unwrap_or(&Value::Nil).clone()), _ => self.push(Value::Nil), } } _ => { return self.panic_with(format!("{} is not a function", called.show())) } } } Call => { self.reduce(); let arity = self.read(); let called = self.pop(); if self.debug { println!("=== calling into {called}/{arity} ==="); } match called { Value::Process => { let args = self.stack.split_off(self.stack.len() - arity as usize); self.handle_msg(args); } Value::Fn(_) => { if !called.as_fn().accepts(arity) { return self.panic_with(format!( "wrong number of arguments to {} passing {arity} args", called.show() )); } let mut scrutinee = vec![]; for i in 0..arity { scrutinee.push( self.stack[self.stack.len() - arity as usize + i as usize] .clone(), ) } self.scrutinee = Some(Value::tuple(scrutinee)); let splat_arity = called.as_fn().splat_arity(); if splat_arity > 0 && arity >= splat_arity { let splatted_args = self.stack.split_off( self.stack.len() - (arity - splat_arity) as usize - 1, ); let gathered_args = Vector::from(splatted_args); self.push(Value::List(Box::new(gathered_args))); } let arity = if splat_arity > 0 { splat_arity.min(arity) } else { arity }; let mut frame = CallFrame { function: called, arity, stack_base: self.stack.len() - arity as usize, ip: 0, }; swap(&mut self.frame, &mut frame); frame.ip = self.ip; self.call_stack.push(frame); self.ip = 0; } Value::BaseFn(base_fn) => { let value = match (arity, *base_fn) { (0, BaseFn::Nullary(_, f)) => f(), (1, BaseFn::Unary(_, f)) => f(&self.pop()), (2, BaseFn::Binary(_, f)) => { let y = &self.pop(); let x = &self.pop(); f(x, y) } (3, BaseFn::Ternary(_, f)) => { let z = &self.pop(); let y = &self.pop(); let x = &self.pop(); f(x, y, z) } _ => { return self.panic_with( "internal ludus error: bad base fn call".to_string(), ) } }; self.push(value); } Value::Partial(partial) => { let last_arg = self.pop(); let args = &partial.args; for arg in args { if *arg == Value::Nothing { self.push(last_arg.clone()); } else { self.push(arg.clone()); } } let the_fn = partial.function.clone(); let mut frame = CallFrame { function: the_fn, arity: args.len() as u8, stack_base: self.stack.len() - args.len(), ip: 0, }; swap(&mut self.frame, &mut frame); frame.ip = self.ip; self.call_stack.push(frame); self.ip = 0; } Value::Keyword(key) => { let dict = self.pop(); if arity != 1 { return self.panic_with( "called keywords may only take a single argument".to_string(), ); } match dict { Value::Dict(d) => self .push(d.get(&Key::Keyword(key)).unwrap_or(&Value::Nil).clone()), _ => self.push(Value::Nil), } } _ => { return self.panic_with(format!("{} is not a function", called.show())) } } } Return => { if self.debug { println!("== returning from {} ==", self.frame.function.show()) } let mut value = Value::Nothing; swap(&mut self.register[0], &mut value); match self.call_stack.pop() { Some(frame) => { self.ip = frame.ip; self.frame = frame; self.push(value); } None => { println!("process {} has returned with {}", self.pid, value); self.result = Some(Ok(value)); return; } } } Print => { println!("{}", self.pop().show()); self.push(Value::Keyword("ok")); } SetUpvalue => { let value = self.pop(); let Value::Fn(lfn) = self.peek() else { panic!("expected function closing over value, got {}", self.peek()); }; lfn.close(value); } GetUpvalue => { let idx = self.read(); if let Value::Fn(ref inner) = self.frame.function { self.push(inner.as_ref().upvalue(idx)); } else { unreachable!(); } } NextMessage => { self.msg_idx += 1; } LoadMessage => match self.mbx.get(self.msg_idx) { Some(msg) => self.push(msg.clone()), None => { self.msg_idx = 0; self.r#yield = true; self.ip -= 2; } }, MatchMessage => { self.mbx.remove(self.msg_idx).unwrap(); } ClearMessage => { self.msg_idx = 0; } SendMethod => { let Value::Tuple(args) = self.pop() else { unreachable!("method args should be a tuple"); }; let method = self.pop(); let target = self.pop(); let mut msg = vec![method]; for arg in args.as_ref() { msg.push(arg.clone()); } self.send_msg(target, Value::tuple(msg)); } LoadScrutinee => { self.scrutinee = Some(self.peek().clone()); } Spawn => { let f = self.pop(); let proc = Creature::spawn(f, self.zoo.clone(), self.debug); let id = self.zoo.borrow_mut().put(proc); self.push(Value::Keyword(id)); } } } } }