diff --git a/Cargo.lock b/Cargo.lock index f643e84..ee61b7c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -280,6 +280,7 @@ dependencies = [ "anyhow", "env_logger", "log", + "opcode", "parser", ] diff --git a/compiler/src/lib.rs b/compiler/src/lib.rs index 2ddc6db..02a8e2b 100644 --- a/compiler/src/lib.rs +++ b/compiler/src/lib.rs @@ -1,7 +1,7 @@ use std::rc::Rc; use anyhow::Error; -use opcode::Opcode; +use opcode::{Opcode, Instructions}; use parser::ast::{ BlockStatement, BooleanLiteral, Expression, IntegerLiteral, Literal, Node, Statement, StringLiteral, }; @@ -29,43 +29,87 @@ impl std::fmt::Debug for Bytecode { } } -#[derive(Debug, Clone)] -struct EmittedInstruction { - opcode: opcode::Opcode, - position: usize, +#[derive(Clone, Debug)] +pub struct CompilationScope { + pub instructions: opcode::Instructions, + pub last_instruction: EmittedInstruction, + pub previous_instruction: EmittedInstruction, } pub struct Compiler { - instructions: opcode::Instructions, pub constants: Vec>, - pub symbol_table: SymbolTable, - last_instruction: Option, - previous_instruction: Option, + scopes: Vec, + scope_index: usize, } impl Compiler { pub fn new() -> Self { - Self { + let main_scope = CompilationScope { instructions: opcode::Instructions::default(), + last_instruction: EmittedInstruction { + opcode: opcode::Opcode::OpNull, + position: 0, + }, + previous_instruction: EmittedInstruction { + opcode: opcode::Opcode::OpNull, + position: 0, + }, + }; + + Self { constants: Vec::new(), - last_instruction: None, - previous_instruction: None, symbol_table: SymbolTable::new(), + scopes: vec![main_scope], + scope_index: 0, } } pub fn new_with_state(constants: Vec>, symbol_table: SymbolTable) -> Self { + let compiler = Self::new(); + Self { - instructions: opcode::Instructions::default(), constants, - last_instruction: None, - previous_instruction: None, symbol_table, + ..compiler } } + pub fn enter_scope(&mut self) { + let scope = CompilationScope { + instructions: opcode::Instructions::default(), + last_instruction: EmittedInstruction { + opcode: Opcode::OpNull, + position: 0, + }, + previous_instruction: EmittedInstruction { + opcode: Opcode::OpNull, + position: 0, + }, + }; + + self.scopes.push(scope); + self.scope_index += 1; + } + + pub fn exit_scope(&mut self) -> opcode::Instructions { + let instructions = self.current_instructions().clone(); + + self.scopes.pop(); + self.scope_index -= 1; + + instructions + } + + pub fn scopes(&self) -> &Vec { + &self.scopes + } + + pub fn scope_index(&self) -> usize { + self.scope_index + } + fn add_constant(&mut self, obj: object::Object) -> usize { self.constants.push(obj.into()); @@ -73,53 +117,67 @@ impl Compiler { } fn change_operand(&mut self, position: usize, operand: usize) { - let op = Opcode::from(self.instructions.0[position]); + let op = Opcode::from(self.current_instructions().0[position]); let new_instruction = opcode::make(op, &vec![operand]); self.replace_instruction(position, new_instruction); } - fn add_instructions(&mut self, instructions: opcode::Instructions) -> usize { - let position = self.instructions.0.len(); + pub fn add_instructions(&mut self, ins: &Instructions) -> usize { + let position = self.current_instructions().0.len(); - self.instructions.0.extend(instructions.0); + let new_instruction = self.scopes[self.scope_index] + .instructions + .merge_instructions(ins); + self.scopes[self.scope_index].instructions = new_instruction; return position; } - fn current_instructions(&self) -> &opcode::Instructions { - return &self.instructions; + pub fn current_instructions(&self) -> &opcode::Instructions { + return &self.scopes[self.scope_index].instructions; } fn replace_instruction(&mut self, position: usize, new_instruction: opcode::Instructions) { + let instructions = &mut self.scopes[self.scope_index].instructions; + for (i, instruction) in new_instruction.0.iter().enumerate() { - self.instructions.0[position + i] = *instruction; + instructions.0[position + i] = *instruction; } } - fn set_last_instruction(&mut self, op: opcode::Opcode, position: usize) { - let previous = self.last_instruction.clone(); - - self.previous_instruction = previous; - - self.last_instruction = Some(EmittedInstruction { + fn set_last_instruction(&mut self, op: opcode::Opcode, position: usize) -> Result<(), Error> { + let previous = self.scopes[self.scope_index].last_instruction.clone(); + let last = EmittedInstruction { opcode: op, position, - }); + }; + + self.scopes[self.scope_index].last_instruction = last; + self.scopes[self.scope_index].previous_instruction = previous; + + Ok(()) + } + + fn replace_last_pop_with_return(&mut self) { + let last_position = self.scopes[self.scope_index].last_instruction.position; + self.replace_instruction(last_position, opcode::make(Opcode::OpReturn, &vec![])); + + self.scopes[self.scope_index].last_instruction.opcode = Opcode::OpReturn; } pub fn bytecode(&self) -> Bytecode { Bytecode { - instructions: self.instructions.clone(), + instructions: self.current_instructions().clone(), constants: self.constants.clone(), } } - fn emit(&mut self, op: opcode::Opcode, operands: Vec) -> usize { + pub fn emit(&mut self, op: opcode::Opcode, operands: Vec) -> usize { let instructions = opcode::make(op, &operands); - let index = self.add_instructions(instructions); + let index = self.add_instructions(&instructions); self.set_last_instruction(op, index); @@ -166,6 +224,8 @@ impl Compiler { Statement::Return(r) => { self.compile_expression(&r.return_value)?; + self.emit(opcode::Opcode::OpReturnValue, vec![]); + Ok(()) } Statement::Expr(e) => { @@ -219,6 +279,42 @@ impl Compiler { Ok(()) } + Expression::Function(function_literal) => { + self.enter_scope(); + + self.compile_block_statement(&function_literal.body)?; + + if self.last_instruction_is(Opcode::OpPop) { + self.replace_last_pop_with_return(); + } + + if ! self.last_instruction_is(Opcode::OpReturnValue) { + self.emit(Opcode::OpReturn, vec![]); + } + + let instructions = self.exit_scope(); + + let compiled_function = Rc::from( + object::CompiledFunction::new( + instructions, + ), + ); + + let operands = vec![self.add_constant( + object::Object::CompiledFunction(compiled_function) + )]; + + self.emit(Opcode::OpConst, operands); + + Ok(()) + } + Expression::Call(call_expression) => { + self.compile_expression(&call_expression.function)?; + + self.emit(Opcode::OpCall, vec![call_expression.arguments.len()]); + + Ok(()) + } Expression::If(if_expression) => { self.compile_expression(&if_expression.condition)?; @@ -250,6 +346,14 @@ impl Compiler { Ok(()) } + Expression::Index(index_expression) => { + self.compile_expression(&index_expression.left)?; + self.compile_expression(&index_expression.index)?; + + self.emit(opcode::Opcode::OpIndex, vec![]); + + Ok(()) + } Expression::Infix(infix_expression) => { self.compile_operands( &infix_expression.left, @@ -282,6 +386,15 @@ impl Compiler { Ok(()) } Expression::Literal(literal_expression) => match literal_expression { + Literal::Array(array) => { + for element in array.elements.iter() { + self.compile_expression(element)?; + } + + self.emit(opcode::Opcode::OpArray, vec![array.elements.len()]); + + Ok(()) + } Literal::Boolean(boolean) => match boolean { BooleanLiteral { value: true, .. } => { self.emit(opcode::Opcode::OpTrue, vec![]); @@ -323,14 +436,27 @@ impl Compiler { } fn last_instruction_is(&self, op: Opcode) -> bool { - match &self.last_instruction { - Some(instruction) => instruction.opcode == op, - None => false, + if self.current_instructions().0.is_empty() { + return false; } + + &self.scopes[self.scope_index].last_instruction.opcode == &op } fn remove_last_pop(&mut self) { - self.instructions.0.pop(); - self.last_instruction = self.previous_instruction.clone(); + let last = self.scopes[self.scope_index].last_instruction.clone(); + let previous = self.scopes[self.scope_index].previous_instruction.clone(); + + let old_instructions = self.current_instructions().0.clone(); + let new_instructions = old_instructions[..last.position].to_vec(); + + self.scopes[self.scope_index].instructions.0 = new_instructions; + self.scopes[self.scope_index].last_instruction = previous; } } + +#[derive(Clone, Copy, Debug)] +pub struct EmittedInstruction { + pub opcode: opcode::Opcode, + pub position: usize, +} diff --git a/compiler/tests/compiler_tests.rs b/compiler/tests/compiler_tests.rs index 070e0fe..35d6368 100644 --- a/compiler/tests/compiler_tests.rs +++ b/compiler/tests/compiler_tests.rs @@ -4,7 +4,7 @@ use anyhow::Error; use compiler::Compiler; use lexer::Lexer; use object::Object; -use opcode::concat_instructions; +use opcode::{concat_instructions, Instructions}; use parser::ast::Node; struct CompilerTestCase { @@ -13,6 +13,63 @@ struct CompilerTestCase { expected_instructions: Vec, } +#[test] +fn test_array_expressions() -> Result<(), Error> { + let tests = vec![ + CompilerTestCase { + input: "[]".to_string(), + expected_constants: vec![], + expected_instructions: vec![ + opcode::make(opcode::Opcode::OpArray, &vec![0]), + opcode::make(opcode::Opcode::OpPop, &vec![]), + ], + }, + CompilerTestCase { + input: "[1, 2, 3]".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::OpPop, &vec![]), + ], + }, + CompilerTestCase { + input: "[1 + 2, 3 - 4, 5 * 6]".to_string(), + expected_constants: vec![ + Object::Integer(1), + Object::Integer(2), + Object::Integer(3), + Object::Integer(4), + Object::Integer(5), + Object::Integer(6), + ], + expected_instructions: vec![ + opcode::make(opcode::Opcode::OpConst, &vec![0]), + opcode::make(opcode::Opcode::OpConst, &vec![1]), + opcode::make(opcode::Opcode::OpAdd, &vec![1]), + opcode::make(opcode::Opcode::OpConst, &vec![2]), + opcode::make(opcode::Opcode::OpConst, &vec![3]), + opcode::make(opcode::Opcode::OpSub, &vec![1]), + opcode::make(opcode::Opcode::OpConst, &vec![4]), + opcode::make(opcode::Opcode::OpConst, &vec![5]), + opcode::make(opcode::Opcode::OpMul, &vec![1]), + opcode::make(opcode::Opcode::OpArray, &vec![3]), + opcode::make(opcode::Opcode::OpPop, &vec![]), + ], + }, + ]; + + run_compiler_tests(tests)?; + + Ok(()) +} + #[test] fn test_boolean_expressions() -> Result<(), Error> { let tests = vec![ @@ -95,6 +152,44 @@ fn test_boolean_expressions() -> Result<(), Error> { Ok(()) } +#[test] +fn test_compilation_scopes() -> Result<(), Error> { + let mut compiler = Compiler::new(); + + compiler.emit(opcode::Opcode::OpMul, vec![]); + + { + compiler.enter_scope(); + + assert_eq!(compiler.scope_index(), 1); + + compiler.emit(opcode::Opcode::OpSub, vec![]); + + assert_eq!(compiler.scopes()[compiler.scope_index()].instructions.0.len(), 1); + + let last = compiler.scopes()[compiler.scope_index()].last_instruction; + assert_eq!(last.opcode, opcode::Opcode::OpSub); + + compiler.exit_scope(); + + assert_eq!(compiler.scope_index(), 0); + + compiler.emit(opcode::Opcode::OpAdd, vec![]); + + assert_eq!(compiler.scopes()[compiler.scope_index()].instructions.0.len(), 2); + } + + let last = compiler.scopes()[compiler.scope_index()].last_instruction; + + assert_eq!(last.opcode, opcode::Opcode::OpAdd); + assert_eq!(compiler.scopes()[compiler.scope_index()].previous_instruction.opcode, opcode::Opcode::OpMul); + + let previous = compiler.scopes()[compiler.scope_index()].previous_instruction; + assert_eq!(previous.opcode, opcode::Opcode::OpMul); + + Ok(()) +} + #[test] fn test_conditionals() -> Result<(), Error> { let tests = vec![ @@ -137,6 +232,138 @@ fn test_conditionals() -> Result<(), Error> { Ok(()) } +#[test] +fn test_functions() -> Result<(), Error> { + let tests = vec![ + CompilerTestCase { + input: "function () { return 5 + 10; }".to_string(), + 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![]), + ] + )))), + ], + expected_instructions: vec![ + opcode::make(opcode::Opcode::OpConst, &vec![2]), + opcode::make(opcode::Opcode::OpPop, &vec![]), + ], + }, + ]; + + run_compiler_tests(tests)?; + + Ok(()) + +} + +#[test] +fn test_functions_with_no_return_value() -> Result<(), Error> { + let tests = vec![ + CompilerTestCase { + input: "function() { }".to_string(), + expected_constants: vec![ + Object::CompiledFunction(Rc::new(object::CompiledFunction::new(concat_instructions(&vec![ + opcode::make(opcode::Opcode::OpReturn, &vec![]), + ] + )))), + ], + expected_instructions: vec![ + opcode::make(opcode::Opcode::OpConst, &vec![0]), + opcode::make(opcode::Opcode::OpPop, &vec![]), + ], + }, + ]; + + run_compiler_tests(tests)?; + + Ok(()) +} + +#[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![]), + ], + }, + ]; + + run_compiler_tests(tests)?; + + Ok(()) +} + +#[test] +fn test_index_expressions() -> Result<(), Error> { + let tests = vec![ + CompilerTestCase { + input: "[1, 2, 3][1 + 1]".to_string(), + expected_constants: vec![ + Object::Integer(1), + Object::Integer(2), + Object::Integer(3), + Object::Integer(1), + Object::Integer(1), + ], + 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::OpConst, &vec![3]), + opcode::make(opcode::Opcode::OpConst, &vec![4]), + opcode::make(opcode::Opcode::OpAdd, &vec![4]), + opcode::make(opcode::Opcode::OpIndex, &vec![]), + opcode::make(opcode::Opcode::OpPop, &vec![]), + ], + }, + CompilerTestCase { + input: "[1, 2, 3][2 - 1]".to_string(), + expected_constants: vec![ + Object::Integer(1), + Object::Integer(2), + Object::Integer(3), + Object::Integer(2), + Object::Integer(1), + ], + 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::OpConst, &vec![3]), + opcode::make(opcode::Opcode::OpConst, &vec![4]), + opcode::make(opcode::Opcode::OpSub, &vec![4]), + opcode::make(opcode::Opcode::OpIndex, &vec![]), + opcode::make(opcode::Opcode::OpPop, &vec![]), + ], + }, + ]; + + run_compiler_tests(tests)?; + + Ok(()) +} + #[test] fn test_integer_arithmetic() -> Result<(), Error> { let tests = vec![ @@ -236,6 +463,9 @@ fn run_compiler_tests(tests: Vec) -> Result<(), Error> { let bytecode = compiler.compile(&Node::Program(program))?; + println!("Testing input: {}", test.input.to_string()); + println!("Constants: {:?}", bytecode.constants); + test_constants(&test.expected_constants, &bytecode.constants); test_instructions(&test.expected_instructions, &bytecode.instructions); } @@ -252,15 +482,15 @@ pub fn test_constants(expected: &Vec, actual: &Vec>) { } fn test_instructions(expected: &Vec, actual: &opcode::Instructions) { - let expected_ins = concat_instructions(expected); + let expected_instructions = concat_instructions(expected); - for (&exp, got) in expected_ins.0.iter().zip(actual.0.clone()) { + for (&exp, got) in expected_instructions.0.iter().zip(actual.0.clone()) { assert_eq!( exp, got, "instruction not equal\n actual : \n{}\n expected: \n{}", actual.to_string(), - expected_ins.to_string() + expected_instructions.to_string() ); } } diff --git a/object/Cargo.toml b/object/Cargo.toml index d271d0b..80ce019 100644 --- a/object/Cargo.toml +++ b/object/Cargo.toml @@ -10,3 +10,4 @@ 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 diff --git a/object/src/lib.rs b/object/src/lib.rs index 33e27e2..20b8363 100644 --- a/object/src/lib.rs +++ b/object/src/lib.rs @@ -1,5 +1,6 @@ use std::rc::Rc; +use opcode::Instructions; use parser::ast::{BlockStatement, Identifier}; use self::environment::Env; @@ -12,6 +13,7 @@ pub enum Object { Boolean(bool), String(String), Function(Vec, BlockStatement, Env), + CompiledFunction(Rc), Return(Rc), Array(Vec>), Null, @@ -36,6 +38,7 @@ impl std::fmt::Display for Object { write!(f, "fn({}) {{\n{}\n}}", parameters_string, body) } + Object::CompiledFunction(_function) => write!(f, "compiled function"), Object::Array(elements) => { let mut elements_string = String::new(); @@ -54,3 +57,18 @@ impl std::fmt::Display for Object { } } } + +#[derive(Clone, Debug, PartialEq)] +pub struct CompiledFunction { + instructions: Instructions, +} + +impl CompiledFunction { + pub fn new(instructions: Instructions) -> Self { + Self { instructions } + } + + pub fn instructions(&self) -> &Instructions { + &self.instructions + } +} \ No newline at end of file diff --git a/parser/src/lib.rs b/parser/src/lib.rs index 7996f4a..acef45c 100644 --- a/parser/src/lib.rs +++ b/parser/src/lib.rs @@ -206,11 +206,6 @@ impl<'a> Parser<'a> { } fn parse_array_literal(&mut self) -> Result { - info!( - "parse_array_literal: Current token: {:?}", - self.current_token - ); - let current_token = self.current_token.clone().unwrap(); let elements = self.parse_expression_list(TokenType::RBracket)?; @@ -292,11 +287,6 @@ impl<'a> Parser<'a> { } fn parse_variable_reference_expression(&mut self) -> Result { - info!( - "parse_variable_reference_expression: Current token: {:?}", - self.current_token - ); - // Expect the next token to be an identifier if let Some(token) = &self.current_token { if token.token_type == TokenType::Ident { @@ -320,11 +310,6 @@ impl<'a> Parser<'a> { } fn parse_assignment_statement(&mut self) -> Result { - info!( - "parse_assignment_statement: Current token: {:?}", - self.current_token - ); - // Ensure the variable name is an identifier. let name_token = if let Some(token) = &self.current_token { if token.token_type == TokenType::Ident { @@ -388,8 +373,6 @@ impl<'a> Parser<'a> { } fn parse_expression(&mut self, precedence: Precedence) -> Result { - info!("parse_expression: Current token: {:?}", self.current_token); - // Get prefix parse function (if it exists) let prefix_fn = self .prefix_parse_fns @@ -406,7 +389,7 @@ impl<'a> Parser<'a> { // Call the prefix parse function let mut left = prefix_fn.unwrap()(self); - while !self.peek_token_is(&TokenType::Semicolon) && precedence < self.peek_precedence() { + while ! self.peek_token_is(&TokenType::Semicolon) && precedence < self.peek_precedence() { let infix_fn = self .infix_parse_fns .get(&self.peek_token.as_ref().unwrap().token_type) @@ -425,11 +408,6 @@ impl<'a> Parser<'a> { } fn parse_expression_list(&mut self, end: TokenType) -> Result> { - info!( - "parse_expression_list: Current token: {:?}", - self.current_token - ); - let mut list = vec![]; if self.peek_token_is(&end) { @@ -458,11 +436,8 @@ impl<'a> Parser<'a> { } fn parse_expression_statement(&mut self) -> Result { - info!( - "parse_expression_statement: Current token: {:?}", - self.current_token - ); let expr = self.parse_expression(Precedence::Lowest)?; + if self.peek_token_is(&TokenType::Semicolon) { self.next_token(); } @@ -471,11 +446,6 @@ impl<'a> Parser<'a> { } fn parse_block_statement(&mut self) -> Result { - info!( - "parse_block_statement: Current token: {:?}", - self.current_token - ); - let current_token = self.current_token.clone().unwrap(); let mut statements = vec![]; @@ -499,11 +469,6 @@ impl<'a> Parser<'a> { } fn parse_function_literal(&mut self) -> Result { - info!( - "parse_function_literal: Current token: {:?}", - self.current_token - ); - let current_token = self.current_token.clone().unwrap(); if !self.expect_peek(&TokenType::LParen) { @@ -530,10 +495,6 @@ impl<'a> Parser<'a> { } fn parse_function_parameters(&mut self) -> Result> { - info!( - "parse_function_parameters: Current token: {:?}", - self.current_token - ); let mut identifiers = vec![]; if self.peek_token_is(&TokenType::RParen) { @@ -577,11 +538,6 @@ impl<'a> Parser<'a> { } fn parse_grouped_expression(&mut self) -> Result { - info!( - "parse_grouped_expression: Current token: {:?}", - self.current_token - ); - self.next_token(); let expression = self.parse_expression(Precedence::Lowest); @@ -594,7 +550,6 @@ impl<'a> Parser<'a> { } fn parse_identifier(&mut self) -> Result { - info!("parse_identifier: Current token: {:?}", self.current_token); let current_token = self.current_token.clone().unwrap(); let identifier = Identifier { @@ -606,11 +561,6 @@ impl<'a> Parser<'a> { } fn parse_integer_literal(&mut self) -> Result { - info!( - "parse_integer_literal: Current token: {:?}", - self.current_token - ); - let current_token = self.current_token.clone().unwrap(); let value = self @@ -628,11 +578,6 @@ impl<'a> Parser<'a> { } fn parse_call_arguments(&mut self) -> Vec { - info!( - "parse_call_arguments: Current token: {:?}", - self.current_token - ); - let mut arguments = vec![]; if self.peek_token_is(&TokenType::RParen) { @@ -658,10 +603,6 @@ impl<'a> Parser<'a> { } fn parse_call_expression(&mut self, function: Expression) -> Result { - info!( - "parse_call_expression: Current token: {:?}", - self.current_token - ); let current_token = self.current_token.clone().unwrap(); let arguments = self.parse_call_arguments(); @@ -674,11 +615,6 @@ impl<'a> Parser<'a> { } fn parse_if_expression(&mut self) -> Result { - info!( - "parse_if_expression: Current token: {:?}", - self.current_token - ); - self.expect_peek(&TokenType::LParen); self.next_token(); @@ -705,11 +641,6 @@ impl<'a> Parser<'a> { } fn parse_index_expression(&mut self, left: Expression) -> Result { - info!( - "parse_index_expression: Current token: {:?}", - self.current_token - ); - let current_token = self.current_token.clone().unwrap(); self.next_token(); @@ -728,11 +659,6 @@ impl<'a> Parser<'a> { } fn parse_infix_expression(&mut self, left: Expression) -> Result { - info!( - "parse_infix_expression: Current token: {:?}", - self.current_token - ); - let current_token = self.current_token.clone().unwrap(); let operator = self.current_token.as_ref().unwrap().to_string(); @@ -757,11 +683,6 @@ impl<'a> Parser<'a> { } fn parse_prefix_expression(&mut self) -> Result { - info!( - "parse_prefix_expression: Current token: {:?}", - self.current_token - ); - let current_token = self.current_token.clone().unwrap(); let operator = self.current_token.as_ref().unwrap().to_string(); diff --git a/vm/src/lib.rs b/vm/src/lib.rs index fe7d940..4c2c514 100644 --- a/vm/src/lib.rs +++ b/vm/src/lib.rs @@ -263,6 +263,50 @@ impl Vm { self.push(Rc::new(result)); } + Opcode::OpArray => { + let num_elements = + BigEndian::read_u16(&self.instructions.0[ip..ip + 2]) as usize; + + ip += 2; + + let mut elements = Vec::with_capacity(num_elements); + + for _ in 0..num_elements { + elements.push(self.pop()); + } + + elements.reverse(); + + self.push(Rc::new(Object::Array(elements))); + } + Opcode::OpIndex => { + let index = self.pop(); + let left = self.pop(); + + let result = match (&*left, &*index) { + (Object::Array(elements), Object::Integer(integer)) => { + let idx = *integer as usize; + + if idx >= elements.len() { + return Err(Error::msg(format!( + "index out of bounds: index={}, length={}", + idx, + elements.len() + ))); + } + + Rc::clone(&elements[idx]) + } + _ => { + return Err(Error::msg(format!( + "unsupported types for index: {}[{}]", + left, index + ))); + } + }; + + self.push(result); + } _ => { return Err(Error::msg(format!("unknown opcode: {}", op))); } @@ -291,6 +335,16 @@ impl Vm { } } +impl std::fmt::Debug for Vm { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + write!( + f, + "VM {{ constants: {:?}, instructions: {:?}, stack: {:?}, stack_pointer: {} }}", + self.constants, self.instructions, self.stack, self.stack_pointer + ) + } +} + fn is_truthy(object: &Object) -> bool { match object { Object::Boolean(boolean) => *boolean, diff --git a/vm/tests/vm_tests.rs b/vm/tests/vm_tests.rs index 8ccf3b2..5f773e2 100644 --- a/vm/tests/vm_tests.rs +++ b/vm/tests/vm_tests.rs @@ -20,8 +20,12 @@ fn run_vm_tests(tests: Vec) -> Result<(), Error> { let mut vm = Vm::new(bytecode); + println!("Running: {}", test.input); + vm.run()?; + // dbg!(&vm); + let stack_elem = vm.last_popped_stack_elem(); assert_eq!(stack_elem.to_string(), test.expected); @@ -30,6 +34,28 @@ fn run_vm_tests(tests: Vec) -> Result<(), Error> { Ok(()) } +#[test] +fn test_array_expressions() -> Result<(), Error> { + let tests = vec![ + VmTestCase { + input: "[]".to_string(), + expected: "[]".to_string(), + }, + VmTestCase { + input: "[1, 2, 3]".to_string(), + expected: "[1, 2, 3]".to_string(), + }, + VmTestCase { + input: "[1 + 2, 3 * 4, 5 + 6]".to_string(), + expected: "[3, 12, 11]".to_string(), + }, + ]; + + run_vm_tests(tests)?; + + Ok(()) +} + #[test] fn test_boolean_expressions() -> Result<(), Error> { let tests = vec![ @@ -250,6 +276,28 @@ fn test_global_dollar_statements() -> Result<(), Error> { Ok(()) } +#[test] +fn test_index_expressions() -> Result<(), Error> { + let tests = vec![ + VmTestCase { + input: "[1, 2, 3][1]".to_string(), + expected: "2".to_string(), + }, + VmTestCase { + input: "[1, 2, 3][0 + 2]".to_string(), + expected: "3".to_string(), + }, + VmTestCase { + input: "[[1, 1, 1]][0][0]".to_string(), + expected: "1".to_string(), + }, + ]; + + run_vm_tests(tests)?; + + Ok(()) +} + #[test] fn test_string_expressions() -> Result<(), Error> { let tests = vec![ diff --git a/wasm/js/index.js b/wasm/js/index.js index 7ed1046..6cf8aea 100644 --- a/wasm/js/index.js +++ b/wasm/js/index.js @@ -8,6 +8,10 @@ import("../pkg/index.js") outputElement.innerHTML += `> php-rs interpreter 0.1.0\n`; outputElement.innerHTML += `Loaded WASM bundle.\n\n`; + inputElement.addEventListener("focus", function () { + document.body.scrollTop = document.body.scrollHeight; + }); + inputElement.addEventListener("keydown", (event) => { if (event.key === "Enter" && !event.shiftKey) { event.preventDefault(); diff --git a/wasm/static/index.html b/wasm/static/index.html index d3d05f2..448c824 100644 --- a/wasm/static/index.html +++ b/wasm/static/index.html @@ -9,6 +9,10 @@ rel="stylesheet" />