From eaaf72e47ed0637347e6b942171bfe0f7c79a774 Mon Sep 17 00:00:00 2001 From: Zak Farmer Date: Mon, 30 Oct 2023 19:04:14 +0000 Subject: [PATCH] feat: builtin compilation and execution --- Cargo.lock | 1 + compiler/src/lib.rs | 54 +++++++++++++++++++--------- compiler/src/symbol_table.rs | 16 +++++++-- compiler/tests/compiler_tests.rs | 29 +++++++++++++++ compiler/tests/symbol_table_tests.rs | 2 +- evaluator/src/builtins.rs | 0 evaluator/src/lib.rs | 10 ++++-- interpreter/src/repl.rs | 4 ++- object/Cargo.toml | 3 +- object/src/builtins.rs | 30 ++++++++++++++++ object/src/lib.rs | 4 +++ opcode/src/lib.rs | 10 ++++++ vm/src/lib.rs | 22 ++++++++++++ vm/tests/vm_tests.rs | 22 ++++++++++++ 14 files changed, 184 insertions(+), 23 deletions(-) create mode 100644 evaluator/src/builtins.rs create mode 100644 object/src/builtins.rs diff --git a/Cargo.lock b/Cargo.lock index ee61b7c..a3432b8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -279,6 +279,7 @@ version = "0.1.0" dependencies = [ "anyhow", "env_logger", + "lazy_static", "log", "opcode", "parser", diff --git a/compiler/src/lib.rs b/compiler/src/lib.rs index 8a4f501..8e70ef5 100644 --- a/compiler/src/lib.rs +++ b/compiler/src/lib.rs @@ -7,7 +7,7 @@ use parser::ast::{ BlockStatement, BooleanLiteral, Expression, IntegerLiteral, Literal, Node, Statement, StringLiteral, }; -use symbol_table::{SymbolScope, SymbolTable}; +use symbol_table::{SymbolScope, SymbolTable, Symbol}; pub mod symbol_table; @@ -60,21 +60,29 @@ impl Compiler { }, }; + let mut symbol_table = SymbolTable::new(); + + for (i, builtin) in object::builtins::BUILTINS.iter().enumerate() { + symbol_table.define_builtin(i, builtin.0.to_string()); + } + Self { constants: Vec::new(), - symbol_table: SymbolTable::new(), + symbol_table, scopes: vec![main_scope], scope_index: 0, } } - pub fn new_with_state(constants: Vec>, symbol_table: SymbolTable) -> Self { - let compiler = Self::new(); - + pub fn new_with_state(constants: Vec>, mut symbol_table: SymbolTable) -> Self { + for (i, builtin) in object::builtins::BUILTINS.iter().enumerate() { + symbol_table.define_builtin(i, builtin.0.to_string()); + } + Self { constants, symbol_table, - ..compiler + ..Self::new() } } @@ -109,6 +117,27 @@ impl Compiler { instructions } + fn load_symbol(&mut self, symbol: &Symbol) { + match symbol.scope { + SymbolScope::Global => { + self.emit(Opcode::OpGetGlobal, vec![symbol.index]); + } + SymbolScope::Local => { + self.emit(Opcode::OpGetLocal, vec![symbol.index]); + } + SymbolScope::Builtin => { + self.emit(Opcode::OpGetBuiltin, vec![symbol.index]); + } + SymbolScope::Free => { + self.emit(Opcode::OpGetFree, vec![symbol.index]); + } + SymbolScope::Function => { + self.emit(Opcode::OpCurrentClosure, vec![]); + } + _ => {} + } + } + pub fn scopes(&self) -> &Vec { &self.scopes } @@ -275,18 +304,11 @@ impl Compiler { fn compile_expression(&mut self, e: &Expression) -> Result<(), Error> { match e { Expression::Identifier(identifier) => { - let symbol = self.symbol_table.resolve(&identifier.value); + let symbol = self.symbol_table.resolve(identifier.value.clone()); match symbol { Some(symbol) => { - self.emit( - if symbol.scope == SymbolScope::Global { - Opcode::OpGetGlobal - } else { - Opcode::OpGetLocal - }, - vec![symbol.index], - ); + self.load_symbol(&symbol); } None => { return Err(Error::msg(format!( @@ -300,7 +322,7 @@ impl Compiler { } Expression::Function(function_literal) => { self.enter_scope(); - + for parameter in function_literal.parameters.iter() { self.symbol_table.define(¶meter.value); } diff --git a/compiler/src/symbol_table.rs b/compiler/src/symbol_table.rs index a1418f1..3e34bff 100644 --- a/compiler/src/symbol_table.rs +++ b/compiler/src/symbol_table.rs @@ -57,8 +57,20 @@ impl SymbolTable { symbol } - pub fn resolve(&self, name: &str) -> Option> { - let symbol = self.store.get(name); + pub fn define_builtin(&mut self, index: usize, name: String) -> Rc { + let symbol = Rc::new(Symbol { + name: name.clone(), + scope: SymbolScope::Builtin, + index, + }); + + self.store.insert(name.to_string(), Rc::clone(&symbol)); + + symbol + } + + pub fn resolve(&self, name: String) -> Option> { + let symbol = self.store.get(&name); if symbol.is_none() && self.outer.is_some() { return self.outer.as_ref().unwrap().resolve(name); diff --git a/compiler/tests/compiler_tests.rs b/compiler/tests/compiler_tests.rs index 06d8608..37d5f53 100644 --- a/compiler/tests/compiler_tests.rs +++ b/compiler/tests/compiler_tests.rs @@ -148,6 +148,35 @@ fn test_boolean_expressions() -> Result<(), Error> { Ok(()) } +#[test] +fn test_builtin_functions() -> Result<(), Error> { + let tests = vec![ + CompilerTestCase { + input: "$x = [1, 2, 3]; len($x);".to_string(), + expected_constants: vec![ + Object::Integer(1), + Object::Integer(2), + Object::Integer(3), + ], + expected_instructions: vec![ + opcode::make(opcode::Opcode::OpConst, &vec![0]), + opcode::make(opcode::Opcode::OpConst, &vec![1]), + opcode::make(opcode::Opcode::OpConst, &vec![2]), + opcode::make(opcode::Opcode::OpArray, &vec![3]), + opcode::make(opcode::Opcode::OpSetGlobal, &vec![0]), + opcode::make(opcode::Opcode::OpGetBuiltin, &vec![0]), + opcode::make(opcode::Opcode::OpGetGlobal, &vec![0]), + opcode::make(opcode::Opcode::OpCall, &vec![1]), + opcode::make(opcode::Opcode::OpPop, &vec![]), + ], + }, + ]; + + run_compiler_tests(tests)?; + + Ok(()) +} + #[test] fn test_compilation_scopes() -> Result<(), Error> { let mut compiler = Compiler::new(); diff --git a/compiler/tests/symbol_table_tests.rs b/compiler/tests/symbol_table_tests.rs index f379436..b8a1814 100644 --- a/compiler/tests/symbol_table_tests.rs +++ b/compiler/tests/symbol_table_tests.rs @@ -128,7 +128,7 @@ fn test_resolve_global() -> Result<(), Error> { ]; for (name, expected_symbol) in expected { - let symbol = global.resolve(&name); + let symbol = global.resolve(name.clone()); if symbol.is_none() { panic!("symbol for {} is None", name); diff --git a/evaluator/src/builtins.rs b/evaluator/src/builtins.rs new file mode 100644 index 0000000..e69de29 diff --git a/evaluator/src/lib.rs b/evaluator/src/lib.rs index 373006c..7094de2 100644 --- a/evaluator/src/lib.rs +++ b/evaluator/src/lib.rs @@ -9,7 +9,7 @@ use parser::ast::{ use object::{ environment::{Env, Environment}, - Object, + Object, builtins, }; pub fn eval(node: Node, env: &Env) -> Result> { @@ -48,6 +48,9 @@ fn apply_function(function: &Rc, args: &Vec>) -> Result { + Ok(builtin(args.to_vec())) + } f => Err(Error::msg(format!("expected {} to be a function", f))), } } @@ -299,7 +302,10 @@ fn eval_statement(statement: &Statement, env: &Env) -> Result> { fn eval_identifier(identifier: String, env: &Env) -> Result> { match env.borrow().get(&identifier) { Some(value) => Ok(value), - None => Err(Error::msg(format!("Identifier not found: {}", identifier))), + None => match object::builtins::get_builtin_by_name(&identifier) { + Some(builtin) => Ok(Rc::new(Object::Builtin(builtin))), + None => Err(Error::msg(format!("Unknown identifier: {}", identifier))), + }, } } diff --git a/interpreter/src/repl.rs b/interpreter/src/repl.rs index 8f2d0cd..d69281d 100644 --- a/interpreter/src/repl.rs +++ b/interpreter/src/repl.rs @@ -37,12 +37,14 @@ pub fn init_repl() -> Result<(), Error> { let mut parser = Parser::new(lexer); let program = parser.parse_program()?; + parser.check_errors()?; - let mut compiler = Compiler::new_with_state(constants, symbol_table); + let mut compiler = Compiler::new_with_state(constants, symbol_table.clone()); match compiler.compile(&Node::Program(program)) { Ok(bytecode) => { + let mut vm = Vm::new_with_globals_store(bytecode, globals); match vm.run() { diff --git a/object/Cargo.toml b/object/Cargo.toml index 80ce019..8be4eb8 100644 --- a/object/Cargo.toml +++ b/object/Cargo.toml @@ -10,4 +10,5 @@ anyhow = "1.0.75" env_logger = "0.10.0" log = "0.4.20" parser = { path = "../parser" } -opcode = { path = "../opcode" } \ No newline at end of file +opcode = { path = "../opcode" } +lazy_static = "1.4.0" diff --git a/object/src/builtins.rs b/object/src/builtins.rs new file mode 100644 index 0000000..edc5f8b --- /dev/null +++ b/object/src/builtins.rs @@ -0,0 +1,30 @@ +use std::{collections::HashMap, rc::Rc}; + +use lazy_static::lazy_static; + +use crate::{BuiltinFunction, Object}; + +lazy_static! { + pub static ref BUILTINS: Vec<(&'static str, BuiltinFunction)> = vec![ + ("len", len), + ]; +} + +pub fn get_builtin_by_name(name: &str) -> Option { + match BUILTINS.iter().find(|(builtin_name, _)| *builtin_name == name) { + Some((_, builtin)) => Some(*builtin), + None => None, + } +} + +pub fn len(args: Vec>) -> Rc { + if args.len() != 1 { + return Rc::new(Object::Null); + } + + match &*args[0] { + Object::Array(elements) => Rc::new(Object::Integer(elements.len() as i64)), + Object::String(s) => Rc::new(Object::Integer(s.len() as i64)), + _ => Rc::new(Object::Null), + } +} \ No newline at end of file diff --git a/object/src/lib.rs b/object/src/lib.rs index 8557233..20e193e 100644 --- a/object/src/lib.rs +++ b/object/src/lib.rs @@ -5,14 +5,18 @@ use parser::ast::{BlockStatement, Identifier}; use self::environment::Env; +pub mod builtins; pub mod environment; +pub type BuiltinFunction = fn(Vec>) -> Rc; + #[derive(Clone, Debug, PartialEq)] pub enum Object { Integer(i64), Boolean(bool), String(String), Function(Vec, BlockStatement, Env), + Builtin(BuiltinFunction), CompiledFunction(Rc), Return(Rc), Array(Vec>), diff --git a/opcode/src/lib.rs b/opcode/src/lib.rs index 04bf621..f0bf84f 100644 --- a/opcode/src/lib.rs +++ b/opcode/src/lib.rs @@ -178,6 +178,8 @@ pub enum Opcode { OpGetFree, /// 0x1C - Current closure OpCurrentClosure, + /// 0x1D - Get a builtin function + OpGetBuiltin, } impl From for Opcode { @@ -212,6 +214,7 @@ impl From for Opcode { 0x1A => Opcode::OpClosure, 0x1B => Opcode::OpGetFree, 0x1C => Opcode::OpCurrentClosure, + 0x1D => Opcode::OpGetBuiltin, _ => panic!("Opcode not found: {}", opcode), } } @@ -456,6 +459,13 @@ lazy_static! { operand_widths: vec![], }, ); + definitions.insert( + Opcode::OpGetBuiltin, + OpcodeDefinition { + name: "OpGetBuiltin", + operand_widths: vec![1], + }, + ); definitions }; diff --git a/vm/src/lib.rs b/vm/src/lib.rs index c3da0f2..0e8688f 100644 --- a/vm/src/lib.rs +++ b/vm/src/lib.rs @@ -48,6 +48,15 @@ impl Vm { let function = &*self.stack[self.stack_pointer - 1 - num_args]; match function { + Object::Builtin(builtin) => { + let args = self.stack[self.stack_pointer - num_args..self.stack_pointer].to_vec(); + + let result = builtin(args); + + self.stack_pointer -= num_args + 1; + + self.push(result); + } Object::CompiledFunction(compiled_function) => { let base_pointer = self.stack_pointer - num_args; let cloned_function = compiled_function.as_ref().clone(); @@ -182,11 +191,24 @@ impl Vm { self.stack[base_pointer + local_index] = self.pop(); } + Opcode::OpGetBuiltin => { + let builtin_index = instructions[instruction_pointer + 1] as usize; + + self.current_frame().instruction_pointer += 1; + + let builtin_definition = object::builtins::BUILTINS.get(builtin_index) + .unwrap() + .1; + + self.push(Rc::new(Object::Builtin(builtin_definition))); + } Opcode::OpCall => { let num_args = instructions[instruction_pointer + 1] as usize; self.current_frame().instruction_pointer += 1; + println!("Calling function"); + self.call_function(num_args); } Opcode::OpReturn => { diff --git a/vm/tests/vm_tests.rs b/vm/tests/vm_tests.rs index 7fd9f36..546c8e6 100644 --- a/vm/tests/vm_tests.rs +++ b/vm/tests/vm_tests.rs @@ -209,6 +209,28 @@ fn test_conditionals() -> Result<(), Error> { Ok(()) } +#[test] +fn test_builtin_functions() -> Result<(), Error> { + let tests = vec![ + VmTestCase { + input: "len(\"\")".to_string(), + expected: Object::Integer(0), + }, + VmTestCase { + input: "len(\"four\")".to_string(), + expected: Object::Integer(4), + }, + VmTestCase { + input: "len([1, 2, 3, 4, 5])".to_string(), + expected: Object::Integer(5), + }, + ]; + + run_vm_tests(tests)?; + + Ok(()) +} + #[test] fn test_functions_with_no_arguments() -> Result<(), Error> { let tests = vec![