use std::collections::HashMap; use std::rc::Rc; use crate::{ parser::{Child, SyntaxTree, Tree, TreeKind, TreeRef}, semantics::{string_constant_to_string, Declaration, Environment, Location, Semantics, Type}, tokens::TokenKind, }; pub const EXTERN_BUILTIN_NOOP: usize = 0; pub const EXTERN_BUILTIN_LIST_GET_ITERATOR: usize = 1; pub const EXTERN_BUILTIN_LIST_ITERATOR_NEXT: usize = 2; pub const EXTERN_USER_FIRST: usize = 100000; // TODO: If I were cool this would by actual bytecode. // But I'm not cool. #[derive(Debug, Clone, Copy)] pub enum Instruction { Panic, BoolNot, Call(usize), Discard, Dup, EqBool, EqFloat, EqString, FloatAdd, FloatDivide, FloatMultiply, FloatSubtract, GreaterFloat, GreaterString, IsBool, IsClass(i64), IsFloat, IsNothing, IsString, Jump(usize), JumpFalse(usize), JumpTrue(usize), // TODO: Only one of these, and use BoolNot? LessFloat, LessString, LoadArgument(usize), LoadExternFunction(usize), // NOTE: FUNKY, might want to indirect this index. LoadFunction(usize), LoadLocal(usize), LoadModule(usize), LoadSlot(usize), NewObject(usize), PushFalse, PushFloat(f64), PushInt(i64), PushNothing, PushString(usize), PushTrue, Return, StoreArgument(usize), StoreLocal(usize), StoreModule(usize), StoreSlot(usize), StringAdd, NewList(usize), } pub enum Export { Function(usize), Global(usize), } pub struct Module { pub functions: Vec>, // Functions pub globals: usize, // The number of global variables pub exports: HashMap, // Exports by name pub init: usize, // The index of the initialization function } impl Module { pub fn new() -> Self { Module { functions: Vec::new(), globals: 0, exports: HashMap::new(), init: 0, } } pub fn functions(&self) -> &[Rc] { &self.functions } } // TODO: Debug information. pub struct Function { name: String, instructions: Vec, strings: Vec>, args: usize, // TODO: Probably type information too? locals: usize, // TODO: Same? } impl Function { pub fn new(name: &str, args: usize) -> Self { Function { name: name.to_string(), instructions: Vec::new(), strings: Vec::new(), args, locals: 0, } } pub fn name(&self) -> &str { &self.name } pub fn args(&self) -> usize { self.args } pub fn locals(&self) -> usize { self.locals } pub fn strings(&self) -> &[Rc] { &self.strings } pub fn instructions(&self) -> &[Instruction] { &self.instructions } } impl std::fmt::Debug for Function { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!( f, "fn {} ({} args, {} locals) ...", self.name, self.args, self.locals ) } } #[derive(Eq, PartialEq, Hash, Clone)] struct FunctionKey { tree: TreeRef, } struct Compiler<'a> { source: &'a str, semantics: &'a Semantics, syntax: &'a SyntaxTree, function_bindings: HashMap, pending_functions: Vec<(FunctionKey, usize, Function)>, temp_functions: Vec>>, module: Module, function: Function, } impl<'a> Compiler<'a> { fn add_string(&mut self, result: String) -> usize { let index = self.function.strings.len(); self.function.strings.push(result.into()); index } fn push(&mut self, inst: Instruction) -> usize { let index = self.function.instructions.len(); self.function.instructions.push(inst); index } fn patch(&mut self, i: usize, f: impl FnOnce(usize) -> Instruction) { let index = self.function.instructions.len(); self.function.instructions[i] = f(index); } } macro_rules! compiler_assert_eq { ($compiler:expr, $tr:expr, $ll:expr, $rr:expr $(,)?) => {{ let left = &$ll; let right = &$rr; if left != right { $compiler.semantics.dump_compiler_state(Some($tr)); assert_eq!(left, right); } }}; ($compiler:expr, $tr:expr, $ll:expr, $rr:expr, $($t:tt)+) => {{ let left = &$ll; let right = &$rr; if left != right { $compiler.semantics.dump_compiler_state(Some($tr)); assert_eq!(left, right, $($t)*); } }}; } macro_rules! compiler_assert { ($compiler:expr, $tr:expr, $cond:expr $(,)?) => {{ if !$cond { $compiler.semantics.dump_compiler_state(Some($tr)); assert!($cond); } }}; ($compiler:expr, $tr:expr, $cond:expr, $($arg:tt)+) => {{ if !$cond { $compiler.semantics.dump_compiler_state(Some($tr)); assert!($cond, $($arg)*); } }}; } macro_rules! ice { ($compiler:expr, $tr:expr, $($t:tt)+) => {{ $compiler.semantics.dump_compiler_state(Some($tr)); panic!($($t)*) }} } macro_rules! inst_panic { ($($t:tt)+) => {{ // eprintln!($($t)*); Instruction::Panic }}; } // macro_rules! ice { // ($compiler:expr, $tr:expr, $($t:tt)*) => {{}}; // } pub fn compile(semantics: &Semantics) -> Rc { let source = semantics.source(); let syntax_tree = semantics.tree(); let mut compiler = Compiler { source: &source, semantics: &semantics, syntax: &syntax_tree, function_bindings: HashMap::new(), pending_functions: Vec::new(), temp_functions: Vec::new(), module: Module::new(), function: Function::new("<< module >>", 0), }; if let Some(t) = semantics.tree().root() { compiler.temp_functions.push(None); file(&mut compiler, t); compiler.temp_functions[0] = Some(Rc::new(compiler.function)); compiler.module.init = 0; } while let Some((fk, idx, func)) = compiler.pending_functions.pop() { if idx >= compiler.temp_functions.len() { compiler.temp_functions.resize(idx + 1, None); } compiler.function = func; compile_function(&mut compiler, fk.tree); compiler.temp_functions[idx] = Some(Rc::new(compiler.function)); } let mut module = compiler.module; for f in compiler.temp_functions { module.functions.push(f.unwrap()); } Rc::new(module) } fn file(c: &mut Compiler, t: TreeRef) { let tree = &c.syntax[t]; compiler_assert_eq!(c, t, tree.kind, TreeKind::File, "must be compiling a file"); let children: Vec<_> = tree.child_trees().collect(); if children.len() == 0 { c.push(Instruction::PushNothing); } else { for i in 0..children.len() - 1 { compile_statement(c, children[i], false); } compile_statement(c, *children.last().unwrap(), true); } c.push(Instruction::Return); } type CR = Option<()>; const OK: CR = CR::Some(()); fn compile_expression(c: &mut Compiler, t: TreeRef) { let tree = &c.syntax[t]; let cr = match tree.kind { TreeKind::Error => None, TreeKind::Argument => compile_argument(c, tree), TreeKind::BinaryExpression => compile_binary_expression(c, t, tree), TreeKind::Block => compile_block_expression(c, tree), TreeKind::CallExpression => compile_call_expression(c, tree), TreeKind::ConditionalExpression => compile_condition_expression(c, tree), TreeKind::FieldValue => compile_field_value(c, t, tree), TreeKind::GroupingExpression => compile_grouping(c, tree), TreeKind::Identifier => compile_identifier_expression(c, t, tree), TreeKind::IsExpression => compile_is_expression(c, tree), TreeKind::ListConstructor => compile_list_constructor(c, tree), TreeKind::ListConstructorElement => compile_list_constructor_element(c, tree), TreeKind::LiteralExpression => compile_literal(c, t, tree), TreeKind::MemberAccess => compile_member_access(c, t, tree), TreeKind::NewObjectExpression => compile_new_object_expression(c, t, tree), TreeKind::SelfReference => compile_self_reference(c), TreeKind::UnaryExpression => compile_unary_operator(c, t, tree), _ => ice!(c, t, "{tree:?} is not an expression, cannot compile"), }; if matches!(cr, None) { c.push(inst_panic!("panic compiling expression {:?}", tree)); } } fn compile_literal(c: &mut Compiler, t: TreeRef, tr: &Tree) -> CR { let tok = tr.nth_token(0)?; match c.semantics.type_of(t) { Type::F64 => c.push(Instruction::PushFloat( tok.as_str(c.source).parse().unwrap(), )), Type::Bool => c.push(if tok.kind == TokenKind::True { Instruction::PushTrue } else { Instruction::PushFalse }), Type::String => { let result = string_constant_to_string(tok.as_str(c.source)); let index = c.add_string(result); c.push(Instruction::PushString(index)) } Type::Error => c.push(inst_panic!("compiling literal {:?}", tr)), _ => ice!(c, t, "unsupported literal type: {t:?}"), }; OK } fn compile_grouping(c: &mut Compiler, t: &Tree) -> CR { compile_expression(c, t.nth_tree(1)?); OK } fn compile_unary_operator(c: &mut Compiler, t: TreeRef, tr: &Tree) -> CR { compile_expression(c, tr.nth_tree(1)?); let tok = tr.nth_token(0)?; match tok.kind { TokenKind::Minus => { c.push(Instruction::PushFloat(-1.0)); c.push(Instruction::FloatMultiply); } TokenKind::Bang => { c.push(Instruction::BoolNot); } _ => ice!(c, t, "unsupported unary operator"), } OK } fn compile_condition_expression(c: &mut Compiler, t: &Tree) -> CR { let condition = t.nth_tree(1)?; compile_expression(c, condition); let jump_else_index = c.push(Instruction::JumpFalse(0)); let then_branch = t.nth_tree(2)?; compile_expression(c, then_branch); let jump_end_index = c.push(Instruction::Jump(0)); c.patch(jump_else_index, |i| Instruction::JumpFalse(i)); if let Some(else_branch) = t.nth_tree(4) { compile_expression(c, else_branch); } else { c.push(Instruction::PushNothing); } c.patch(jump_end_index, |i| Instruction::Jump(i)); OK } fn compile_simple_binary_expression(c: &mut Compiler, tr: &Tree, f: T) -> CR where T: FnOnce(&mut Compiler, &Type) -> Instruction, { compile_expression(c, tr.nth_tree(0)?); let arg_tree = tr.nth_tree(2)?; let arg_type = c.semantics.type_of(arg_tree); compile_expression(c, arg_tree); let inst = f(c, &arg_type); c.push(inst); OK } fn compile_binary_expression(c: &mut Compiler, t: TreeRef, tr: &Tree) -> CR { let op = tr.nth_token(1)?; match op.kind { TokenKind::Plus => compile_simple_binary_expression(c, tr, |_, t| match t { Type::F64 => Instruction::FloatAdd, Type::String => Instruction::StringAdd, _ => inst_panic!("panic adding {}", t), }), TokenKind::Minus => { compile_simple_binary_expression(c, tr, |_, _| Instruction::FloatSubtract) } TokenKind::Star => { compile_simple_binary_expression(c, tr, |_, _| Instruction::FloatMultiply) } TokenKind::Slash => { compile_simple_binary_expression(c, tr, |_, _| Instruction::FloatDivide) } TokenKind::Less => compile_simple_binary_expression(c, tr, |_, t| match t { Type::F64 => Instruction::LessFloat, Type::String => Instruction::LessString, _ => inst_panic!("panic less {}", t), }), TokenKind::LessEqual => { compile_simple_binary_expression(c, tr, |_, t| match t { Type::F64 => Instruction::GreaterFloat, Type::String => Instruction::GreaterString, _ => inst_panic!("panic less equal {}", t), }); c.push(Instruction::BoolNot); OK } TokenKind::Greater => compile_simple_binary_expression(c, tr, |_, t| match t { Type::F64 => Instruction::GreaterFloat, Type::String => Instruction::GreaterString, _ => inst_panic!("panic greater {}", t), }), TokenKind::GreaterEqual => { compile_simple_binary_expression(c, tr, |_, t| match t { Type::F64 => Instruction::LessFloat, Type::String => Instruction::LessString, _ => inst_panic!("panic greater equal {}", t), }); c.push(Instruction::BoolNot); OK } TokenKind::And => { // Compile the left hand side, it leaves a bool on the stack compile_expression(c, tr.nth_tree(0)?); // If the first part is true (hooray!) then we need to evaluate // the right hand side, so jump around the short circuit... let jump_true_index = c.push(Instruction::JumpTrue(0)); // ...but if the first part is false then we stop here. We need // to leave a value on the stack (it was consumed by jump above) // so we push an extra False here, and jump to the end. c.push(Instruction::PushFalse); let jump_end_index = c.push(Instruction::Jump(0)); // Here we are, we consumed the `true` off the stack now time to // do the right hand side. c.patch(jump_true_index, |i| Instruction::JumpTrue(i)); // The right hand side leaves true or false on the stack, it's // the result of the expression. compile_expression(c, tr.nth_tree(2)?); // (here's where you go after you leave the "false" on the stack.) c.patch(jump_end_index, |i| Instruction::Jump(i)); OK } TokenKind::Or => { // Compile the left hand side, it leaves a bool on the stack compile_expression(c, tr.nth_tree(0)?); // If the first part is false (boo!) then we need to evaluate the // right hand side, so jump around the short circuit... let jump_false_index = c.push(Instruction::JumpFalse(0)); // ...but if the first part os true then we stop here. We need to // leave a value on the stack (it was consumed by jump above) so // we push an extra True here and jump to the end. c.push(Instruction::PushTrue); let jump_end_index = c.push(Instruction::Jump(0)); // Here we are, we consumed the `false` off the stack now time to // do the right hand side. c.patch(jump_false_index, |i| Instruction::JumpFalse(i)); // The right hand side leaves true or false on the stack, it's // the result of the expression. compile_expression(c, tr.nth_tree(2)?); // (here's where you go after you leave "true" on the stack.) c.patch(jump_end_index, |i| Instruction::Jump(i)); OK } TokenKind::EqualEqual => { compile_simple_binary_expression(c, tr, |c, arg_type| { if c.semantics.can_convert(&arg_type, &Type::Nothing) { c.push(Instruction::Discard); c.push(Instruction::Discard); Instruction::PushTrue } else { match arg_type { Type::F64 => Instruction::EqFloat, Type::String => Instruction::EqString, Type::Bool => Instruction::EqBool, // ? _ => inst_panic!("panic comparing {}", arg_type), } } }) } TokenKind::Equal => { compile_expression(c, tr.nth_tree(2)?); c.push(Instruction::Dup); let lvalue = tr.nth_tree(0)?; let ltree = &c.syntax[lvalue]; #[allow(unused_assignments)] let mut environment = Environment::error(); let declaration = match ltree.kind { // TODO: Assign to list access TreeKind::Identifier => { let id = ltree.nth_token(0)?.as_str(&c.source); environment = c.semantics.environment_of(lvalue); environment.bind(id)? } TreeKind::MemberAccess => { let id = ltree.nth_token(2)?.as_str(&c.source); let t = ltree.nth_tree(0)?; let typ = c.semantics.type_of(t); environment = c.semantics.member_environment(t, &typ); environment.bind(id)? } _ => return None, }; let instruction = match declaration { Declaration::Variable { location, index, .. } => { let index = *index; match location { Location::Argument => { compiler_assert!(c, t, index < c.function.args); Instruction::StoreArgument(index) } Location::Local => { if index >= c.function.locals { c.function.locals = index + 1; } Instruction::StoreLocal(index) } Location::Module => { compiler_assert!(c, t, index < c.module.globals); Instruction::StoreModule(index) } Location::Slot => { compile_expression(c, ltree.nth_tree(0)?); Instruction::StoreSlot(index) } } } Declaration::ExternFunction { .. } => inst_panic!("store ext"), Declaration::Function { .. } => inst_panic!("store func"), Declaration::Class { .. } => inst_panic!("store class"), Declaration::Import { .. } => inst_panic!("store import"), }; c.push(instruction); OK } _ => ice!( c, t, "Unsupported binary expression '{}'", op.as_str(&c.source) ), } } fn compile_identifier_expression(c: &mut Compiler, t: TreeRef, tree: &Tree) -> Option<()> { let ident = tree.nth_token(0)?.as_str(&c.source); let environment = c.semantics.environment_of(t); let declaration = environment.bind(ident)?; compile_load_declaration(c, t, declaration) } fn compile_load_declaration(c: &mut Compiler, t: TreeRef, declaration: &Declaration) -> CR { let instruction = match declaration { Declaration::Variable { location, index, .. } => { let index = *index; match location { Location::Local => { if index >= c.function.locals { c.function.locals = index + 1; } Instruction::LoadLocal(index) } Location::Argument => { compiler_assert!(c, t, index < c.function.args); Instruction::LoadArgument(index) } Location::Module => { compiler_assert!(c, t, index < c.module.globals); Instruction::LoadModule(index) } Location::Slot => { // TODO: Assert slot is in field range? Instruction::LoadSlot(index) } } } Declaration::Function { declaration, .. } => { let key = FunctionKey { tree: *declaration }; let index = match c.function_bindings.get(&key) { Some(index) => *index, None => { let tree = &c.syntax[*declaration]; compiler_assert_eq!(c, t, tree.kind, TreeKind::FunctionDecl); compile_function_declaration(c, *declaration, tree, false)?; match c.function_bindings.get(&key) { Some(index) => *index, None => { ice!( c, t, "did not compile the function with key {:?}!", declaration ) } } } }; Instruction::LoadFunction(index) } Declaration::ExternFunction { id, .. } => Instruction::LoadExternFunction(id.id()), // Must be a static don't worry about it. Declaration::Class { .. } => return OK, // fix later Declaration::Import { .. } => ice!(c, t, "import compile not supported"), }; c.push(instruction); OK } fn compile_is_expression(c: &mut Compiler, tree: &Tree) -> Option<()> { compile_expression(c, tree.nth_tree(0)?); compile_pattern(c, tree.nth_tree(2)?) } fn compile_pattern(c: &mut Compiler, t: TreeRef) -> Option<()> { let tree = &c.syntax[t]; // Let's *try* to generate good code in the presence of a wildcard pattern.... let is_wildcard = tree .child_tree_of_kind(&c.syntax, TreeKind::WildcardPattern) .is_some(); let type_expr = tree.child_tree_of_kind(&c.syntax, TreeKind::TypeExpression); let and_index = tree.children.iter().position(|c| match c { Child::Token(t) => t.kind == TokenKind::And, _ => false, }); // If you have a binding, dup and store now, it is in scope. if let Some(binding) = tree.child_tree_of_kind(&c.syntax, TreeKind::VariableBinding) { if let Some(variable) = binding.nth_token(0) { let environment = c.semantics.environment_of(t); let declaration = environment.bind(variable.as_str(&c.source))?; let Declaration::Variable { location: Location::Local, index, .. } = declaration else { ice!(c, t, "is cannot make a non-local, non-variable declaration") }; // If we aren't a wildcard or we have an attached predicate then // we will need the value on the stack, otherwise we can discard // it. if and_index.is_some() || !is_wildcard { c.push(Instruction::Dup); } c.push(Instruction::StoreLocal(*index)); } } if !is_wildcard { let type_expr = type_expr?; compile_type_expr_eq(c, type_expr.nth_tree(0)?); } if let Some(and_index) = and_index { let jump_end_index = if is_wildcard { // If the pattern was a wildcard then don't bother with this jump // nonsense; we know the pattern matched, all we need to do is // evaluate the predicate. None } else { // Otherwise test the pattern to see if it passed; if it did then // we need to run the predicate. (This is the back half of an AND // expression.) let jump_true_index = c.push(Instruction::JumpTrue(0)); c.push(Instruction::PushFalse); let jump_end_index = c.push(Instruction::Jump(0)); c.patch(jump_true_index, |i| Instruction::JumpTrue(i)); Some(jump_end_index) }; compile_expression(c, tree.nth_tree(and_index + 1)?); // If we wound up with a jump what needs patching, patch it. if let Some(jump_end_index) = jump_end_index { c.patch(jump_end_index, |i| Instruction::Jump(i)); } } else if is_wildcard { // If there was no predicate *and* the pattern was a wildcard then // I'll just need to push true here. c.push(Instruction::PushTrue); } OK } fn compile_type_expr_eq(c: &mut Compiler, t: TreeRef) { let tree = &c.syntax[t]; let result = match tree.kind { TreeKind::TypeIdentifier => compile_type_identifier_eq(c, t, tree), TreeKind::AlternateType => compile_type_alternate_eq(c, tree), _ => ice!(c, t, "tree is not a type expression"), }; if result.is_none() { c.push(inst_panic!("panic compiling type expression eq {:?}", tree)); } } fn compile_type_identifier_eq(c: &mut Compiler, t: TreeRef, tree: &Tree) -> CR { let identifier = tree.nth_token(0)?.as_str(&c.source); match identifier { "f64" => { c.push(Instruction::IsFloat); } "string" => { c.push(Instruction::IsString); } "bool" => { c.push(Instruction::IsBool); } "nothing" => { c.push(Instruction::IsNothing); } _ => { let environment = c.semantics.environment_of(t); match environment.bind(identifier)? { Declaration::Class { declaration, .. } => { // The runtime identifier of the class is the tree index of the // class declaration sure why not. let index = declaration.index(); c.push(Instruction::IsClass(index.try_into().unwrap())); } _ => return None, } } }; OK } fn compile_type_alternate_eq(c: &mut Compiler, tree: &Tree) -> CR { // Compile the left hand side, it leaves a bool on the stack compile_type_expr_eq(c, tree.nth_tree(0)?); // If the first part is false (boo!) then we need to evaluate the // right hand side, so jump around the short circuit... let jump_false_index = c.push(Instruction::JumpFalse(0)); // ...but if the first part is true then we stop here. We need to // leave a value on the stack (it was consumed by jump above) so // we push an extra True here and jump to the end. c.push(Instruction::PushTrue); let jump_end_index = c.push(Instruction::Jump(0)); // Here we are, we consumed the `false` off the stack now time to // do the right hand side. c.patch(jump_false_index, |i| Instruction::JumpFalse(i)); // The right hand side leaves true or false on the stack, it's // the result of the expression. compile_type_expr_eq(c, tree.nth_tree(2)?); // (here's where you go after you leave "true" on the stack.) c.patch(jump_end_index, |i| Instruction::Jump(i)); OK } fn compile_call_expression(c: &mut Compiler, tree: &Tree) -> CR { let arg_list = tree.child_tree_of_kind(&c.syntax, TreeKind::ArgumentList)?; let mut args: Vec<_> = arg_list.child_trees().collect(); let arg_count = args.len(); args.reverse(); for arg in args { compile_expression(c, arg); } let func = tree.nth_tree(0)?; let func_type = c.semantics.type_of(func); let arg_count = match func_type { // TODO: Consider being guided by syntax here? Type::Method(..) => arg_count + 1, _ => arg_count, }; compile_expression(c, func); c.push(Instruction::Call(arg_count)); OK } fn compile_block_expression(c: &mut Compiler, tree: &Tree) -> CR { if tree.children.len() == 2 { c.push(Instruction::PushNothing); return OK; } let last_is_brace = tree.nth_token(tree.children.len() - 1).is_some(); let last_index = tree.children.len() - if last_is_brace { 2 } else { 1 }; for i in 1..last_index { compile_statement(c, tree.nth_tree(i)?, false); } compile_statement(c, tree.nth_tree(last_index)?, true); OK } fn compile_argument(c: &mut Compiler, tree: &Tree) -> CR { compile_expression(c, tree.nth_tree(0)?); OK } fn compile_new_object_expression(c: &mut Compiler, t: TreeRef, tree: &Tree) -> CR { // We pass in the arguments.... by... field order? let Type::Object(ct, _) = c.semantics.type_of(t) else { c.push(inst_panic!("new obj not ob")); return OK; }; let class = c.semantics.class_of(ct); let field_list = tree.child_tree_of_kind(&c.syntax, TreeKind::FieldList)?; let mut field_bindings = HashMap::new(); for field in field_list.children_of_kind(&c.syntax, TreeKind::FieldValue) { let f = &c.syntax[field]; let name = f.nth_token(0)?; field_bindings.insert(name.as_str(&c.source), field); } // The fields come in this order and since arguments are backwards // (stack!) we compile them in reverse order. Missing fields panic, // obviously. for field in class.fields.iter().rev() { let binding = field_bindings.get(&*field.name)?; compile_expression(c, *binding); } // Fetch the correct constructor. // TODO: Binding this type should be done by semantics, and we should borrow it. let type_reference = tree.child_tree_of_kind(&c.syntax, TreeKind::TypeIdentifier)?; let identifier = type_reference.nth_token(0)?.as_str(&c.source); let environment = c.semantics.environment_of(t); match environment.bind(identifier)? { Declaration::Class { declaration, .. } => { let key = FunctionKey { tree: *declaration }; let index = match c.function_bindings.get(&key) { Some(index) => *index, None => { let tree = &c.syntax[*declaration]; compiler_assert_eq!(c, t, tree.kind, TreeKind::ClassDecl); compile_class_declaration(c, t, tree, false)?; *c.function_bindings .get(&key) .expect("did not compile the class constructor!") } }; c.push(Instruction::LoadFunction(index)); } _ => return None, } c.push(Instruction::Call(class.fields.len())); OK } fn compile_field_value(c: &mut Compiler, t: TreeRef, tree: &Tree) -> CR { if let Some(colon) = tree.nth_token(1) { if colon.kind == TokenKind::Colon { compile_expression(c, tree.nth_tree(2)?); return OK; } } // Form 2: { x, ... } let environment = c.semantics.environment_of(t); let id = tree.nth_token(0)?.as_str(&c.source); let declaration = environment.bind(id)?; compile_load_declaration(c, t, declaration) } fn compile_member_access(c: &mut Compiler, t: TreeRef, tree: &Tree) -> CR { // In member access; the lhs sets up the object and in theory the rhs // binds against it. ::shrug:: // compile_expression(c, tree.nth_tree(0)?); let typ = c.semantics.type_of(tree.nth_tree(0)?); let ident = tree.nth_token(2)?.as_str(&c.source); let environment = match &typ { Type::Object(ct, _) => { let class = c.semantics.class_of(*ct); class.env.clone() } Type::Class(ct, _) => { let class = c.semantics.class_of(*ct); class.static_env.clone() } _ => { c.push(inst_panic!("cannot get environment of {typ}")); return None; } }; let declaration = environment.bind(ident)?; // NOTE: If this is a method call we still don't have to do anything // special here, since the load of the member function will *not* // consume the self pointer from the stack. compile_load_declaration(c, t, declaration); OK } fn compile_self_reference(c: &mut Compiler) -> CR { c.push(Instruction::LoadArgument(0)); OK } fn compile_list_constructor(c: &mut Compiler, tree: &Tree) -> CR { let mut children: Vec<_> = tree .children_of_kind(&c.syntax, TreeKind::ListConstructorElement) .collect(); children.reverse(); let count = children.len(); for child in children { compile_expression(c, child); } c.push(Instruction::NewList(count)); OK } fn compile_list_constructor_element(c: &mut Compiler, tree: &Tree) -> CR { compile_expression(c, tree.nth_tree(0)?); OK } fn compile_statement(c: &mut Compiler, t: TreeRef, gen_value: bool) { let tree = &c.semantics.tree()[t]; let cr = match tree.kind { TreeKind::Error => None, TreeKind::Import => compile_import_statement(c, gen_value), TreeKind::Block => compile_block_statement(c, t, gen_value), TreeKind::ClassDecl => compile_class_declaration(c, t, tree, gen_value), TreeKind::ExpressionStatement => compile_expression_statement(c, tree, gen_value), TreeKind::ForStatement => compile_for_statement(c, tree, gen_value), TreeKind::FunctionDecl => compile_function_declaration(c, t, tree, gen_value), TreeKind::IfStatement => compile_if_statement(c, tree, gen_value), TreeKind::LetStatement => compile_let_statement(c, t, tree, gen_value), TreeKind::ReturnStatement => compile_return_statement(c, tree), TreeKind::WhileStatement => compile_while_statement(c, tree, gen_value), _ => ice!(c, t, "unsupported statement tree kind {:?}", tree.kind), }; if matches!(cr, None) { c.push(inst_panic!("stat {:?}", tree)); } } fn compile_if_statement(c: &mut Compiler, tree: &Tree, gen_value: bool) -> CR { compile_expression(c, tree.nth_tree(0)?); if !gen_value { c.push(Instruction::Discard); } OK } fn compile_expression_statement(c: &mut Compiler, tree: &Tree, gen_value: bool) -> CR { if let Some(expr) = tree.nth_tree(0) { compile_expression(c, expr); if tree .nth_token(1) .is_some_and(|t| t.kind == TokenKind::Semicolon) { c.push(Instruction::Discard); if gen_value { c.push(Instruction::PushNothing); } } else if !gen_value { c.push(Instruction::Discard); } } else if gen_value { c.push(Instruction::PushNothing); }; OK } fn compile_let_statement(c: &mut Compiler, t: TreeRef, tree: &Tree, gen_value: bool) -> CR { compile_expression(c, tree.nth_tree(3)?); let environment = c.semantics.environment_of(t); let declaration = environment.bind(tree.nth_token(1)?.as_str(&c.source))?; let Declaration::Variable { location, index, .. } = declaration else { ice!(c, t, "let cannot make a non-variable declaration") }; let index = *index; let instruction = match location { Location::Local => { if index >= c.function.locals { c.function.locals = index + 1; } Instruction::StoreLocal(index) } Location::Module => { if index >= c.module.globals { c.module.globals = index + 1; } Instruction::StoreModule(index) } _ => ice!(c, t, "unsuitable location for let declaration"), }; c.push(instruction); if gen_value { c.push(Instruction::PushNothing); } OK } fn compile_function_declaration(c: &mut Compiler, t: TreeRef, tree: &Tree, gen_value: bool) -> CR { // Only compile a given function once. // // TODO: When it's time for generics, this should only actually compile // if we have no unbound type variables. let fk = FunctionKey { tree: t }; if !c.function_bindings.contains_key(&fk) { // TODO: If this is a method the name should be different. let name = tree.nth_token(1)?.as_str(&c.source); let param_list = tree.child_tree_of_kind(&c.syntax, TreeKind::ParamList)?; let param_count = param_list.children.len() - 2; let function_index = c.temp_functions.len(); c.temp_functions.push(None); c.pending_functions .push((fk.clone(), function_index, Function::new(name, param_count))); c.function_bindings.insert(fk, function_index); c.module .exports .insert(name.to_string(), Export::Function(function_index)); } if gen_value { c.push(Instruction::PushNothing); } OK } fn compile_class_declaration(c: &mut Compiler, t: TreeRef, tree: &Tree, gen_value: bool) -> CR { // Only compile a given function once. // Classes get compiled as constructor functions which get called. let fk = FunctionKey { tree: t }; if !c.function_bindings.contains_key(&fk) { let name = tree.nth_token(1)?.as_str(&c.source); let field_count = tree.children.len() - 2; let function_index = c.temp_functions.len(); c.temp_functions.push(None); c.pending_functions .push((fk.clone(), function_index, Function::new(name, field_count))); c.function_bindings.insert(fk, function_index); c.module .exports .insert(name.to_string(), Export::Function(function_index)); } if gen_value { c.push(Instruction::PushNothing); } OK } fn compile_function(c: &mut Compiler, t: TreeRef) -> CR { let tree = &c.syntax[t]; match tree.kind { TreeKind::FunctionDecl => { let block = tree.child_of_kind(&c.syntax, TreeKind::Block)?; compile_expression(c, block); } TreeKind::ClassDecl => { let count = tree .children_of_kind(&c.syntax, TreeKind::FieldDecl) .count(); for i in 0..count { c.push(Instruction::LoadArgument(count - 1 - i)); } let name = tree.nth_token(1)?.as_str(&c.source); let name_index = c.add_string(name.to_string()); c.push(Instruction::PushString(name_index)); c.push(Instruction::PushInt(t.index().try_into().unwrap())); c.push(Instruction::NewObject(count)); } _ => ice!(c, t, "what is this tree doing in compile_function?"), } c.push(Instruction::Return); OK } fn compile_block_statement(c: &mut Compiler, t: TreeRef, gen_value: bool) -> CR { compile_expression(c, t); if !gen_value { c.push(Instruction::Discard); } OK } fn compile_while_statement(c: &mut Compiler, tree: &Tree, gen_value: bool) -> CR { let start_index = c.function.instructions.len(); compile_expression(c, tree.nth_tree(1)?); let jump_end_index = c.push(Instruction::JumpFalse(0)); compile_block_statement(c, tree.nth_tree(2)?, false); c.push(Instruction::Jump(start_index)); c.patch(jump_end_index, |i| Instruction::JumpFalse(i)); if gen_value { c.push(Instruction::PushNothing); } OK } fn compile_return_statement(c: &mut Compiler, tree: &Tree) -> CR { if let Some(expr) = tree.nth_tree(1) { compile_expression(c, expr); } else { c.push(Instruction::PushNothing); } c.push(Instruction::Return); OK } fn compile_for_statement(c: &mut Compiler, tree: &Tree, gen_value: bool) -> CR { // Figure out the variable. let vt = tree.nth_tree(1)?; let var = &c.syntax[vt]; let id = var.nth_token(0)?.as_str(&c.source); let body = tree.nth_tree(4)?; let env = c.semantics.environment_of(body); let Some(variable_decl) = env.bind(id) else { ice!(c, body, "Unable to bind {id} in loop body"); }; let variable_slot = match variable_decl { Declaration::Variable { location, index, .. } => { compiler_assert_eq!( c, vt, *location, Location::Local, "expected loop variable to be local" ); *index } _ => ice!(c, vt, "loop variable was not a variable"), }; // Figure out the generator. let iterable = tree.nth_tree(3)?; compile_expression(c, iterable); // call 'get_iterator' let iterable_typ = c.semantics.type_of(iterable); match iterable_typ { Type::List(_) => { c.push(Instruction::LoadExternFunction( EXTERN_BUILTIN_LIST_GET_ITERATOR, )); c.push(Instruction::Call(1)); } _ => return None, // TODO: Bind and call get_iterator() on type of iterable } // iterate // (Stack is clear except for the iterator.) let loop_top = c.push(Instruction::Dup); // Save the iterator match iterable_typ { Type::List(_) => { c.push(Instruction::LoadExternFunction( EXTERN_BUILTIN_LIST_ITERATOR_NEXT, )); } _ => return None, // TODO: Bind and call next() on type of iterator } c.push(Instruction::Call(1)); // Call 'next' c.push(Instruction::Dup); // Save the result c.push(Instruction::IsNothing); // Check to see if iteration is done let jump_to_end = c.push(Instruction::JumpTrue(0)); c.push(Instruction::StoreLocal(variable_slot)); // Store the value // (Stack is clear except for the iterator.) compile_statement(c, body, false); // Run the body // (Stack is clear except for the iterator.) c.push(Instruction::Jump(loop_top)); // Clean up the loop. c.patch(jump_to_end, |i| Instruction::JumpTrue(i)); c.push(Instruction::Discard); // Drop the unused value c.push(Instruction::Discard); // Drop the iterator if gen_value { c.push(Instruction::PushNothing); } OK } fn compile_import_statement(c: &mut Compiler, gen_value: bool) -> CR { if gen_value { c.push(Instruction::PushNothing); } OK }