diff --git a/.github/workflows/deploy-wasm.yml b/.github/workflows/deploy-wasm.yml index 9355cdd..1d623e6 100644 --- a/.github/workflows/deploy-wasm.yml +++ b/.github/workflows/deploy-wasm.yml @@ -3,8 +3,6 @@ name: Deploy WASM bundle on: push: branches: ["main"] - pull_request: - branches: ["main"] jobs: build: diff --git a/compiler/src/lib.rs b/compiler/src/lib.rs index bcb51e9..8a4f501 100644 --- a/compiler/src/lib.rs +++ b/compiler/src/lib.rs @@ -1,7 +1,7 @@ use std::rc::Rc; use anyhow::Error; -use lexer::token::TokenType; +use lexer::token::{Token, TokenType}; use opcode::{Instructions, Opcode}; use parser::ast::{ BlockStatement, BooleanLiteral, Expression, IntegerLiteral, Literal, Node, Statement, @@ -257,10 +257,10 @@ impl Compiler { &mut self, left: &Box, right: &Box, - operator: &str, + operator: Token, ) -> Result<(), Error> { - match operator { - "<" => { + match operator.token_type { + TokenType::Lt => { self.compile_expression(right)?; self.compile_expression(left)?; } @@ -300,6 +300,10 @@ impl Compiler { } Expression::Function(function_literal) => { self.enter_scope(); + + for parameter in function_literal.parameters.iter() { + self.symbol_table.define(¶meter.value); + } self.compile_block_statement(&function_literal.body)?; @@ -311,9 +315,10 @@ impl Compiler { self.emit(Opcode::OpReturn, vec![]); } + let num_locals = self.symbol_table.num_definitions; let instructions = self.exit_scope(); - let compiled_function = Rc::from(object::CompiledFunction::new(instructions)); + let compiled_function = Rc::from(object::CompiledFunction::new(instructions, num_locals)); let operands = vec![self.add_constant(object::Object::CompiledFunction(compiled_function))]; @@ -325,6 +330,10 @@ impl Compiler { Expression::Call(call_expression) => { self.compile_expression(&call_expression.function)?; + for argument in call_expression.arguments.iter() { + self.compile_expression(argument)?; + } + self.emit(Opcode::OpCall, vec![call_expression.arguments.len()]); Ok(()) @@ -369,17 +378,11 @@ impl Compiler { Ok(()) } Expression::Infix(infix_expression) => { - if infix_expression.operator.token_type == TokenType::Lt { - self.compile_expression(&infix_expression.right)?; - self.compile_expression(&infix_expression.left)?; - - self.emit(opcode::Opcode::OpGreaterThan, vec![]); - - return Ok(()); - } - - self.compile_expression(&infix_expression.left)?; - self.compile_expression(&infix_expression.right)?; + self.compile_operands( + &infix_expression.left, + &infix_expression.right, + infix_expression.operator.clone(), + )?; match infix_expression.operator.token_type { TokenType::Plus => self.emit(opcode::Opcode::OpAdd, vec![]), diff --git a/compiler/tests/compiler_tests.rs b/compiler/tests/compiler_tests.rs index f44c61d..06d8608 100644 --- a/compiler/tests/compiler_tests.rs +++ b/compiler/tests/compiler_tests.rs @@ -252,14 +252,21 @@ fn test_functions() -> Result<(), Error> { expected_constants: vec![ Object::Integer(5), Object::Integer(10), - Object::CompiledFunction(Rc::new(object::CompiledFunction::new(concat_instructions( - &vec![ - opcode::make(opcode::Opcode::OpConst, &vec![0]), - opcode::make(opcode::Opcode::OpConst, &vec![1]), - opcode::make(opcode::Opcode::OpAdd, &vec![]), - opcode::make(opcode::Opcode::OpReturnValue, &vec![]), - ], - )))), + Object::CompiledFunction( + Rc::new( + object::CompiledFunction::new( + concat_instructions( + &vec![ + opcode::make(opcode::Opcode::OpConst, &vec![0]), + opcode::make(opcode::Opcode::OpConst, &vec![1]), + opcode::make(opcode::Opcode::OpAdd, &vec![]), + opcode::make(opcode::Opcode::OpReturnValue, &vec![]), + ], + ), + 0 + ) + ) + ), ], expected_instructions: vec![ opcode::make(opcode::Opcode::OpConst, &vec![2]), @@ -280,7 +287,7 @@ fn test_functions_with_no_return_value() -> Result<(), Error> { object::CompiledFunction::new(concat_instructions(&vec![opcode::make( opcode::Opcode::OpReturn, &vec![], - )])), + )]), 0), ))], expected_instructions: vec![ opcode::make(opcode::Opcode::OpConst, &vec![0]), @@ -295,25 +302,47 @@ fn test_functions_with_no_return_value() -> Result<(), Error> { #[test] fn test_function_calls() -> Result<(), Error> { - let tests = vec![CompilerTestCase { - input: "$noArg = function () { return 24; }; $noArg();".to_string(), - expected_constants: vec![ - Object::Integer(24), - Object::CompiledFunction(Rc::new(object::CompiledFunction::new(concat_instructions( - &vec![ - opcode::make(opcode::Opcode::OpConst, &vec![0]), - opcode::make(opcode::Opcode::OpReturnValue, &vec![]), - ], - )))), - ], - expected_instructions: vec![ - opcode::make(opcode::Opcode::OpConst, &vec![1]), - opcode::make(opcode::Opcode::OpSetGlobal, &vec![0]), - opcode::make(opcode::Opcode::OpGetGlobal, &vec![0]), - opcode::make(opcode::Opcode::OpCall, &vec![0]), - opcode::make(opcode::Opcode::OpPop, &vec![]), - ], - }]; + let tests = vec![ + CompilerTestCase { + input: "$noArg = function () { return 24; }; $noArg();".to_string(), + expected_constants: vec![ + Object::Integer(24), + Object::CompiledFunction(Rc::new(object::CompiledFunction::new(concat_instructions( + &vec![ + opcode::make(opcode::Opcode::OpConst, &vec![0]), + opcode::make(opcode::Opcode::OpReturnValue, &vec![]), + ], + ), 0))), + ], + expected_instructions: vec![ + opcode::make(opcode::Opcode::OpConst, &vec![1]), + opcode::make(opcode::Opcode::OpSetGlobal, &vec![0]), + opcode::make(opcode::Opcode::OpGetGlobal, &vec![0]), + opcode::make(opcode::Opcode::OpCall, &vec![0]), + opcode::make(opcode::Opcode::OpPop, &vec![]), + ], + }, + CompilerTestCase { + input: "$oneArg = function ($a) { return $a; }; $oneArg(24);".to_string(), + expected_constants: vec![ + Object::CompiledFunction(Rc::new(object::CompiledFunction::new(concat_instructions( + &vec![ + opcode::make(opcode::Opcode::OpGetLocal, &vec![0]), + opcode::make(opcode::Opcode::OpReturnValue, &vec![]), + ], + ), 1))), + Object::Integer(24), + ], + expected_instructions: vec![ + opcode::make(opcode::Opcode::OpConst, &vec![0]), + opcode::make(opcode::Opcode::OpSetGlobal, &vec![0]), + opcode::make(opcode::Opcode::OpGetGlobal, &vec![0]), + opcode::make(opcode::Opcode::OpConst, &vec![1]), + opcode::make(opcode::Opcode::OpCall, &vec![1]), + opcode::make(opcode::Opcode::OpPop, &vec![]), + ], + }, + ]; run_compiler_tests(tests)?; diff --git a/compiler/tests/symbol_table_tests.rs b/compiler/tests/symbol_table_tests.rs index f410c3f..f379436 100644 --- a/compiler/tests/symbol_table_tests.rs +++ b/compiler/tests/symbol_table_tests.rs @@ -26,16 +26,32 @@ fn test_define() -> Result<(), Error> { "c".to_string(), Symbol { name: "c".to_string(), - scope: SymbolScope::Global, - index: 2, + scope: SymbolScope::Local, + index: 0, }, ), ( "d".to_string(), Symbol { name: "d".to_string(), - scope: SymbolScope::Global, - index: 3, + scope: SymbolScope::Local, + index: 1, + }, + ), + ( + "e".to_string(), + Symbol { + name: "e".to_string(), + scope: SymbolScope::Local, + index: 0, + }, + ), + ( + "f".to_string(), + Symbol { + name: "f".to_string(), + scope: SymbolScope::Local, + index: 1, }, ), ]); @@ -54,6 +70,34 @@ fn test_define() -> Result<(), Error> { panic!("b.name not 'b'. got={}", b.name); } + let mut first_local = SymbolTable::new_enclosed(global.clone()); + + let c = first_local.define("c"); + + if c.name != "c" { + panic!("c.name not 'c'. got={}", c.name); + } + + let d = first_local.define("d"); + + if d.name != "d" { + panic!("d.name not 'd'. got={}", d.name); + } + + let mut second_local = SymbolTable::new_enclosed(first_local.clone()); + + let e = second_local.define("e"); + + if e.name != "e" { + panic!("e.name not 'e'. got={}", e.name); + } + + let f = second_local.define("f"); + + if f.name != "f" { + panic!("f.name not 'f'. got={}", f.name); + } + Ok(()) } diff --git a/object/src/lib.rs b/object/src/lib.rs index 8431568..8557233 100644 --- a/object/src/lib.rs +++ b/object/src/lib.rs @@ -38,7 +38,6 @@ impl std::fmt::Display for Object { write!(f, "fn({}) {{\n{}\n}}", parameters_string, body) } - Object::CompiledFunction(function) => write!(f, "{}", function), Object::Array(elements) => { let mut elements_string = String::new(); @@ -54,6 +53,7 @@ impl std::fmt::Display for Object { } Object::Return(value) => write!(f, "{}", value), Object::Null => write!(f, "null"), + _ => Ok(()), } } } @@ -61,11 +61,15 @@ impl std::fmt::Display for Object { #[derive(Clone, Debug, PartialEq)] pub struct CompiledFunction { pub instructions: Instructions, + pub num_locals: usize, } impl CompiledFunction { - pub fn new(instructions: Instructions) -> Self { - Self { instructions } + pub fn new(instructions: Instructions, num_locals: usize) -> Self { + Self { + instructions, + num_locals, + } } pub fn instructions(&self) -> &Instructions { diff --git a/vm/src/lib.rs b/vm/src/lib.rs index 8032650..c3da0f2 100644 --- a/vm/src/lib.rs +++ b/vm/src/lib.rs @@ -44,10 +44,36 @@ impl Vm { &self.globals } + fn call_function(&mut self, num_args: usize) { + let function = &*self.stack[self.stack_pointer - 1 - num_args]; + + match function { + Object::CompiledFunction(compiled_function) => { + let base_pointer = self.stack_pointer - num_args; + let cloned_function = compiled_function.as_ref().clone(); + + let frame = frame::Frame::new( + cloned_function, + base_pointer, + ); + + self.stack_pointer = base_pointer + compiled_function.num_locals as usize; + self.push_frame(frame); + } + _ => { + panic!("calling non-function object: {}", function); + } + } + } + pub fn new(bytecode: Bytecode) -> Self { - let empty_frame = frame::Frame::new(CompiledFunction::new(Instructions(vec![])), 0); + let empty_frame = frame::Frame::new(CompiledFunction::new(Instructions(vec![]), 0), 0); + + let main_function = CompiledFunction::new( + bytecode.instructions.clone(), + 0 + ); - let main_function = CompiledFunction::new(bytecode.instructions.clone()); let main_frame = frame::Frame::new(main_function, 0); let mut frames = vec![empty_frame; MAX_FRAMES]; @@ -157,19 +183,11 @@ impl Vm { self.stack[base_pointer + local_index] = self.pop(); } Opcode::OpCall => { - self.current_frame().instruction_pointer += 1; + let num_args = instructions[instruction_pointer + 1] as usize; - let function = &*self.stack[self.stack_pointer - 1]; - - if let Object::CompiledFunction(function) = function { - let frame = - frame::Frame::new(function.as_ref().clone(), self.stack_pointer); + self.current_frame().instruction_pointer += 1; - self.stack_pointer = frame.base_pointer; - self.push_frame(frame); - } else { - return Err(Error::msg(format!("calling non-function: {}", function))); - } + self.call_function(num_args); } Opcode::OpReturn => { let frame = self.pop_frame(); diff --git a/vm/tests/vm_tests.rs b/vm/tests/vm_tests.rs index 39315ed..7fd9f36 100644 --- a/vm/tests/vm_tests.rs +++ b/vm/tests/vm_tests.rs @@ -233,6 +233,32 @@ fn test_functions_with_no_arguments() -> Result<(), Error> { Ok(()) } +#[test] +fn test_functions_with_arguments() -> Result<(), Error> { + let tests = vec![ + VmTestCase { + input: "$identity = function ($x) { $x; }; $identity(4);".to_string(), + expected: Object::Integer(4), + }, + VmTestCase { + input: "$identity = function ($x) { return $x; }; $identity(4);".to_string(), + expected: Object::Integer(4), + }, + VmTestCase { + input: "$double = function ($x) { $x * 2; }; $double(4);".to_string(), + expected: Object::Integer(8), + }, + VmTestCase { + input: "$add = function ($x, $y) { $x + $y; }; $add(4, 5);".to_string(), + expected: Object::Integer(9), + }, + ]; + + run_vm_tests(tests)?; + + Ok(()) +} + #[test] fn test_functions_with_no_return_value() -> Result<(), Error> { let tests = vec![VmTestCase { diff --git a/wasm/src/lib.rs b/wasm/src/lib.rs index e9a2424..535ab0b 100644 --- a/wasm/src/lib.rs +++ b/wasm/src/lib.rs @@ -6,6 +6,7 @@ use object::Object; use parser::{ast::Node, Parser}; use vm::{Vm, GLOBALS_SIZE}; use wasm_bindgen::{prelude::wasm_bindgen, JsValue}; +use web_sys::console; #[cfg(feature = "wee_alloc")] #[global_allocator] @@ -67,7 +68,17 @@ fn execute(input: &str, state: &mut ExecutionState) -> Result { state.symbol_table = compiler.symbol_table; state.globals = vm.globals.clone(); - Ok(vm.last_popped_stack_elem().to_string()) + let last_popped = vm.last_popped_stack_elem(); + + match last_popped.as_ref() { + Object::Integer(i) => return Ok(i.to_string()), + Object::Boolean(b) => return Ok(b.to_string()), + Object::String(s) => return Ok(s.to_string()), + Object::Null => return Ok("null".to_string()), + _ => {} + } + + Ok("".to_string()) } #[wasm_bindgen]