diff --git a/fine/TODO b/fine/TODO new file mode 100644 index 00000000..33d3eccf --- /dev/null +++ b/fine/TODO @@ -0,0 +1,6 @@ +- The Export enum is stupid I think, for runtime modules. Why do we even have them? We should just put all the names in, like Declaration {} but for runtime. + +- Module IDs must be globally resolvable from within a given semantics object +- When adding PANIC instructions, push a diagnostic that I find if I can find one instead of a hard-coded string. + +- runtime should have `new` with 0 args and `with_loader` that does the boxing, and `new` should just make the standard one \ No newline at end of file diff --git a/fine/src/compiler.rs b/fine/src/compiler.rs index 0086b35c..dbb4a5c2 100644 --- a/fine/src/compiler.rs +++ b/fine/src/compiler.rs @@ -142,20 +142,11 @@ impl std::fmt::Debug for Function { } } -#[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, } @@ -249,16 +240,33 @@ macro_rules! ice { }} } -// macro_rules! inst_panic { -// ($($t:tt)+) => {{ -// // eprintln!($($t)*); -// Instruction::Panic -// }}; -// } +type CR = Result<(), &'static str>; +const OK: CR = CR::Ok(()); -// macro_rules! ice { -// ($compiler:expr, $tr:expr, $($t:tt)*) => {{}}; -// } +fn function_from_function_decl( + source: &str, + syntax: &SyntaxTree, + tree: &Tree, +) -> Result { + // TODO: If this is a method the name should be different. + let name = tree.nth_token(1).ok_or("no id")?.as_str(source); + + let param_list = tree + .child_tree_of_kind(syntax, TreeKind::ParamList) + .ok_or("no paramlist")?; + let param_count = param_list.children.len() - 2; + + Ok(Function::new(name, param_count)) +} + +fn function_from_class_decl(source: &str, tree: &Tree) -> Result { + let name = tree.nth_token(1).ok_or("no name")?.as_str(source); + + // TODO: I think this is incorrect! + let field_count = tree.children.len() - 2; + + Ok(Function::new(name, field_count)) +} pub fn compile(semantics: &Semantics) -> Rc { let source = semantics.source(); @@ -269,32 +277,44 @@ pub fn compile(semantics: &Semantics) -> Rc { 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), }; + let mut functions = vec![None; semantics.function_count() + 1]; 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; + let index = functions.len() - 1; + functions[index] = Some(Rc::new(compiler.function)); + compiler.module.init = index; } - while let Some((fk, idx, func)) = compiler.pending_functions.pop() { - if idx >= compiler.temp_functions.len() { - compiler.temp_functions.resize(idx + 1, None); + for t in semantics.tree().trees() { + if let Some(function_index) = semantics.get_function_index(t) { + let tree = &semantics.tree()[t]; + let function = match tree.kind { + TreeKind::FunctionDecl => function_from_function_decl(&source, &syntax_tree, tree), + TreeKind::ClassDecl => function_from_class_decl(&source, tree), + _ => Err("don't know how to make a function of this"), + }; + + if let Ok(function) = function { + compiler.function = function; + + let _ = compile_function(&mut compiler, t); + + let function = Rc::new(compiler.function); + compiler + .module + .exports + .insert(function.name.clone(), Export::Function(function_index)); + functions[function_index] = Some(function); + } } - compiler.function = func; - let _ = 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 { + for f in functions { module.functions.push(f.unwrap()); } @@ -317,9 +337,6 @@ fn file(c: &mut Compiler, t: TreeRef) { c.push(Instruction::Return); } -type CR = Result<(), &'static str>; -const OK: CR = CR::Ok(()); - fn compile_expression(c: &mut Compiler, t: TreeRef) { let tree = &c.syntax[t]; let cr = match tree.kind { @@ -656,43 +673,15 @@ fn compile_load_declaration(c: &mut Compiler, t: TreeRef, declaration: &Declarat Instruction::LoadSlot(index) } - Location::Function => { - // TODO: Assert declaration is local - let Origin::Source(ft) = declaration.origin else { - ice!(c, t, "Function location but external origin?"); - }; - let key = FunctionKey { tree: ft }; - let index = match c.function_bindings.get(&key) { - Some(index) => *index, - None => { - let tree = &c.syntax[ft]; - compiler_assert_eq!(c, t, tree.kind, TreeKind::FunctionDecl); - - compile_function_declaration(c, ft, 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) - } + Location::Function => Instruction::LoadFunction(index), Location::ExternalFunction => Instruction::LoadExternFunction(index), // Must be a static don't worry about it. Location::Class => return OK, - // fix later - Location::Import => ice!(c, t, "import compile not supported"), + // Imports are handled with an instruction prefix. + Location::Import => return OK, }; c.push(instruction); @@ -988,24 +977,7 @@ fn compile_new_object_expression(c: &mut Compiler, t: TreeRef, tree: &Tree) -> C let declaration = environment.bind(identifier).ok_or("cannot bind type")?; match declaration.location { Location::Class => { - let Origin::Source(classdecl) = declaration.origin else { - ice!(c, t, "this class declaration has no source?"); - }; - let key = FunctionKey { tree: classdecl }; - let index = match c.function_bindings.get(&key) { - Some(index) => *index, - None => { - let tree = &c.syntax[classdecl]; - 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)); + c.push(Instruction::LoadFunction(declaration.index)); } _ => return Err("unsupported type for construction"), } @@ -1039,20 +1011,7 @@ fn compile_member_access(c: &mut Compiler, t: TreeRef, tree: &Tree) -> CR { let typ = c.semantics.type_of(lhs); let ident = tree.nth_token(2).ok_or("no ident")?.as_str(&c.source); - let environment = match &typ { - Type::Object(mid, ct, _) => { - let class = c.semantics.class_of(*mid, *ct); - class.env.clone() - } - Type::Class(mid, ct, _) => { - let class = c.semantics.class_of(*mid, *ct); - class.static_env.clone() - } - _ => { - c.push_panic("cannot get environment of {typ}"); - return Err("cannot get environment"); - } - }; + let environment = c.semantics.member_environment(t, &typ); let declaration = environment.bind(ident).ok_or("cannot bind")?; // NOTE: If this is a method call we still don't have to do anything @@ -1092,10 +1051,10 @@ fn compile_statement(c: &mut Compiler, t: TreeRef, gen_value: bool) { 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::ClassDecl => compile_class_declaration(c, 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::FunctionDecl => compile_function_declaration(c, 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), @@ -1176,32 +1135,7 @@ fn compile_let_statement(c: &mut Compiler, t: TreeRef, tree: &Tree, gen_value: b 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).ok_or("no id")?.as_str(&c.source); - - let param_list = tree - .child_tree_of_kind(&c.syntax, TreeKind::ParamList) - .ok_or("no 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)); - } - +fn compile_function_declaration(c: &mut Compiler, gen_value: bool) -> CR { if gen_value { c.push(Instruction::PushNothing); } @@ -1209,26 +1143,7 @@ fn compile_function_declaration(c: &mut Compiler, t: TreeRef, tree: &Tree, gen_v 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).ok_or("no name")?.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)); - } - +fn compile_class_declaration(c: &mut Compiler, gen_value: bool) -> CR { if gen_value { c.push(Instruction::PushNothing); } diff --git a/fine/src/lib.rs b/fine/src/lib.rs index 2a4fc39d..c80463e2 100644 --- a/fine/src/lib.rs +++ b/fine/src/lib.rs @@ -183,7 +183,7 @@ pub fn process_file(file: &str) { } }; - // OK now there might be errors. + // OK now there might be semantic errors or whatnot. if errors.len() > 0 { for e in errors { eprintln!("{file}: {}:{}: {}", e.start.0, e.start.1, e.message); diff --git a/fine/src/semantics.rs b/fine/src/semantics.rs index d65b85c6..585ec172 100644 --- a/fine/src/semantics.rs +++ b/fine/src/semantics.rs @@ -344,9 +344,9 @@ pub enum Location { Local, // A local in an frame Slot, // A slot in an object Module, // A global in a module - Function, // A function in a module (index unrelated) + Function, // A function in a module ExternalFunction, // An external function (module unrelated) - Class, // A class in a module (index unrelated) + Class, // A class in a module Import, // An import in a module (index unrelated) } @@ -683,6 +683,9 @@ pub struct Semantics { // This is what is used for binding. logical_parents: Vec>, + function_count: usize, + function_indices: Vec>, + // TODO: State should be externalized instead of this refcell nonsense. errors: RefCell>>, types: RefCell>>, @@ -706,6 +709,19 @@ impl Semantics { let root_environment = Environment::new(mid, None, Location::Module); + let mut function_count = 0; + let mut function_indices = vec![None; tree.len()]; + for t in tree.trees() { + let tree = &tree[t]; + match tree.kind { + TreeKind::FunctionDecl | TreeKind::ClassDecl => { + function_indices[t.index()] = Some(function_count); + function_count += 1; + } + _ => {} + } + } + let mut semantics = Semantics { mid, file, @@ -714,6 +730,8 @@ impl Semantics { lines, import_map: OnceCell::new(), logical_parents, + function_count, + function_indices, errors: RefCell::new(vec![]), types: RefCell::new(vec![Incremental::None; tree.len()]), environments: RefCell::new(vec![Incremental::None; tree.len()]), @@ -892,6 +910,27 @@ impl Semantics { Environment::error(error) } + pub fn function_count(&self) -> usize { + self.function_count + } + + pub fn get_function_index(&self, t: TreeRef) -> Option { + let index = t.index(); + if index >= self.function_indices.len() { + None + } else { + self.function_indices[t.index()] + } + } + + pub fn function_index_of(&self, t: TreeRef) -> usize { + let Some(index) = self.function_indices[t.index()] else { + self.internal_compiler_error(Some(t), "Why didn't I get a function index for this?"); + }; + + index + } + pub fn environment_of(&self, t: TreeRef) -> EnvironmentRef { { // I want to make sure that this borrow is dropped after this block. @@ -952,7 +991,7 @@ impl Semantics { name.as_str(&self.source).into(), Declaration { location: Location::Function, - index: 0, + index: self.function_index_of(*t), module: self.mid, origin: Origin::Source(*t), exported: false, @@ -1007,7 +1046,7 @@ impl Semantics { let declaration = Declaration { location: Location::Function, - index: 0, + index: self.function_index_of(t), module: self.mid, origin: Origin::Source(t), exported, @@ -1024,7 +1063,7 @@ impl Semantics { let declaration = Declaration { location: Location::Class, - index: 0, + index: self.function_index_of(t), module: self.mid, origin: Origin::Source(t), exported, @@ -1393,7 +1432,7 @@ impl Semantics { (&*method.name).into(), Declaration { location: Location::Function, - index: 0, + index: self.function_index_of(method.declaration), module: self.mid, origin: Origin::Source(method.declaration), exported: false, diff --git a/fine/src/vm.rs b/fine/src/vm.rs index 032a8c7e..25d761ea 100644 --- a/fine/src/vm.rs +++ b/fine/src/vm.rs @@ -44,6 +44,11 @@ pub enum VMErrorCode { SlotOutOfRange(usize, Rc, usize), #[error("internal error: the extern function with ID {0} was not registered")] UnregisteredExternFunction(usize), + + #[error("the requested export was not found: {0}")] + ExportNotFound(String), + #[error("the requested export is not a function: {0}")] + ExportNotFunction(String), } #[derive(Debug)] @@ -687,8 +692,18 @@ pub fn eval_export_fn( ) -> std::result::Result { let export = match c.module.exports.get(name) { Some(Export::Function(id)) => id, - Some(_) => todo!(), - None => todo!(), + Some(_) => { + return Err(VMError { + code: VMErrorCode::ExportNotFunction(name.to_string()), + stack: Box::new([]), + }) + } + None => { + return Err(VMError { + code: VMErrorCode::ExportNotFound(name.to_string()), + stack: Box::new([]), + }) + } }; let function = c.module.functions[*export].clone(); diff --git a/fine/tests/expression/argument.fine b/fine/tests/expression/argument.fine index a432d24e..9b7453fe 100644 --- a/fine/tests/expression/argument.fine +++ b/fine/tests/expression/argument.fine @@ -64,11 +64,6 @@ fun test() -> f64 { // | RightBrace:'"}"' // | // @compiles-to: -// | function << module >> (0 args, 0 locals): -// | strings (0): -// | code (2): -// | 0: PushNothing -// | 1: Return // | function foo (1 args, 0 locals): // | strings (0): // | code (4): @@ -80,7 +75,12 @@ fun test() -> f64 { // | strings (0): // | code (4): // | 0: PushFloat(1.0) -// | 1: LoadFunction(1) +// | 1: LoadFunction(0) // | 2: Call(1) // | 3: Return +// | function << module >> (0 args, 0 locals): +// | strings (0): +// | code (2): +// | 0: PushNothing +// | 1: Return // | diff --git a/fine/tests/expression/arithmetic.fine b/fine/tests/expression/arithmetic.fine index 03639511..0a57ae32 100644 --- a/fine/tests/expression/arithmetic.fine +++ b/fine/tests/expression/arithmetic.fine @@ -40,11 +40,6 @@ fun test() -> f64 { // | RightBrace:'"}"' // | // @compiles-to: -// | function << module >> (0 args, 0 locals): -// | strings (0): -// | code (2): -// | 0: PushNothing -// | 1: Return // | function test (0 args, 0 locals): // | strings (0): // | code (10): @@ -58,4 +53,9 @@ fun test() -> f64 { // | 7: FloatMultiply // | 8: FloatAdd // | 9: Return +// | function << module >> (0 args, 0 locals): +// | strings (0): +// | code (2): +// | 0: PushNothing +// | 1: Return // | diff --git a/fine/tests/expression/assignment.fine b/fine/tests/expression/assignment.fine index 56497b75..4f822871 100644 --- a/fine/tests/expression/assignment.fine +++ b/fine/tests/expression/assignment.fine @@ -8,11 +8,6 @@ fun test() -> f64 { // @no-errors // @compiles-to: -// | function << module >> (0 args, 0 locals): -// | strings (0): -// | code (2): -// | 0: PushNothing -// | 1: Return // | function test (0 args, 3 locals): // | strings (0): // | code (14): @@ -30,5 +25,10 @@ fun test() -> f64 { // | 11: Discard // | 12: LoadLocal(0) // | 13: Return +// | function << module >> (0 args, 0 locals): +// | strings (0): +// | code (2): +// | 0: PushNothing +// | 1: Return // | // @eval: Float(2.0) diff --git a/fine/tests/expression/block.fine b/fine/tests/expression/block.fine index c6785fa2..867b2275 100644 --- a/fine/tests/expression/block.fine +++ b/fine/tests/expression/block.fine @@ -4,12 +4,12 @@ fun test() { // @no-errors // @compiles-to: -// | function << module >> (0 args, 0 locals): +// | function test (0 args, 0 locals): // | strings (0): // | code (2): // | 0: PushNothing // | 1: Return -// | function test (0 args, 0 locals): +// | function << module >> (0 args, 0 locals): // | strings (0): // | code (2): // | 0: PushNothing diff --git a/fine/tests/expression/boolean.fine b/fine/tests/expression/boolean.fine index 5ae7741b..01615b02 100644 --- a/fine/tests/expression/boolean.fine +++ b/fine/tests/expression/boolean.fine @@ -4,11 +4,6 @@ fun test() -> bool { // @no-errors // @compiles-to: -// | function << module >> (0 args, 0 locals): -// | strings (0): -// | code (2): -// | 0: PushNothing -// | 1: Return // | function test (0 args, 0 locals): // | strings (0): // | code (15): @@ -27,6 +22,11 @@ fun test() -> bool { // | 12: PushTrue // | 13: BoolNot // | 14: Return +// | function << module >> (0 args, 0 locals): +// | strings (0): +// | code (2): +// | 0: PushNothing +// | 1: Return // | // @eval: Bool(false) // @type: 15 bool diff --git a/fine/tests/expression/class.fine b/fine/tests/expression/class.fine index 0b04c979..3bf8e056 100644 --- a/fine/tests/expression/class.fine +++ b/fine/tests/expression/class.fine @@ -30,11 +30,26 @@ fun test() -> f64 { // @no-errors // @eval: Float(597.0) // @compiles-to: -// | function << module >> (0 args, 0 locals): +// | function something_static (0 args, 0 locals): // | strings (0): // | code (2): -// | 0: PushNothing +// | 0: PushFloat(12.0) // | 1: Return +// | function square_length (1 args, 0 locals): +// | strings (0): +// | code (12): +// | 0: LoadArgument(0) +// | 1: LoadSlot(0) +// | 2: LoadArgument(0) +// | 3: LoadSlot(0) +// | 4: FloatMultiply +// | 5: LoadArgument(0) +// | 6: LoadSlot(1) +// | 7: LoadArgument(0) +// | 8: LoadSlot(1) +// | 9: FloatMultiply +// | 10: FloatAdd +// | 11: Return // | function Point (6 args, 0 locals): // | strings (1): // | 0: "Point" @@ -60,13 +75,13 @@ fun test() -> f64 { // | code (27): // | 0: PushFloat(99.0) // | 1: PushFloat(999.0) -// | 2: LoadFunction(1) +// | 2: LoadFunction(2) // | 3: Call(2) // | 4: PushFloat(23.0) // | 5: PushFloat(7.0) -// | 6: LoadFunction(1) +// | 6: LoadFunction(2) // | 7: Call(2) -// | 8: LoadFunction(2) +// | 8: LoadFunction(3) // | 9: Call(2) // | 10: StoreLocal(0) // | 11: LoadLocal(0) @@ -76,33 +91,18 @@ fun test() -> f64 { // | 15: LoadSlot(0) // | 16: LoadSlot(0) // | 17: LoadLocal(1) -// | 18: LoadFunction(4) +// | 18: LoadFunction(1) // | 19: Call(1) // | 20: FloatAdd -// | 21: LoadFunction(5) +// | 21: LoadFunction(0) // | 22: Call(0) // | 23: FloatAdd // | 24: StoreLocal(2) // | 25: LoadLocal(2) // | 26: Return -// | function square_length (1 args, 0 locals): -// | strings (0): -// | code (12): -// | 0: LoadArgument(0) -// | 1: LoadSlot(0) -// | 2: LoadArgument(0) -// | 3: LoadSlot(0) -// | 4: FloatMultiply -// | 5: LoadArgument(0) -// | 6: LoadSlot(1) -// | 7: LoadArgument(0) -// | 8: LoadSlot(1) -// | 9: FloatMultiply -// | 10: FloatAdd -// | 11: Return -// | function something_static (0 args, 0 locals): +// | function << module >> (0 args, 0 locals): // | strings (0): // | code (2): -// | 0: PushFloat(12.0) +// | 0: PushNothing // | 1: Return // | diff --git a/fine/tests/expression/conditional.fine b/fine/tests/expression/conditional.fine index cad9fa10..24c0d09c 100644 --- a/fine/tests/expression/conditional.fine +++ b/fine/tests/expression/conditional.fine @@ -58,11 +58,6 @@ fun test() -> f64 { // | RightBrace:'"}"' // // @compiles-to: -// | function << module >> (0 args, 0 locals): -// | strings (0): -// | code (2): -// | 0: PushNothing -// | 1: Return // | function test (0 args, 0 locals): // | strings (1): // | 0: "discarded" @@ -75,5 +70,10 @@ fun test() -> f64 { // | 5: Jump(7) // | 6: PushFloat(45.0) // | 7: Return +// | function << module >> (0 args, 0 locals): +// | strings (0): +// | code (2): +// | 0: PushNothing +// | 1: Return // | // @eval: Float(23.0) diff --git a/fine/tests/expression/variable.fine b/fine/tests/expression/variable.fine index e7335ac8..34e39dc6 100644 --- a/fine/tests/expression/variable.fine +++ b/fine/tests/expression/variable.fine @@ -64,6 +64,13 @@ fun test() -> f64 { // | RightBrace:'"}"' // | // @compiles-to: +// | function test (0 args, 0 locals): +// | strings (0): +// | code (4): +// | 0: LoadModule(0) +// | 1: LoadModule(1) +// | 2: FloatAdd +// | 3: Return // | function << module >> (0 args, 0 locals): // | strings (0): // | code (12): @@ -79,11 +86,4 @@ fun test() -> f64 { // | 9: Discard // | 10: PushNothing // | 11: Return -// | function test (0 args, 0 locals): -// | strings (0): -// | code (4): -// | 0: LoadModule(0) -// | 1: LoadModule(1) -// | 2: FloatAdd -// | 3: Return // | diff --git a/fine/tests/modules/import.fine b/fine/tests/modules/import.fine index da98ad17..f583592c 100644 --- a/fine/tests/modules/import.fine +++ b/fine/tests/modules/import.fine @@ -6,3 +6,4 @@ fun test() -> string { // TODO: Obviously run the code duh // @no-errors +/// @eval: asdf \ No newline at end of file