use crate::chunk::{Chunk, StrPattern}; use crate::op::Op; use crate::parser::Ast; use crate::parser::StringPart; use crate::spans::Spanned; use crate::value::*; use chumsky::prelude::SimpleSpan; use regex::Regex; use std::cell::RefCell; use std::collections::HashMap; use std::rc::Rc; #[derive(Clone, Debug, PartialEq)] pub struct Binding { name: &'static str, depth: isize, stack_pos: usize, } impl std::fmt::Display for Binding { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { write!(f, "{}@{}//{}", self.name, self.stack_pos, self.depth) } } #[derive(Clone, Debug, PartialEq)] pub struct Upvalue { name: &'static str, stack_pos: usize, } #[derive(Debug, Clone, PartialEq)] struct LoopInfo { start: usize, stack_root: usize, } impl LoopInfo { fn new(start: usize, stack_root: usize) -> LoopInfo { LoopInfo { start, stack_root } } } fn get_builtin(name: &str, arity: usize) -> Option { // match (name, arity) { // ("type", 1) => Some(Op::TypeOf), // ("eq?", 2) => Some(Op::Eq), // ("add", 2) => Some(Op::Add), // ("sub", 2) => Some(Op::Sub), // ("mult", 2) => Some(Op::Mult), // ("div", 2) => Some(Op::Div), // ("unbox", 1) => Some(Op::Unbox), // ("store!", 2) => Some(Op::BoxStore), // ("assert!", 1) => Some(Op::Assert), // ("get", 2) => Some(Op::Get), // ("at", 2) => Some(Op::At), // ("not", 1) => Some(Op::Not), // ("print!", 1) => Some(Op::Print), // _ => None, // } None } #[derive(Debug, Clone)] pub struct Compiler { pub chunk: Chunk, pub bindings: Vec, pub scope_depth: isize, pub match_depth: usize, pub stack_depth: usize, pub spans: Vec, pub nodes: Vec<&'static Ast>, pub ast: &'static Ast, pub span: SimpleSpan, pub src: &'static str, pub name: &'static str, pub depth: usize, pub upvalues: Vec<&'static str>, loop_info: Vec, tail_pos: bool, debug: bool, } fn is_binding(expr: &Spanned) -> bool { let (ast, _) = expr; use Ast::*; match ast { Let(..) | LBox(..) | FnDeclaration(..) => true, Fn(name, ..) => !name.is_empty(), _ => false, } } fn has_placeholder(args: &[Spanned]) -> bool { args.iter().any(|arg| matches!(arg, (Ast::Placeholder, _))) } impl Compiler { pub fn new( ast: &'static Spanned, name: &'static str, src: &'static str, depth: usize, env: imbl::HashMap<&'static str, Value>, debug: bool, ) -> Compiler { let chunk = Chunk { constants: vec![], bytecode: vec![], keywords: vec![], string_patterns: vec![], env, msgs: vec![], }; Compiler { chunk, bindings: vec![], depth, scope_depth: -1, match_depth: 0, stack_depth: 0, spans: vec![], nodes: vec![], ast: &ast.0, span: ast.1, loop_info: vec![], upvalues: vec![], src, name, tail_pos: false, debug, } } pub fn visit(&mut self, node: &'static Spanned) { let root_node = self.ast; let root_span = self.span; let (ast, span) = node; self.ast = ast; self.span = *span; self.compile(); self.ast = root_node; self.span = root_span; } fn jump(&mut self, op: Op, len: usize) { let low = len as u8; let high = (len >> 8) as u8; self.emit_op(op); self.chunk.bytecode.push(high); self.chunk.bytecode.push(low); } fn stub_jump(&mut self, op: Op) -> usize { use Op::*; match op { JumpIfFalse | JumpIfTrue | JumpIfZero => self.stack_depth -= 1, _ => (), } let out = self.chunk.bytecode.len(); self.emit_op(op); self.emit_byte(0xff); self.emit_byte(0xff); out } fn patch_jump(&mut self, i: usize, len: usize) { let low = len as u8; let high = (len >> 8) as u8; self.chunk.bytecode[i + 1] = high; self.chunk.bytecode[i + 2] = low; } pub fn emit_constant(&mut self, val: Value) { let const_idx = if let Some(idx) = self.chunk.constants.iter().position(|v| *v == val) { idx } else { self.chunk.constants.push(val); self.chunk.constants.len() - 1 }; if const_idx > u16::MAX as usize { panic!( "internal Ludus compiler error: too many constants in chunk:{}:: {}", self.span, self.ast ) } self.emit_op(Op::Constant); let low = const_idx as u8; let high = (const_idx >> 8) as u8; self.chunk.bytecode.push(high); self.chunk.bytecode.push(low); self.stack_depth += 1; } fn reset_match(&mut self) { self.emit_op(Op::ResetMatch); self.match_depth = 0; } fn match_constant(&mut self, val: Value) { let const_idx = match self.chunk.constants.iter().position(|v| *v == val) { Some(idx) => idx, None => { self.chunk.constants.push(val); self.chunk.constants.len() - 1 } }; if const_idx > u16::MAX as usize { panic!( "internal Ludus compiler error: too many constants in chunk:{}:: {}", self.span, self.ast ) } self.emit_op(Op::MatchConstant); let low = const_idx as u8; let high = (const_idx >> 8) as u8; self.chunk.bytecode.push(high); self.chunk.bytecode.push(low); } fn emit_op(&mut self, op: Op) { self.chunk.bytecode.push(op as u8); self.spans.push(self.span); } fn emit_byte(&mut self, byte: usize) { self.chunk.bytecode.push(byte as u8); self.spans.push(self.span); } fn len(&self) -> usize { self.chunk.bytecode.len() } pub fn bind(&mut self, name: &'static str) { self.msg(format!("binding `{name}` in {}", self.name)); self.msg(format!( "stack depth: {}; match depth: {}", self.stack_depth, self.match_depth )); self.msg(format!( "at stack index: {}", self.stack_depth - self.match_depth - 1 )); let binding = Binding { name, depth: self.scope_depth, stack_pos: self.stack_depth - self.match_depth - 1, }; self.bindings.push(binding); self.msg(format!( "new locals: {}", self.bindings .iter() .map(|binding| format!("{binding}")) .collect::>() .join("|") )); } fn resolve_local(&self, name: &'static str) -> Option { for binding in self.bindings.iter() { if binding.name == name { return Some(binding.stack_pos); } } None } fn resolve_upvalue(&self, name: &'static str) -> Option { self.upvalues.iter().position(|uv| *uv == name) } fn resolve_binding(&mut self, name: &'static str) { self.msg(format!( "resolving binding `{name}` in {}\nlocals: {}", self.name, self.bindings .iter() .map(|binding| format!("{binding}")) .collect::>() .join("|") )); if let Some(pos) = self.resolve_local(name) { self.msg(format!("at locals position {pos}")); self.emit_op(Op::PushBinding); self.emit_byte(pos); self.stack_depth += 1; return; } if let Some(pos) = self.resolve_upvalue(name) { self.msg(format!("as upvalue {pos}")); self.emit_op(Op::GetUpvalue); self.emit_byte(pos); self.stack_depth += 1; return; } if self.depth == 0 { self.msg("as global".to_string()); self.emit_constant(Value::Keyword(name)); self.emit_op(Op::PushGlobal); } else { self.msg(format!("as enclosing upvalue {}", self.upvalues.len())); self.emit_op(Op::GetUpvalue); self.emit_byte(self.upvalues.len()); self.upvalues.push(name); self.stack_depth += 1; } } fn duplicate(&mut self) { self.emit_op(Op::Duplicate); self.stack_depth += 1; } fn pop(&mut self) { self.emit_op(Op::Pop); self.stack_depth -= 1; } fn pop_n(&mut self, n: usize) { match n { 0 => (), 1 => self.pop(), n => { self.emit_op(Op::PopN); self.emit_byte(n); self.stack_depth -= n; } } } fn store(&mut self) { self.emit_op(Op::Store); self.stack_depth -= 1; } fn store_n(&mut self, n: usize) { self.emit_op(Op::StoreN); self.emit_byte(n); self.stack_depth -= n; } fn load(&mut self) { self.emit_op(Op::Load); self.stack_depth += 1; } fn load_n(&mut self, n: usize) { self.emit_op(Op::LoadN); self.emit_byte(n); self.stack_depth += n; } fn enter_scope(&mut self) { self.scope_depth += 1; } fn leave_scope(&mut self) { self.msg(format!("leaving scope {}", self.scope_depth)); while let Some(binding) = self.bindings.last() { if binding.depth == self.scope_depth { let unbound = self.bindings.pop(); self.msg(format!("releasing binding {}", unbound.unwrap())); } else { break; } } self.scope_depth -= 1; } fn enter_loop(&mut self, arity: usize) { self.loop_info .push(LoopInfo::new(self.len(), self.stack_depth - arity)); } fn leave_loop(&mut self) { self.loop_info.pop(); } fn loop_info(&self) -> LoopInfo { self.loop_info.last().unwrap().clone() } fn loop_idx(&self) -> usize { self.loop_info.last().unwrap().start } fn loop_root(&self) -> usize { self.loop_info.last().unwrap().stack_root } fn msg(&mut self, str: String) { if self.debug { self.emit_op(Op::Msg); self.emit_byte(self.chunk.msgs.len()); println!("{str}"); self.chunk.msgs.push(str); } } fn report_depth(&mut self, label: &'static str) { self.msg(format!("***{label} stack depth: {}", self.stack_depth)); } fn report_depth_str(&mut self, label: String) { self.msg(format!("***{label} stack depth: {}", self.stack_depth)); } fn report_ast(&mut self, label: String, node: &'static Spanned) { self.msg(format!("***{label}: {}", node.0.show())) } pub fn compile(&mut self) { use Ast::*; match self.ast { Error => unreachable!(), Nil => { self.emit_op(Op::Nil); self.stack_depth += 1; } Number(n) => self.emit_constant(Value::Number(*n)), Boolean(b) => { self.emit_op(if *b { Op::True } else { Op::False }); self.stack_depth += 1; } String(s) => { self.emit_constant(Value::Interned(s)); } Keyword(s) => self.emit_constant(Value::Keyword(s)), Block(lines) => { let tail_pos = self.tail_pos; self.tail_pos = false; // increase the scope self.enter_scope(); // stash the stack depth let stack_depth = self.stack_depth; // evaluate all the lines but the last for expr in lines.iter().take(lines.len() - 1) { // evaluate the expression self.visit(expr); // if it doesn't bind a name, pop the result from the stack if !is_binding(expr) { self.pop(); } } // now, evaluate the last expression in the block let last_expr = lines.last().unwrap(); match last_expr { // if the last expression is a let form, // return the evaluated rhs instead of whatever is last on the stack // we do this by pretending it's a binding (Let(patt, expr), _) => { // self.match_depth = 0; self.visit(expr); let expr_pos = self.stack_depth - 1; self.report_ast("let binding: matching".to_string(), patt); self.reset_match(); self.visit(patt); self.emit_op(Op::PanicIfNoMatch); self.emit_op(Op::PushBinding); self.emit_byte(expr_pos); self.stack_depth += 1; } // otherwise, just evaluate it and leave the value on the stack _ => { self.tail_pos = tail_pos; self.visit(last_expr); } } // store the value in the return register self.store(); // reset the scope self.leave_scope(); // reset the stack self.report_depth("leaving block before pop"); self.msg(format!( "popping back from {} to {}", self.stack_depth, stack_depth, )); self.pop_n(self.stack_depth - stack_depth); // load the value from the return register self.load(); } If(cond, then, r#else) => { let tail_pos = self.tail_pos; self.tail_pos = false; self.visit(cond); self.report_depth("after condition"); let jif_idx = self.stub_jump(Op::JumpIfFalse); // self.stack_depth -= 1; self.tail_pos = tail_pos; self.visit(then); self.report_depth("after consequent"); let jump_idx = self.stub_jump(Op::Jump); self.stack_depth -= 1; self.visit(r#else); self.report_depth("after alternative"); // self.stack_depth += 1; let end_idx = self.len(); let jif_offset = jump_idx - jif_idx; let jump_offset = end_idx - jump_idx - 3; self.patch_jump(jif_idx, jif_offset); self.patch_jump(jump_idx, jump_offset); } Let(patt, expr) => { self.report_depth("before let binding"); // self.match_depth = 0; // self.emit_op(Op::ResetMatch); self.visit(expr); self.report_depth("after let expr"); self.report_ast("let binding: matching".to_string(), patt); self.reset_match(); self.visit(patt); self.emit_op(Op::PanicIfNoMatch); self.report_depth("after let binding"); } WordPattern(name) => { self.emit_op(Op::UnconditionalMatch); self.bind(name); } Word(name) | Splat(name) => self.resolve_binding(name), PlaceholderPattern => { self.emit_op(Op::UnconditionalMatch); } NilPattern => { self.emit_op(Op::MatchNil); } BooleanPattern(b) => { if *b { self.emit_op(Op::MatchTrue); } else { self.emit_op(Op::MatchFalse); } } NumberPattern(n) => { self.match_constant(Value::Number(*n)); } KeywordPattern(s) => { let existing_kw = self.chunk.keywords.iter().position(|kw| kw == s); let kw_index = match existing_kw { Some(index) => index, None => self.chunk.keywords.len(), }; if kw_index == self.chunk.keywords.len() { self.chunk.keywords.push(s); } self.match_constant(Value::Keyword(s)); } AsPattern(word, typ) => { self.emit_constant(Value::Keyword(typ)); self.emit_op(Op::MatchType); self.stack_depth -= 1; self.bind(word); } StringPattern(s) => { self.match_constant(Value::Interned(s)); } TuplePattern(members) => { // first, test the tuple against length // check if we're splatted // different opcodes w/ splats, but same logic let mut is_splatted = false; if let Some((Splattern(_), _)) = members.last() { is_splatted = true; self.emit_op(Op::MatchSplattedTuple); } else { self.emit_op(Op::MatchTuple); } self.emit_byte(members.len()); // skip everything if tuple lengths don't match let before_load_tup_idx = self.stub_jump(Op::JumpIfNoMatch); // set up the per-member conditional logic let mut jump_idxes = vec![]; // stash match_depth, and set it to the tuple len let match_depth = self.match_depth; self.match_depth = members.len(); // load the tuple and update the stack len if is_splatted { self.emit_op(Op::LoadSplattedTuple); self.emit_byte(members.len()); } else { self.emit_op(Op::LoadTuple); } self.stack_depth += members.len(); // visit each member for member in members { // reduce the match depth to start self.match_depth -= 1; self.emit_op(Op::MatchDepth); self.emit_byte(self.match_depth); // visit the pattern member self.visit(member); // and jump if there's no match jump_idxes.push(self.stub_jump(Op::JumpIfNoMatch)); } // if we get here--not having jumped on no match--we're matched; jump the "no match" code let jump_idx = self.stub_jump(Op::Jump); // patch up the previous no match jumps to jump to clean-up code for idx in jump_idxes { self.patch_jump(idx, self.len() - idx - 3) } // pop everything that was pushed // don't change the compiler stack representation, tho // we need this as cleanup code with no matches // the compiler should still have access to the bindings in this pattern self.emit_op(Op::PopN); self.emit_byte(members.len()); // patch up the tuple length match jump self.patch_jump(before_load_tup_idx, self.len() - before_load_tup_idx - 3); // patch up the yes-matches unconditional jump self.patch_jump(jump_idx, self.len() - jump_idx - 3); // finally, for any further matches (e.g. nested lists/tuples) // add increase the match depth, since we've added a bunch // of bindings to the stack self.match_depth = match_depth + members.len(); } ListPattern(members) => { let mut is_splatted = false; if let Some((Splattern(_), _)) = members.last() { is_splatted = true; self.emit_op(Op::MatchSplattedList) } else { self.emit_op(Op::MatchList); } // TODO: lists must be able to be longer than 256 elements; fix this self.emit_byte(members.len()); let before_load_tup_idx = self.stub_jump(Op::JumpIfNoMatch); let mut jump_idxes = vec![]; let match_depth = self.match_depth; self.match_depth = members.len(); if is_splatted { self.emit_op(Op::LoadSplattedList); self.emit_byte(members.len()); } else { self.emit_op(Op::LoadList); } self.stack_depth += members.len(); for member in members { self.match_depth -= 1; self.emit_op(Op::MatchDepth); self.emit_byte(self.match_depth); self.visit(member); jump_idxes.push(self.stub_jump(Op::JumpIfNoMatch)); } let jump_idx = self.stub_jump(Op::Jump); for idx in jump_idxes { self.patch_jump(idx, self.len() - idx - 3) } self.emit_op(Op::PopN); self.emit_byte(members.len()); self.patch_jump(before_load_tup_idx, self.len() - before_load_tup_idx - 3); self.patch_jump(jump_idx, self.len() - jump_idx - 3); self.match_depth = match_depth + members.len(); } DictPattern(pairs) => { // here's an algorithm for dealing with splatted dicts // check len to see it's at least as long as the pattern // then, match against all the values // then push the dict itself as last value // and then emit an opcode and constant/keyword to OMIT that key from the dict let mut is_splatted = false; if let Some((Splattern(_), _)) = pairs.last() { is_splatted = true; self.emit_op(Op::MatchSplattedDict); } else { self.emit_op(Op::MatchDict); } self.emit_byte(pairs.len()); let before_load_dict_idx = self.stub_jump(Op::JumpIfNoMatch); let mut jump_idxes = vec![]; let dict_stack_pos = self.stack_depth - self.match_depth - 1; let mut splattern = None; let mut pairs_len = pairs.len(); if is_splatted { splattern = pairs.last(); pairs_len -= 1; } let match_depth = self.match_depth; self.match_depth = 0; for pair in pairs.iter().take(pairs_len) { let (PairPattern(key, pattern), _) = pair else { unreachable!() }; self.emit_constant(Value::Keyword(key)); self.emit_op(Op::LoadDictValue); self.emit_byte(dict_stack_pos); self.visit(pattern); jump_idxes.push(self.stub_jump(Op::JumpIfNoMatch)); } if is_splatted { // pull the dict out of the stack // drop every value in the pattern self.emit_op(Op::PushBinding); self.emit_byte(dict_stack_pos); self.stack_depth += 1; for pair in pairs.iter().take(pairs_len) { let (PairPattern(key, _), _) = pair else { unreachable!() }; self.emit_constant(Value::Keyword(key)); self.emit_op(Op::DropDictEntry); self.stack_depth -= 1; } if let Some(splatt) = splattern { self.visit(splatt); } } self.match_depth = match_depth + pairs.len(); let jump_idx = self.stub_jump(Op::Jump); for idx in jump_idxes { self.patch_jump(idx, self.len() - idx - 3); } // do this explicitly to keep compiler & vm stacks in sync self.emit_op(Op::Pop); self.emit_byte(pairs.len()); self.patch_jump(before_load_dict_idx, self.len() - before_load_dict_idx - 3); self.patch_jump(jump_idx, self.len() - jump_idx - 3); } Splattern(patt) => self.visit(patt), InterpolatedPattern(parts, _) => { // println!("An interpolated pattern of {} parts", parts.len()); let mut pattern = "".to_string(); let mut words = vec![]; for (part, _) in parts { match part { StringPart::Word(word) => { // println!("wordpart: {word}"); words.push(word.clone()); pattern.push_str("(.*)"); } StringPart::Data(data) => { // println!("datapart: {data}"); let data = regex::escape(data); pattern.push_str(data.as_str()); } StringPart::Inline(..) => unreachable!(), } } let re = Regex::new(pattern.as_str()).unwrap(); let moar_words = words.clone(); let string_pattern = StrPattern { words, re }; let pattern_idx = self.chunk.string_patterns.len(); self.chunk.string_patterns.push(string_pattern); self.emit_op(Op::MatchString); self.emit_byte(pattern_idx); let jnm_idx = self.stub_jump(Op::JumpIfNoMatch); self.emit_op(Op::PushStringMatches); self.emit_byte(pattern_idx); for word in moar_words { let name: &'static str = std::string::String::leak(word); let binding = Binding { name, depth: self.scope_depth, stack_pos: self.stack_depth, }; self.bindings.push(binding); self.stack_depth += 1; } self.patch_jump(jnm_idx, self.len() - jnm_idx - 3); } PairPattern(_, _) => unreachable!(), Tuple(members) => { self.tail_pos = false; for member in members { self.visit(member); } self.emit_op(Op::PushTuple); self.emit_byte(members.len()); self.stack_depth = self.stack_depth + 1 - members.len(); } List(members) => { self.tail_pos = false; self.emit_op(Op::PushList); self.stack_depth += 1; for member in members { self.visit(member); if matches!(member, (Splat(..), _)) { self.emit_op(Op::ConcatList); } else { self.emit_op(Op::AppendList); } self.stack_depth -= 1; } } LBox(name, expr) => { self.tail_pos = false; self.visit(expr); self.emit_op(Op::PushBox); self.bind(name); } Dict(pairs) => { self.tail_pos = false; self.emit_op(Op::PushDict); self.stack_depth += 1; for pair in pairs.iter().rev() { self.visit(pair); if matches!(pair, (Splat(..), _)) { self.emit_op(Op::ConcatDict); self.stack_depth -= 1; } else { self.emit_op(Op::AppendDict); self.stack_depth -= 2; } } } Pair(key, value) => { self.tail_pos = false; self.emit_constant(Value::Keyword(key)); self.visit(value); } // TODO: thread tail position through this Synthetic(first, second, rest) => { let tail_pos = self.tail_pos; self.tail_pos = false; match (&first.0, &second.0) { (Word(name), Keyword(key)) => { self.report_depth_str(format!("accessing keyword: {name} :{key}")); self.visit(first); self.visit(second); self.emit_op(Op::GetKey); self.stack_depth -= 1; self.report_depth("after keyword access"); } (Keyword(_), Arguments(args)) => { self.visit(&args[0]); self.visit(first); self.emit_op(Op::GetKey); self.stack_depth -= 1; } (Or, Arguments(args)) => { let mut jump_idxes = vec![]; if !args.is_empty() { let mut args = args.iter().rev(); let last = args.next().unwrap(); for arg in args.rev() { self.visit(arg); self.duplicate(); jump_idxes.push(self.stub_jump(Op::JumpIfTrue)); self.pop(); } self.visit(last); for idx in jump_idxes { self.patch_jump(idx, self.len() - idx - 3); } } else { self.emit_op(Op::False); self.stack_depth += 1; } } (And, Arguments(args)) => { let mut jump_idxes = vec![]; if !args.is_empty() { let mut args = args.iter().rev(); let last = args.next().unwrap(); for arg in args.rev() { self.visit(arg); self.duplicate(); jump_idxes.push(self.stub_jump(Op::JumpIfFalse)); self.pop(); } self.visit(last); for idx in jump_idxes { self.patch_jump(idx, self.len() - idx - 3); } } else { self.emit_op(Op::True); self.stack_depth += 1; } } (Word(fn_name), Arguments(args)) => { self.report_depth_str(format!("calling function {fn_name}")); if has_placeholder(args) { let arity = args.len(); for arg in args { self.visit(arg); } self.resolve_binding(fn_name); self.emit_op(Op::Partial); self.emit_byte(arity); self.stack_depth -= args.len() - 1; } else { match get_builtin(fn_name, args.len()) { Some(code) => { for arg in args { self.visit(arg); } self.emit_op(code); self.stack_depth -= args.len() - 1; } None => { let arity = args.len(); for arg in args { self.visit(arg); } self.resolve_binding(fn_name); // if we're in tail position AND there aren't any rest args, this should be a tail call (I think) self.report_depth_str(format!("after {arity} args")); if rest.is_empty() && tail_pos { self.emit_op(Op::TailCall); } else { self.emit_op(Op::Call); } self.emit_byte(arity); self.stack_depth -= arity; } } } } _ => unreachable!(), } // the last term in rest should be in tail position if we are in tail position let num_rest_terms = rest.len(); for (i, (term, _)) in rest.iter().enumerate() { match term { Keyword(str) => { self.emit_constant(Value::Keyword(str)); self.emit_op(Op::GetKey); self.stack_depth -= 1; } Arguments(args) => { self.store(); let arity = args.len(); for arg in args { self.visit(arg); } self.load(); if tail_pos && i == num_rest_terms - 1 { self.emit_op(Op::TailCall) } else { self.emit_op(Op::Call); } self.emit_byte(arity); self.stack_depth -= arity; } _ => unreachable!(), } } self.tail_pos = tail_pos; } When(clauses) => { let tail_pos = self.tail_pos; let mut jump_idxes = vec![]; let mut clauses = clauses.iter(); while let Some((WhenClause(cond, body), _)) = clauses.next() { self.tail_pos = false; self.visit(cond.as_ref()); let jif_jump_idx = self.stub_jump(Op::JumpIfFalse); self.tail_pos = tail_pos; self.visit(body); self.stack_depth -= 1; jump_idxes.push(self.stub_jump(Op::Jump)); self.patch_jump(jif_jump_idx, self.len() - jif_jump_idx - 3); } self.emit_op(Op::PanicNoWhen); for idx in jump_idxes { self.patch_jump(idx, self.len() - idx - 3); } self.stack_depth += 1; } WhenClause(..) => unreachable!(), Match(scrutinee, clauses) => { let tail_pos = self.tail_pos; self.tail_pos = false; self.visit(scrutinee.as_ref()); let stack_depth = self.stack_depth; let mut jump_idxes = vec![]; let mut clauses = clauses.iter(); while let Some((MatchClause(pattern, guard, body), _)) = clauses.next() { self.tail_pos = false; let mut no_match_jumps = vec![]; self.report_ast("match clause: ".to_string(), pattern); // self.scope_depth += 1; self.enter_scope(); self.match_depth = 0; self.visit(pattern); no_match_jumps.push(self.stub_jump(Op::JumpIfNoMatch)); if guard.is_some() { let guard_expr: &'static Spanned = Box::leak(Box::new(guard.clone().unwrap())); self.visit(guard_expr); no_match_jumps.push(self.stub_jump(Op::JumpIfFalse)); } self.tail_pos = tail_pos; self.visit(body); self.store(); self.leave_scope(); self.pop_n(self.stack_depth - stack_depth); jump_idxes.push(self.stub_jump(Op::Jump)); for idx in no_match_jumps { self.patch_jump(idx, self.len() - idx - 3); } } self.emit_op(Op::PanicNoMatch); for idx in jump_idxes { self.patch_jump(idx, self.len() - idx - 3); } self.pop_n(self.stack_depth - stack_depth); self.emit_op(Op::Load); self.stack_depth += 1; } MatchClause(..) => unreachable!(), Fn(name, body, doc) => { let is_anon = name.is_empty(); let mut name = name; if !is_anon { let declared = self.chunk.constants.iter().any(|val| match val { Value::Fn(lfn) => { if matches!(lfn.as_ref(), LFn::Declared { .. }) { lfn.name() == *name } else { false } } _ => false, }); if !declared { let declaration = Value::Fn(Rc::new(LFn::Declared { name })); self.emit_constant(declaration); self.bind(name); } } else { name = &"_anon"; } let FnBody(fn_body) = &body.as_ref().0 else { unreachable!() }; let mut compilers: HashMap = HashMap::new(); let mut upvalues = vec![]; let mut has_splat = false; for clause in fn_body { let MatchClause(pattern, guard, clause_body) = &clause.0 else { unreachable!() }; let full_pattern = pattern; let TuplePattern(pattern) = &pattern.0 else { unreachable!() }; if matches!(pattern.last(), Some((Splattern(_), _))) { has_splat = true; }; let arity = pattern.len() as u8; let compiler = match compilers.get_mut(&arity) { Some(compiler) => compiler, None => { let mut compiler = Compiler::new( clause, name, self.src, self.depth + 1, self.chunk.env.clone(), self.debug, ); compiler.reset_match(); // compiler.emit_op(Op::ResetMatch); compilers.insert(arity, compiler); compilers.get_mut(&arity).unwrap() } }; compiler.tail_pos = false; compiler.stack_depth += arity as usize; compiler.scope_depth += 1; compiler.match_depth = arity as usize; std::mem::swap(&mut upvalues, &mut compiler.upvalues); let mut tup_jump_idxes = vec![]; compiler.report_ast("function clause matching: ".to_string(), full_pattern); for member in pattern { compiler.match_depth -= 1; compiler.emit_op(Op::MatchDepth); compiler.emit_byte(compiler.match_depth); compiler.visit(member); tup_jump_idxes.push(compiler.stub_jump(Op::JumpIfNoMatch)); } if pattern.is_empty() { compiler.emit_op(Op::UnconditionalMatch); } let jump_idx = compiler.stub_jump(Op::Jump); for idx in tup_jump_idxes { compiler.patch_jump(idx, compiler.len() - idx - 3); } // compiler.emit_op(Op::PopN); // compiler.emit_byte(arity as usize); compiler.patch_jump(jump_idx, compiler.len() - jump_idx - 3); let mut no_match_jumps = vec![]; no_match_jumps.push(compiler.stub_jump(Op::JumpIfNoMatch)); if guard.is_some() { let guard_expr: &'static Spanned = Box::leak(Box::new(guard.clone().unwrap())); compiler.visit(guard_expr); no_match_jumps.push(compiler.stub_jump(Op::JumpIfFalse)); compiler.stack_depth -= 1; } compiler.tail_pos = true; compiler.visit(clause_body); compiler.store(); compiler.scope_depth -= 1; while let Some(binding) = compiler.bindings.last() { if binding.depth > compiler.scope_depth { compiler.bindings.pop(); } else { break; } } compiler.pop_n(compiler.stack_depth); compiler.stack_depth = 0; compiler.emit_op(Op::Return); for idx in no_match_jumps { compiler.patch_jump(idx, compiler.len() - idx - 3); } // compiler.scope_depth -= 1; std::mem::swap(&mut compiler.upvalues, &mut upvalues); } let mut compilers = compilers.into_iter().collect::>(); compilers.sort_by(|(a, _), (b, _)| a.cmp(b)); let mut arities = vec![]; let mut chunks = vec![]; for (arity, mut compiler) in compilers { compiler.emit_op(Op::PanicNoMatch); let chunk = compiler.chunk; if self.debug { println!("=== function chuncktion: {name}/{arity} ==="); chunk.dissasemble(); } arities.push(arity); chunks.push(chunk); } let splat = if has_splat { arities.iter().fold(0, |max, curr| max.max(*curr)) } else { 0 }; let lfn = crate::value::LFn::Defined { name, doc: *doc, arities, chunks, splat, closed: RefCell::new(vec![]), }; let the_fn = Value::Fn(Rc::new(lfn)); // self.emit_constant(the_fn); // self.bind(name); if !is_anon { let declaration_idx = self .chunk .constants .iter() .position(|val| match val { Value::Fn(lfn) => { if matches!(lfn.as_ref(), LFn::Declared { .. }) { lfn.name() == *name } else { false } } _ => false, }) .unwrap(); self.chunk.constants[declaration_idx] = the_fn; // if the function been forward-declared, bring it to the top of the stack if declaration_idx < self.chunk.constants.len() - 1 { self.resolve_binding(name); } } else { self.emit_constant(the_fn) } for upvalue in upvalues { self.resolve_binding(upvalue); self.emit_op(Op::SetUpvalue); self.stack_depth -= 1; } } FnDeclaration(name) => { let lfn = Value::Fn(Rc::new(LFn::Declared { name })); self.emit_constant(lfn); self.bind(name); } FnBody(_) => unreachable!(), Repeat(times, body) => { let tail_pos = self.tail_pos; self.tail_pos = false; self.visit(times); self.emit_op(Op::ToInt); // skip the decrement the first time self.emit_op(Op::Jump); self.emit_byte(0); self.emit_byte(1); // begin repeat self.emit_op(Op::Decrement); let repeat_begin = self.len(); self.duplicate(); let jiz_idx = self.stub_jump(Op::JumpIfZero); // compile the body self.visit(body); // pop whatever value the body returns self.pop(); let jump_back = self.stub_jump(Op::JumpBack); // set jump points self.patch_jump(jump_back, self.len() - repeat_begin - 2); self.patch_jump(jiz_idx, self.len() - repeat_begin - 4); self.pop(); self.emit_constant(Value::Nil); self.tail_pos = tail_pos; } Loop(value, clauses) => { self.report_depth("entering loop"); let tail_pos = self.tail_pos; self.tail_pos = false; //algo: //first, put the values on the stack let (Ast::Tuple(members), _) = value.as_ref() else { unreachable!() }; for member in members { self.visit(member); } self.report_depth("after loop args"); let arity = members.len(); // self.emit_op(Op::StoreN); // self.emit_byte(members.len()); self.store_n(arity); let stack_depth = self.stack_depth; self.report_depth("loop: after store"); //then, save the beginning of the loop // self.emit_op(Op::Load); self.load_n(arity); self.enter_loop(arity); // self.stack_depth += arity; //next, compile each clause: let mut clauses = clauses.iter(); let mut jump_idxes = vec![]; while let Some((Ast::MatchClause(pattern, guard, body), _)) = clauses.next() { self.tail_pos = false; self.report_depth("loop: after load"); self.reset_match(); // self.emit_op(Op::ResetMatch); self.enter_scope(); // self.scope_depth += 1; let (Ast::TuplePattern(members), _) = pattern.as_ref() else { unreachable!() }; self.match_depth = arity; let mut jnm_idxes = vec![]; self.report_ast("loop clause matching: ".to_string(), pattern); for member in members { self.match_depth -= 1; self.emit_op(Op::MatchDepth); self.emit_byte(self.match_depth); self.visit(member); jnm_idxes.push(self.stub_jump(Op::JumpIfNoMatch)); } if guard.is_some() { let guard_expr: &'static Spanned = Box::leak(Box::new(guard.clone().unwrap())); self.visit(guard_expr); jnm_idxes.push(self.stub_jump(Op::JumpIfFalse)); } self.tail_pos = tail_pos; self.report_depth("loop: before body"); self.visit(body); self.report_depth("loop: after body, before store"); // self.emit_op(Op::Store); self.store(); self.report_depth("loop: after body, after store"); self.leave_scope(); self.report_depth_str(format!( "resetting the stack after loop from {} to {stack_depth}", self.stack_depth, )); self.pop_n(self.stack_depth - stack_depth); // while self.stack_depth > stack_depth { // self.pop(); // } jump_idxes.push(self.stub_jump(Op::Jump)); for idx in jnm_idxes { self.patch_jump(idx, self.len() - idx - 3); } self.stack_depth += arity; } self.emit_op(Op::PanicNoMatch); for idx in jump_idxes { self.patch_jump(idx, self.len() - idx - 3); } self.report_depth("before loop arity adjustment"); self.stack_depth -= arity; // pop back to the original depth before load // i.e. clear loop args // self.pop(); // self.emit_op(Op::Load); self.load(); // self.stack_depth += 1; self.leave_loop(); self.report_depth("at very end of loop after load"); } Recur(args) => { // self.emit_op(Op::Nothing); self.report_depth("recur: before args"); let tail_pos = self.tail_pos; self.tail_pos = false; let mut argnum = 0; for arg in args { self.msg(format!("recur arg: {argnum}")); argnum += 1; self.visit(arg); } self.report_depth("recur: after args"); self.store_n(args.len()); self.report_depth("recur: after store"); self.msg(format!("loop root depth: {}", self.loop_root())); self.pop_n(self.stack_depth - self.loop_root()); self.report_depth("recur: after stack reset"); self.load_n(args.len()); self.report_depth("recur: after load, end of compilation"); self.jump(Op::JumpBack, self.len() - self.loop_idx()); self.tail_pos = tail_pos; } Panic(msg) => { self.visit(msg); self.emit_op(Op::Panic); } Interpolated(parts) => { self.emit_op(Op::EmptyString); self.stack_depth += 1; for part in parts { let str = &part.0; match str { StringPart::Inline(_) => unreachable!(), StringPart::Data(str) => { let allocated = Value::String(Rc::new(str.clone())); self.emit_constant(allocated); self.emit_op(Op::ConcatStrings); } StringPart::Word(word) => { self.resolve_binding(word); self.emit_op(Op::Stringify); self.emit_op(Op::ConcatStrings); } } self.stack_depth -= 1; } } Do(terms) => { let mut terms = terms.iter(); let first = terms.next().unwrap(); let mut terms = terms.rev(); let last = terms.next().unwrap(); let terms = terms.rev(); // put the first value on the stack let tail_pos = self.tail_pos; self.tail_pos = false; self.visit(first); for term in terms { self.visit(term); self.emit_op(Op::Call); self.emit_byte(1); self.stack_depth -= 1; } self.visit(last); if tail_pos { self.emit_op(Op::TailCall) } else { self.emit_op(Op::Call); } self.emit_byte(1); self.tail_pos = tail_pos; self.stack_depth -= 1; } Placeholder => { self.emit_op(Op::Nothing); } And | Or | Arguments(..) => unreachable!(), } } pub fn disassemble(&self) { println!("=== chunk: {} ===", self.name); self.chunk.dissasemble(); } }