diff --git a/Cargo.lock b/Cargo.lock index 70e55ba..efc8e9d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -125,7 +125,7 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "chimpanzee" -version = "0.0.0" +version = "0.1.0" dependencies = [ "byteorder", "chrono", diff --git a/README.md b/README.md index e0953f7..2ef2401 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,7 @@ There are some issues that I want to fix before I can call this implementation c To start the REPL, run the following command: ```bash -cargo run --release --bin monkey +monkey ``` ### File interpreter @@ -24,7 +24,7 @@ cargo run --release --bin monkey To run a Monkey file, run the following command: ```bash -cargo run --release --bin monkey -- +monkey ``` ### Other modes @@ -32,7 +32,7 @@ cargo run --release --bin monkey -- You can also test the compiler, parser and lexer in the same way, adding the following flag after the path to the file: ```bash ---mode +monkey --mode ``` Where `` can be `compiler`, `parser`, `lexer` or `interpreter`. @@ -40,7 +40,7 @@ Where `` can be `compiler`, `parser`, `lexer` or `interpreter`. Example: ```bash -cargo run --release --bin monkey -- --mode compiler +monkey --mode compiler ``` ### Formatter @@ -49,7 +49,7 @@ A monkey formatter is also available, with the binary `monkeyfmt`. I will format To use it you only need to run the following command: ```bash -cargo run --release --bin monkeyfmt -- +monkeyfmt ``` Adding the `-r` flag after the file name will replace the contents of the file with the @@ -61,246 +61,32 @@ formatted code. If the flag is not activated, the formatted code will be printed To see the help, run the following command: ```bash -cargo run --release --bin monkey -- --help +monkey --help ``` -## Monkey syntax +## Installation -### Types +### Crates.io -The monkey language supports the following types: +`Chimpanzee` is available as a cargo crate, which means that you can install it +by simple using: -- Integers -- Booleans -- Strings -- Arrays -- Hashes -- Functions (yes, functions are a type in Monkey) - -#### Integers - -Integers are 64-bit signed integers. They are written as follows: - -```monkey -let a = 1; -let b = 2; -``` - -##### Operators - -Integers support the following operators: - -- `+`: addition -- `-`: subtraction -- `*`: multiplication -- `/`: division (integer division) -- `==`: equality -- `!=`: inequality -- `<`: less than -- `>`: greater than -- `<=`: less than or equal to -- `>=`: greater than or equal to - -#### Booleans - -Booleans are either `true` or `false`. They are written as follows: - -```monkey -let a = true; -let b = false; -``` - -##### Operators - -Booleans support the following operators: - -- `==`: equality -- `!=`: inequality -- `!`: negation -- `&&`: and -- `||`: or - -#### Strings - -Strings are sequences of characters. They are written as follows: - -```monkey -let a = "Hello, world!"; -``` - -##### String interpolation - -Strings can be interpolated using the `+` operator. The following example shows how to interpolate a string: - -```monkey -let a = "Hello " + "world!"; -``` - -###### Built-in functions - -Strings have the following built-in functions: - -- `len()`: returns the length of the string - -#### Arrays - -Arrays are sequences of values. They are written as follows: - -```monkey -let a = [1, "two", [1,2,3]]; -``` - -They can contain any type of value, including other arrays and functions. - -##### Indexing - -Arrays can be indexed using the `[]` operator. The index must be an integer. The index starts at 0. The following example shows how to index an array: - -```monkey -let a = [1,2,3]; -let b = a[0]; // b = 1 -``` - -##### Built-in functions - -Arrays have the following built-in functions: - -- `len(array)`: returns the length of the array -- `first(array)`: returns the first element of the array -- `last(array)`: returns the last element of the array -- `rest(array)`: returns a new array containing all elements except the first -- `push(array, value)`: returns a new array containing all elements of the original array and the new value (at the end) - -#### Hashes - -Hashes are key-value pairs. They are written as follows: - -```monkey -let a = {"one": 1, "two": 2}; -``` - -The keys can be: `Integer` , `Boolean` or `String`. The values can be any type of value, including other hashes and functions. - -##### Indexing - -Hashes can be indexed using the `[]` operator. The index must be a key. The following example shows how to index a hash: - -```monkey -let a = {"one": 1, "two": 2}; -let b = a["one"]; // b = 1 -``` - -##### Built-in functions - -For now hashes have no built-in functions. In the future the following built-in functions will be supported: - -- `keys(hash)`: returns an array containing all keys of the hash -- `values(hash)`: returns an array containing all values of the hash -- `add(hash, key, value)`: returns a new hash containing all key-value pairs of the original hash and the new key-value pair - -#### Functions - -The function syntax is as follows: - -```monkey -let add = fn(a, b) { - return a + b; -}; -``` - -Functions are first-class citizens in Monkey. This means that they can be assigned to variables, passed as arguments to other functions, and returned from other functions. -One example is the map function: - -```monkey -let map = fn(arr, f) { - let iter = fn(arr, accumulated) { - if (len(arr) == 0) { - accumulated - } else { - iter(rest(arr), push(accumulated, f(first(arr)))); - } - }; - iter(arr, []); -}; -let a = [1, 2, 3, 4]; -let double = fn(x) { x * 2 }; -map(a, double); -``` - -#### Return - -Functions can return a value using the `return` keyword. The following example shows how to return a value from a function: - -```monkey -let add = fn(a, b) { - return a + b; -}; -``` - -Note that the `return` keyword is optional, Monkey allows implicit returns. The following example shows how to use an implicit return: - -```monkey -let add = fn(a, b) { - a + b; -}; -``` - -### Variables - -Variables are declared using the `let` keyword. The following example shows how to declare a variable: - -```monkey -let a = 1; -``` - -Shadowing is supported. The following example shows how to shadow a variable: - -```monkey -let a = 1; -let a = 2; +```bash +cargo install chimpanzee ``` -### Control flow +### From source -#### If-else +To install it from source you bust clone the repo. Once you have clone it you build the project -The if-else syntax is as follows: - -```monkey - -if (condition) { - // code -} else { - // code -} -``` - -The following example shows how to use if-else: - -```monkey -let a = 1; -if (a == 1) { - return "a is 1"; -} else { - return "a is not 1"; -} +```bash +cargo build --release ``` -#### Loops - -For now loops are not supported. To achieve the same result as a loop, use recursion. In the future loops might be supported. - -### Comments - -For now comments are not supported ( not a huge loss :) ) +> This step can take some time, the expected time is less that 2 minutes, but it can be even longer. -### Built-in functions +In the directory `target/directory` the two executables will be now available: `monkey` and `monkeyfmt`. -Monkey has the following built-in functions: +## Monkey language -- `puts(value)`: prints the value to the console -- `len(value)` -- `first(array)` -- `last(array)` -- `rest(array)` -- `push(array, value)` +Information about the monkey language is available in the [MONKEY file](docs/MONKEY.md). diff --git a/docs/MONKEY.md b/docs/MONKEY.md new file mode 100644 index 0000000..288e436 --- /dev/null +++ b/docs/MONKEY.md @@ -0,0 +1,261 @@ +# Monkey syntax + +## Types + +The monkey language supports the following types: + +- Integers +- Booleans +- Strings +- Arrays +- Hashes +- Functions (yes, functions are a type in Monkey) + +### Integers + +Integers are 64-bit signed integers. They are written as follows: + +```monkey +let a = 1; +let b = 2; +``` + +#### Operators + +Integers support the following operators: + +- `+`: addition +- `-`: subtraction +- `*`: multiplication +- `/`: division (integer division) +- `==`: equality +- `!=`: inequality +- `<`: less than +- `>`: greater than +- `<=`: less than or equal to +- `>=`: greater than or equal to + +### Booleans + +Booleans are either `true` or `false`. They are written as follows: + +```monkey +let a = true; +let b = false; +``` + +#### Operators + +Booleans support the following operators: + +- `==`: equality +- `!=`: inequality +- `!`: negation +- `&&`: and +- `||`: or + +### Strings + +Strings are sequences of characters. They are written as follows: + +```monkey +let a = "Hello, world!"; +``` + +#### String interpolation + +Strings can be interpolated using the `+` operator. The following example shows how to interpolate a string: + +```monkey +let a = "Hello " + "world!"; +``` + +##### Built-in functions + +Strings have the following built-in functions: + +- `len()`: returns the length of the string + +### Arrays + +Arrays are sequences of values. They are written as follows: + +```monkey +let a = [1, "two", [1,2,3]]; +``` + +They can contain any type of value, including other arrays and functions. + +#### Indexing + +Arrays can be indexed using the `[]` operator. The index must be an integer. The index starts at 0. The following example shows how to index an array: + +```monkey +let a = [1,2,3]; +let b = a[0]; // b = 1 +``` + +#### Built-in functions + +Arrays have the following built-in functions: + +- `len(array)`: returns the length of the array +- `first(array)`: returns the first element of the array +- `last(array)`: returns the last element of the array +- `rest(array)`: returns a new array containing all elements except the first +- `push(array, value)`: returns a new array containing all elements of the original array and the new value (at the end) + +### Hashes + +Hashes are key-value pairs. They are written as follows: + +```monkey +let a = {"one": 1, "two": 2}; +``` + +The keys can be: `Integer` , `Boolean` or `String`. The values can be any type of value, including other hashes and functions. + +#### Indexing + +Hashes can be indexed using the `[]` operator. The index must be a key. The following example shows how to index a hash: + +```monkey +let a = {"one": 1, "two": 2}; +let b = a["one"]; // b = 1 +``` + +#### Built-in functions + +For now hashes have no built-in functions. In the future the following built-in functions will be supported: + +- `keys(hash)`: returns an array containing all keys of the hash +- `values(hash)`: returns an array containing all values of the hash +- `add(hash, key, value)`: returns a new hash containing all key-value pairs of the original hash and the new key-value pair + +### Functions + +The function syntax is as follows: + +```monkey +let add = fn(a, b) { + return a + b; +}; +``` + +Functions are first-class citizens in Monkey. This means that they can be assigned to variables, passed as arguments to other functions, and returned from other functions. +One example is the map function: + +```monkey +let map = fn(arr, f) { + let iter = fn(arr, accumulated) { + if (len(arr) == 0) { + accumulated + } else { + iter(rest(arr), push(accumulated, f(first(arr)))); + } + }; + iter(arr, []); +}; +let a = [1, 2, 3, 4]; +let double = fn(x) { x * 2 }; +map(a, double); +``` + +### Return + +Functions can return a value using the `return` keyword. The following example shows how to return a value from a function: + +```monkey +let add = fn(a, b) { + return a + b; +}; +``` + +Note that the `return` keyword is optional, Monkey allows implicit returns. The following example shows how to use an implicit return: + +```monkey +let add = fn(a, b) { + a + b; +}; +``` + +## Variables + +Variables are declared using the `let` keyword. The following example shows how to declare a variable: + +```monkey +let a = 1; +``` + +Shadowing is supported. The following example shows how to shadow a variable: + +```monkey +let a = 1; +let a = 2; +``` + +## Control flow + +### If-else + +The if-else syntax is as follows: + +```monkey + +if (condition) { + // code +} else { + // code +} +``` + +The following example shows how to use if-else: + +```monkey +let a = 1; +if (a == 1) { + return "a is 1"; +} else { + return "a is not 1"; +} +``` + +### Loops + +While loops have been implemented. + +```monkey +let a = 1; +while (a < 4) { + puts(a); + let a = a + 1; +} +``` + +You can also use `break` and `continue` inside a loop. + +```monkey +let a = 1; +while (a < 4) { + if (a == 2) { + break; + } + puts(a); + let a = a + 1; +} +``` + +## Comments + +For now comments are not supported ( not a huge loss :) ) + +## Built-in functions + +Monkey has the following built-in functions: + +- `puts(value)`: prints the value to the console +- `len(value)` +- `first(array)` +- `last(array)` +- `rest(array)` +- `push(array, value)` diff --git a/src/compiler/compiler_tests.rs b/src/compiler/compiler_tests.rs index 3fbabd1..902776c 100644 --- a/src/compiler/compiler_tests.rs +++ b/src/compiler/compiler_tests.rs @@ -1188,4 +1188,159 @@ pub mod tests { run_compiler(tests); } + + #[test] + fn test_while_statements() { + let tests = vec![CompilerTestCase { + input: r#" + while (true){ + puts("yes"); + } + "# + .to_string(), + expected_constants: vec![Object::STRING("yes".to_string())], + expected_instructions: flatten_instructions(vec![ + Opcode::True.make(vec![]), // 000 + Opcode::JumpNotTruthy.make(vec![15]), // 001 + Opcode::GetBuiltin.make(vec![5]), // 004 + Opcode::Constant.make(vec![0]), // 006 + Opcode::Call.make(vec![1]), // 009 + Opcode::Pop.make(vec![]), // 011 + Opcode::Jump.make(vec![0]), // 012 + // 015 + ]), + }]; + + run_compiler(tests); + } + + #[test] + fn test_break_in_while() { + let tests = vec![CompilerTestCase { + input: r#" + while (true){ + break; + } + "# + .to_string(), + expected_constants: vec![], + expected_instructions: flatten_instructions(vec![ + Opcode::True.make(vec![]), // 000 + Opcode::JumpNotTruthy.make(vec![10]), // 001 + Opcode::Jump.make(vec![10]), // 004 + Opcode::Jump.make(vec![0]), // 007 + // 010 + ]), + }]; + + run_compiler(tests); + } + + #[test] + fn test_nested_breaks_in_while() { + let tests = vec![CompilerTestCase { + input: r#" + while (true){ + while (true){ + break; + } + break; + } + "# + .to_string(), + expected_constants: vec![], + expected_instructions: flatten_instructions(vec![ + Opcode::True.make(vec![]), // 000 + Opcode::JumpNotTruthy.make(vec![20]), // 001 + Opcode::True.make(vec![]), // 004 + Opcode::JumpNotTruthy.make(vec![14]), // 005 + Opcode::Jump.make(vec![14]), // 008 + Opcode::Jump.make(vec![4]), // 011 + Opcode::Jump.make(vec![20]), // 014 + Opcode::Jump.make(vec![0]), // 017 + // 020 + ]), + }]; + + run_compiler(tests); + } + #[test] + fn test_continue_in_while() { + let tests = vec![CompilerTestCase { + input: r#" + while (true){ + continue; + } + "# + .to_string(), + expected_constants: vec![], + expected_instructions: flatten_instructions(vec![ + Opcode::True.make(vec![]), // 000 + Opcode::JumpNotTruthy.make(vec![10]), // 001 + Opcode::Jump.make(vec![0]), // 004 + Opcode::Jump.make(vec![0]), // 007 + // 010 + ]), + }]; + + run_compiler(tests); + } + + #[test] + fn test_nested_continue_in_while() { + let tests = vec![CompilerTestCase { + input: r#" + while (true){ + while (true){ + continue; + } + continue; + } + "# + .to_string(), + expected_constants: vec![], + expected_instructions: flatten_instructions(vec![ + Opcode::True.make(vec![]), // 000 + Opcode::JumpNotTruthy.make(vec![20]), // 001 + Opcode::True.make(vec![]), // 004 + Opcode::JumpNotTruthy.make(vec![14]), // 005 + Opcode::Jump.make(vec![4]), // 008 + Opcode::Jump.make(vec![4]), // 011 + Opcode::Jump.make(vec![0]), // 014 + Opcode::Jump.make(vec![0]), // 017 + // 020 + ]), + }]; + + run_compiler(tests); + } + + #[test] + fn test_continue_and_break_in_while() { + let tests = vec![CompilerTestCase { + input: r#" + while (true){ + while (true){ + continue; + } + break; + } + "# + .to_string(), + expected_constants: vec![], + expected_instructions: flatten_instructions(vec![ + Opcode::True.make(vec![]), // 000 + Opcode::JumpNotTruthy.make(vec![20]), // 001 + Opcode::True.make(vec![]), // 004 + Opcode::JumpNotTruthy.make(vec![14]), // 005 + Opcode::Jump.make(vec![4]), // 008 + Opcode::Jump.make(vec![4]), // 011 + Opcode::Jump.make(vec![20]), // 014 + Opcode::Jump.make(vec![0]), // 017 + // 020 + ]), + }]; + + run_compiler(tests); + } } diff --git a/src/compiler/mod.rs b/src/compiler/mod.rs index e970968..367d57a 100644 --- a/src/compiler/mod.rs +++ b/src/compiler/mod.rs @@ -16,8 +16,8 @@ use crate::{ {CompiledFunction, Object}, }, parser::ast::{ - BlockStatement, Conditional, Expression, FunctionLiteral, InfixOperator, Primitive, - Program, Statement, + BlockStatement, Conditional, Expression, FunctionLiteral, InfixOperator, LetStatement, + LoopStatement, Primitive, Program, Statement, WhileStatement, }, }; @@ -34,6 +34,7 @@ struct CompilerScope { instructions: Instructions, last_instruction: Option, previous_instruction: Option, + loop_scope: Option>>, } impl Default for CompilerScope { @@ -48,8 +49,51 @@ impl CompilerScope { instructions: Instructions::default(), last_instruction: None, previous_instruction: None, + loop_scope: None, } } + + fn enter_loop_scope(&mut self, start_position: usize) { + let loop_scope = LoopScope::new_enclosed(self.loop_scope.clone(), start_position); + self.loop_scope = Some(Rc::new(RefCell::new(loop_scope))); + } + + fn leave_loop_scope(&mut self) -> Option>> { + let outer = self.loop_scope.clone(); + self.loop_scope = self + .loop_scope + .clone() + .unwrap() + .as_ref() + .borrow() + .outer + .clone(); + outer + } +} + +struct LoopScope { + outer: Option>>, + start_position: usize, + breaks: Vec, +} + +impl LoopScope { + pub fn new_enclosed(outer: Option>>, start_position: usize) -> Self { + Self { + outer, + start_position, + breaks: vec![], + } + } + + pub fn add_break(&mut self, pos: usize) { + self.breaks.push(pos); + } + + pub fn breaks(&self) -> Vec { + self.breaks.clone() + } } pub struct Compiler { @@ -115,59 +159,74 @@ impl Compiler { self.emit(Opcode::Pop, vec![]); } Statement::Let(s) => { - // This step is extremely important. If it is not done then when shadowing variables - // and using the previous value we get an error. Because we would have assigned - // a new index to the symbol and the GetGlobal instruction would get a NULL - // value instead of the previous value. (corresponds to issue #8) - let symbol = match self.symbol_table.resolve(&s.name.value) { - Some(symbol) => match symbol.scope { - SymbolScope::Global => { - // A Local variable should never replace a global one - if self.symbol_table.has_outer() { - // This means that the symbol will - // be local and not global, and thus not - // replace the global one - self.symbol_table.define(s.name.value) - } else { - symbol - } - } - SymbolScope::Local => symbol, - - // We only want to do in in the case of "normal" variable assignation. - // The special cases should not be touched, since the program should not - // have access to them, only the compiler/vm - _ => self.symbol_table.define(s.name.value), - }, - None => self.symbol_table.define(s.name.value), - }; - - self.compile_expression(s.value)?; - - match symbol.scope { - SymbolScope::Global => { - self.emit(Opcode::SetGlobal, vec![symbol.index as i32]); - } - SymbolScope::Local => { - self.emit(Opcode::SetLocal, vec![symbol.index as i32]); - } - SymbolScope::Free => { - unreachable!( - "Free symbols should not be set, the compiler should panic before this" - ) - } - SymbolScope::Builtin => { - unreachable!("Builtin symbols should not be set, the compiler should panic before this") - } - SymbolScope::Function => { - unreachable!("Function symbols should not be set, the compiler should panic before this") - } - } + self.compiler_let_statement(s)?; } Statement::Return(r) => { self.compile_expression(r.return_value)?; self.emit(Opcode::ReturnValue, vec![]); } + Statement::While(wh) => { + self.compile_while_statement(wh)?; + } + + Statement::LoopStatements(smt) => self.compile_loop_statement(&smt), + } + + Ok(()) + } + + fn compiler_let_statement(&mut self, s: LetStatement) -> Result<(), String> { + // This step is extremely important. If it is not done then when shadowing variables + // and using the previous value we get an error. Because we would have assigned + // a new index to the symbol and the GetGlobal instruction would get a NULL + // value instead of the previous value. (corresponds to issue #8) + let symbol = match self.symbol_table.resolve(&s.name.value) { + Some(symbol) => match symbol.scope { + SymbolScope::Global => { + // A Local variable should never replace a global one + if self.symbol_table.has_outer() { + // This means that the symbol will + // be local and not global, and thus not + // replace the global one + self.symbol_table.define(s.name.value) + } else { + symbol + } + } + SymbolScope::Local => symbol, + + // We only want to do in in the case of "normal" variable assignation. + // The special cases should not be touched, since the program should not + // have access to them, only the compiler/vm + _ => self.symbol_table.define(s.name.value), + }, + None => self.symbol_table.define(s.name.value), + }; + + self.compile_expression(s.value)?; + + match symbol.scope { + SymbolScope::Global => { + self.emit(Opcode::SetGlobal, vec![symbol.index as i32]); + } + SymbolScope::Local => { + self.emit(Opcode::SetLocal, vec![symbol.index as i32]); + } + SymbolScope::Free => { + unreachable!( + "Free symbols should not be set, the compiler should panic before this" + ) + } + SymbolScope::Builtin => { + unreachable!( + "Builtin symbols should not be set, the compiler should panic before this" + ) + } + SymbolScope::Function => { + unreachable!( + "Function symbols should not be set, the compiler should panic before this" + ) + } } Ok(()) @@ -380,6 +439,64 @@ impl Compiler { Ok(()) } + fn compile_while_statement(&mut self, wh: WhileStatement) -> Result<(), String> { + let condition_pos = self.current_instructions().data.len(); + self.scopes[self.scope_index].enter_loop_scope(condition_pos); + + self.compile_expression(wh.condition)?; + + let jump_not_truthy_pos = self.emit(Opcode::JumpNotTruthy, vec![9999]); // We emit a dummy value for the jump offset + // and we will fix it later + self.compile_block_statement(wh.body)?; + + self.emit(Opcode::Jump, vec![condition_pos as i32]); // We emit a dummy value for the jump offset + // and we will fix it later + + let after_body_pos = self.current_instructions().data.len(); + self.change_operand(jump_not_truthy_pos, after_body_pos as i32)?; + + for break_pos in self.scopes[self.scope_index] + .loop_scope + .clone() // TODO: Improve this + .unwrap() + .as_ref() + .borrow() + .breaks() + { + self.change_operand(break_pos, after_body_pos as i32)?; + } + + self.scopes[self.scope_index].leave_loop_scope(); + + Ok(()) + } + + fn compile_loop_statement(&mut self, smt: &LoopStatement) { + match smt { + LoopStatement::Break => { + let pos = self.emit(Opcode::Jump, vec![9999]); // We emit a dummy value for the jump offset + // and we will fix it later + self.scopes[self.scope_index] + .loop_scope + .clone() + .unwrap() + .as_ref() + .borrow_mut() + .add_break(pos); + } + LoopStatement::Continue => { + let while_initial_pos = self.scopes[self.scope_index] + .loop_scope + .as_ref() + .unwrap() + .borrow() + .start_position; + + self.emit(Opcode::Jump, vec![while_initial_pos as i32]); + } + } + } + fn last_instruction_is(&self, opcode: Opcode) -> bool { match self.scopes[self.scope_index].last_instruction { Some(ref last) => last.opcode == opcode, diff --git a/src/formatter/formatter_tests.rs b/src/formatter/formatter_tests.rs index 088f9e9..8a2c2ed 100644 --- a/src/formatter/formatter_tests.rs +++ b/src/formatter/formatter_tests.rs @@ -536,6 +536,46 @@ let a = 10; let expected = r#"puts("Hello, Monkey!"); let arr = [1, 2, 3]; let length = len(arr); +"#; + + assert_eq!(format(input), expected); + } + + #[test] + fn test_while() { + let input = r#" + let a = 1; + while (a<3){ +let a = a + 1, +puts(21); + } + let a = fn (x){ + let a = 1; + while (x > 0){ + let a = a * 2; + break; + } + continue; + a + }; + a(12); + "#; + + let expected = r#"let a = 1; +while (a < 3) { + let a = a + 1; + puts(21); +} +let a = fn (x) { + let a = 1; + while (x > 0) { + let a = a * 2; + break; + } + continue; + a +}; +a(12); "#; assert_eq!(format(input), expected); diff --git a/src/formatter/mod.rs b/src/formatter/mod.rs index 8a1b794..594f4d8 100644 --- a/src/formatter/mod.rs +++ b/src/formatter/mod.rs @@ -112,6 +112,20 @@ impl Formatter { self.push(";"); } } + Statement::While(wh) => { + self.push("while ("); + self.visit_expression(&wh.condition); + self.push(") {\n"); + self.indent += 1; + self.visit_block_statement(&wh.body); + self.indent -= 1; + self.push_indent(); + self.push("}"); + } + Statement::LoopStatements(cf) => { + self.push(cf.to_string().as_str()); + self.push(";"); + } } self.push("\n"); self.last_expression = None; diff --git a/src/interpreter/evaluator.rs b/src/interpreter/evaluator.rs index f598c72..530f340 100644 --- a/src/interpreter/evaluator.rs +++ b/src/interpreter/evaluator.rs @@ -55,6 +55,7 @@ impl Evaluator { result } + #[allow(clippy::match_wildcard_for_single_variants, unreachable_patterns)] fn eval_statement(&mut self, statement: Statement) -> Object { match statement { Statement::Expression(x) => self.eval_expression(x), @@ -73,9 +74,24 @@ impl Evaluator { self.env.borrow_mut().set(x.name.to_string(), value); NULL } + Statement::While(stm) => { + let mut result = NULL; + while Self::is_truthy(&self.eval_expression(stm.condition.clone())) { + result = self.eval_block_statemet(stm.body.clone()); + match result { + Object::RETURN(_) | Object::ERROR(_) => return result, + _ => (), + } + } + result + } + + _ => unimplemented!(), // I have decided not to implement the rest of the expressions, + // I will focus on the compiler } } + #[allow(clippy::match_wildcard_for_single_variants, unreachable_patterns)] fn eval_expression(&mut self, expression: Expression) -> Object { match expression { Expression::Primitive(x) => Self::eval_primitive_expression(x), @@ -128,6 +144,8 @@ impl Evaluator { self.eval_index_expression(index_expression) } Expression::HashMapLiteral(hashmap) => self.eval_hashmap_literal(hashmap), + _ => unimplemented!(), // I have decided not to implement the rest of the expressions, + // I will focus on the compiler } } @@ -351,551 +369,3 @@ impl Evaluator { Object::HASHMAP(hashmap) } } -#[cfg(test)] -mod tests { - - use super::*; - use crate::{lexer::Lexer, parser::Parser}; - use std::collections::HashMap; - - #[test] - fn test_eval_integer_expression() { - let tests = vec![ - ("5", 5), - ("10", 10), - ("-5", -5), - ("-10", -10), - ("5 + 5 + 5 + 5 - 10", 10), - ("2 * 2 * 2 * 2 * 2", 32), - ("-50 + 100 + -50", 0), - ("5 * 2 + 10", 20), - ("5 + 2 * 10", 25), - ("20 + 2 * -10", 0), - ("50 / 2 * 2 + 10", 60), - ("2 * (5 + 10)", 30), - ("3 * 3 * 3 + 10", 37), - ("3 * (3 * 3) + 10", 37), - ("(5 + 10 * 2 + 15 / 3) * 2 + -10", 50), - ]; - - for (input, expected) in tests { - let evaluated = test_eval(input); - test_integer_object(evaluated, expected); - } - } - - #[test] - fn test_eval_boolean_expression() { - let tests = vec![ - ("true", true), - ("false", false), - ("1 < 2", true), - ("1 > 2", false), - ("1 < 1", false), - ("1 > 1", false), - ("1 <= 2", true), - ("1 >= 2", false), - ("1 <= 1", true), - ("1 >= 1", true), - ("1 == 1", true), - ("1 != 1", false), - ("1 == 2", false), - ("1 != 2", true), - // - ("true == true", true), - ("false == false", true), - ("true == false", false), - ("true != false", true), - ("false != true", true), - // - ("false && true", false), - ("true && false", false), - ("false && false", false), - ("true && true", true), - // - ("false || true", true), - ("true || false", true), - ("false || false", false), - ("true || true", true), - // - ("(1 < 2) == true", true), - ("(1 < 2) == false", false), - ("(1 > 2) == true", false), - ("(1 > 2) == false", true), - ]; - - for (input, expected) in tests { - let evaluated = test_eval(input); - test_boolean_object(evaluated, expected); - } - } - - #[test] - fn test_bang_operator() { - let tests = vec![ - ("!true", false), - ("!false", true), - ("!5", false), - ("!!true", true), - ("!!false", false), - ("!!5", true), - ]; - - for (input, expected) in tests { - let evaluated = test_eval(input); - test_boolean_object(evaluated, expected); - } - } - - #[test] - fn test_if_else_expression() { - let tests = vec![ - ("if (true) { 10 }", Some(10)), - ("if (false) { 10 }", None), - ("if (1) { 10 }", Some(10)), - ("if (1 < 2) { 10 }", Some(10)), - ("if (1 > 2) { 10 }", None), - ("if (1 > 2) { 10 } else { 20 }", Some(20)), - ("if (1 < 2) { 10 } else { 20 }", Some(10)), - ]; - - for (input, expected) in tests { - let evaluated = test_eval(input); - if let Some(expected) = expected { - test_integer_object(evaluated, expected); - } else { - test_null_object(evaluated); - } - } - } - - #[test] - fn test_return_statements() { - let tests = vec![ - ("return 10;", 10), - ("return 10; 9;", 10), - ("return 2 * 5; 9;", 10), - ("9; return 2 * 5; 9;", 10), - ("if (10 > 1) { return 10; }", 10), - ("if (10 > 1) { if (10 > 1) { return 10; } return 1; }", 10), - ]; - - for (input, expected) in tests { - let evaluated = test_eval(input); - test_integer_object(evaluated, expected); - } - } - - #[test] - fn test_error_handling() { - let tests = vec![ - ("5 + true;", "type mismatch: INTEGER + BOOLEAN"), - ("5 + true; 5;", "type mismatch: INTEGER + BOOLEAN"), - ("-true", "unknown operator: -true"), - ("true + false;", "unknown operator: BOOLEAN + BOOLEAN"), - ("5; true + false; 5", "unknown operator: BOOLEAN + BOOLEAN"), - ( - "if (10 > 1) { true + false; }", - "unknown operator: BOOLEAN + BOOLEAN", - ), - ( - r#" - if (10 > 1) { - if (10 > 1) { - return true + false; - } - return 1; - }"#, - "unknown operator: BOOLEAN + BOOLEAN", - ), - ("foobar", "identifier not found: foobar"), - (r#""Hello" - "World""#, "unknown operator: STRING - STRING"), - ( - r#"{"name": "Monkey"}[fn(x) { x }];"#, - "unusable as hash key: FUNCTION", - ), - ]; - - for (input, expected) in tests { - let evaluated = test_eval(input); - test_error_object(evaluated, expected.to_string()); - } - } - - #[test] - fn test_let_stateemtns() { - let tests = vec![ - ("let a = 5; a;", Some(5)), - ("let a = 5 * 5; a;", Some(25)), - ("let a = 5; let b = a; b;", Some(5)), - ("let a = 5; let b = a; let c = a + b + 5; c;", Some(15)), - ("let a = 5;", None), - ]; - - for (input, expected) in tests { - let evaluated = test_eval(input); - match expected { - Some(expected) => test_integer_object(evaluated, expected), - None => test_null_object(evaluated), - } - } - } - - #[test] - fn test_function_object() { - let input = "fn(x) { x + 2; };"; - - let evaluated = test_eval(input); - - match evaluated { - Object::FUNCTION(x) => { - assert_eq!(x.parameters.len(), 1); - assert_eq!(x.parameters[0].to_string(), "x"); - assert_eq!(x.body.to_string(), "(x + 2)\n"); - } - _ => panic!("The object is not a function"), - } - } - - #[test] - fn test_function_application() { - let tests = vec![ - ("let identity = fn(x) { x; }; identity(5);", 5), - ("let identity = fn(x) { return x; }; identity(5);", 5), - ("let double = fn(x) { x * 2; }; double(5);", 10), - ("let add = fn(x, y) { x + y; }; add(5, 11);", 16), - ( - "let add = fn(x, y) { x + y; }; add(5 + 5, add(10, 10));", - 30, - ), - ("fn(x) { x; }(5)", 5), - ]; - for (input, expected) in tests { - let evaluated = test_eval(input); - test_integer_object(evaluated, expected); - } - } - - #[test] - fn test_closures() { - let input = r#" - let newAdder = fn(x) { - fn(y) { x + y }; - }; - - let addTwo = newAdder(2); - addTwo(2);"#; - - test_integer_object(test_eval(input), 4); - } - - #[test] - fn test_string_literal() { - let input = "\"Hello World!\""; - - let evaluated = test_eval(input); - - test_string_object(evaluated, "Hello World!".to_string()); - } - - #[test] - fn test_string_concatenationm() { - let input = "\"Hello\" + \" \" + \"World!\""; - - let evaluated = test_eval(input); - - test_string_object(evaluated, "Hello World!".to_string()); - } - - #[test] - fn test_builttin_len_function() { - let tests_striung = vec![ - (r#"len("")"#, 0), - (r#"len("four")"#, 4), - (r#"len("hello world")"#, 11), - (r#"len([1,2,3,4,5])"#, 5), - ]; - - for (input, expected) in tests_striung { - test_integer_object(test_eval(input), expected); - } - } - - #[test] - fn test_builttin_len_function_errors() { - let tests_striung = vec![ - (r#"len(1)"#, "argument to `len` not supported, got INTEGER"), - ( - r#"len("one", "two")"#, - "wrong number of arguments. got=2, want=1", - ), - ]; - - for (input, expected) in tests_striung { - test_error_object(test_eval(input), expected.to_string()); - } - } - - #[test] - fn test_array_literals() { - let input = "[1, 2 * 2, 3 + 3]"; - - let evaluated = test_eval(input); - - match evaluated { - Object::ARRAY(x) => { - assert_eq!(x.len(), 3); - test_integer_object(x[0].clone(), 1); - test_integer_object(x[1].clone(), 4); - test_integer_object(x[2].clone(), 6); - } - _ => panic!("The object is not an array"), - } - } - - #[test] - fn test_array_index_expression() { - let tests = vec![ - ("[1, 2, 3][0]", Some(1)), - ("[1, 2, 3][1]", Some(2)), - ("[1, 2, 3][2]", Some(3)), - ("let i = 0; [1][i];", Some(1)), - ("[1, 2, 3][1 + 1];", Some(3)), - ("let myArray = [1, 2, 3]; myArray[2];", Some(3)), - ( - "let myArray = [1, 2, 3]; myArray[0] + myArray[1] + myArray[2];", - Some(6), - ), - ( - "let myArray = [1, 2, 3]; let i = myArray[0]; myArray[i]", - Some(2), - ), - ("[1, 2, 3][3]", None), - ("[1, 2, 3][-1]", None), - ]; - - for (input, expected) in tests { - match expected { - Some(x) => test_integer_object(test_eval(input), x), - None => test_null_object(test_eval(input)), - } - } - } - - #[test] - fn test_first_function() { - let tests = vec![ - ("first([1, 2, 3])", Some(1)), - ("first([1])", Some(1)), - ("first([])", None), - ("first(1)", None), - ("first([1, 2, 3], [4, 5, 6])", None), - ]; - - for (input, expected) in tests { - println!("{input}"); - match expected { - Some(x) => test_integer_object(test_eval(input), x), - None => test_null_object(test_eval(input)), - } - } - } - - #[test] - fn test_last_function() { - let tests = vec![ - ("last([1, 2, 3])", Some(3)), - ("last([1])", Some(1)), - ("last([])", None), - ("last(1)", None), - ("last([1, 2, 3], [4, 5, 6])", None), - ]; - - for (input, expected) in tests { - println!("{input}"); - match expected { - Some(x) => test_integer_object(test_eval(input), x), - None => test_null_object(test_eval(input)), - } - } - } - - #[test] - fn test_rest_function() { - let tests = vec![ - ("rest([1, 2, 3])", Some(vec![2, 3])), - ("rest([1])", Some(Vec::new())), - ("rest([])", None), - ("rest(1)", None), - ("rest([1, 2, 3], [4, 5, 6])", None), - ]; - - for (input, expected) in tests { - println!("{input}"); - match expected { - Some(x) => { - let evaluated = test_eval(input); - test_array_object(evaluated, x); - } - None => test_null_object(test_eval(input)), - } - } - } - - #[test] - fn test_push_function() { - let tests = vec![ - ("push([], 1)", Some(vec![1])), - ("push([1], 2)", Some(vec![1, 2])), - ("push([1,2], 3)", Some(vec![1, 2, 3])), - ("push(1, 1)", None), - ("push([1,2], 3, 4)", None), - ]; - - for (input, expected) in tests { - println!("{input}"); - match expected { - Some(x) => test_array_object(test_eval(input), x), - None => test_null_object(test_eval(input)), - } - } - } - - #[test] - fn test_array_functions_together() { - let input = r#" - let map = fn(arr, f) { - let iter = fn(arr, accumulated) { - if (len(arr) == 0) { - accumulated - } else { - iter(rest(arr), push(accumulated, f(first(arr)))); - } - }; - iter(arr, []); - }; - let a = [1, 2, 3, 4]; - let double = fn(x) { x * 2 }; - map(a, double); - "#; - - let expected = vec![2, 4, 6, 8]; - - test_array_object(test_eval(input), expected); - } - - #[test] - fn test_evaluate_hash_literals() { - let input = r#" - let two = "two"; - { - "one": 10 - 9, - two: 1 + 1, - "thr" + "ee": 6 / 2, - 4: 4, - true: 5, - false: 6 - } - "#; - - let mut expected = HashMap::new(); - expected.insert(Object::STRING("one".to_string()), Object::INTEGER(1)); - expected.insert(Object::STRING("two".to_string()), Object::INTEGER(2)); - expected.insert(Object::STRING("three".to_string()), Object::INTEGER(3)); - expected.insert(Object::INTEGER(4), Object::INTEGER(4)); - expected.insert(Object::BOOLEAN(true), Object::INTEGER(5)); - expected.insert(Object::BOOLEAN(false), Object::INTEGER(6)); - - let evaluated = test_eval(input); - match evaluated { - Object::HASHMAP(hash) => { - assert_eq!(hash.len(), expected.len()); - - for (expected_key, expected_value) in expected { - match hash.get(&expected_key) { - Some(value) => assert_eq!(value, &expected_value), - None => panic!("No pair for given key in Pairs"), - } - } - } - _ => panic!("The object is not a hash"), - } - } - - #[test] - fn test_hash_index_expressions() { - let tests = vec![ - (r#"{"foo": 5}["foo"]"#, Some(5)), - (r#"{"foo": 5}["bar"]"#, None), - (r#"let key = "foo"; {"foo": 5}[key]"#, Some(5)), - (r#"{}["foo"]"#, None), - (r#"{5: 5}[5]"#, Some(5)), - (r#"{true: 5}[true]"#, Some(5)), - (r#"{false: 5}[false]"#, Some(5)), - ]; - - for (input, expected) in tests { - println!("{input}"); - match expected { - Some(x) => test_integer_object(test_eval(input), x), - None => test_null_object(test_eval(input)), - } - } - } - - fn test_eval(input: &str) -> Object { - let lexer = Lexer::new(input); - let mut parser = Parser::new(lexer); - let program = parser.parse_program(); - let mut evaluator = Evaluator::new(); - evaluator.eval(program) - } - - fn test_integer_object(object: Object, expected: i64) { - match object { - Object::INTEGER(x) => assert_eq!(x, expected), - _ => panic!("The object is not an integer"), - } - } - - fn test_boolean_object(object: Object, expected: bool) { - match object { - Object::BOOLEAN(x) => assert_eq!(x, expected), - _ => panic!("The object is not a boolean"), - } - } - - fn test_null_object(object: Object) { - match object { - Object::NULL | Object::ERROR(_) => (), - - _ => panic!("The object is not null"), - } - } - - fn test_error_object(object: Object, expected: String) { - match object { - Object::ERROR(x) => assert_eq!(x, expected), - _ => panic!("The object is not an error"), - } - } - - fn test_string_object(object: Object, expected: String) { - match object { - Object::STRING(s) => assert_eq!(format!("{s}"), expected), - _ => panic!("The object is not an string"), - } - } - - fn test_array_object(object: Object, expected: Vec) { - match object { - Object::ARRAY(x) => { - assert_eq!(x.len(), expected.len()); - for (i, v) in x.iter().enumerate() { - test_integer_object(v.clone(), expected[i]); - } - } - _ => panic!("The object is not an array"), - } - } -} diff --git a/src/interpreter/evaluator_tests.rs b/src/interpreter/evaluator_tests.rs new file mode 100644 index 0000000..b9f5b27 --- /dev/null +++ b/src/interpreter/evaluator_tests.rs @@ -0,0 +1,567 @@ +#[cfg(test)] +mod tests { + + use crate::{interpreter::evaluator::Evaluator, lexer::Lexer, object::Object, parser::Parser}; + use std::collections::HashMap; + + #[test] + fn test_eval_integer_expression() { + let tests = vec![ + ("5", 5), + ("10", 10), + ("-5", -5), + ("-10", -10), + ("5 + 5 + 5 + 5 - 10", 10), + ("2 * 2 * 2 * 2 * 2", 32), + ("-50 + 100 + -50", 0), + ("5 * 2 + 10", 20), + ("5 + 2 * 10", 25), + ("20 + 2 * -10", 0), + ("50 / 2 * 2 + 10", 60), + ("2 * (5 + 10)", 30), + ("3 * 3 * 3 + 10", 37), + ("3 * (3 * 3) + 10", 37), + ("(5 + 10 * 2 + 15 / 3) * 2 + -10", 50), + ]; + + for (input, expected) in tests { + let evaluated = test_eval(input); + test_integer_object(evaluated, expected); + } + } + + #[test] + fn test_eval_boolean_expression() { + let tests = vec![ + ("true", true), + ("false", false), + ("1 < 2", true), + ("1 > 2", false), + ("1 < 1", false), + ("1 > 1", false), + ("1 <= 2", true), + ("1 >= 2", false), + ("1 <= 1", true), + ("1 >= 1", true), + ("1 == 1", true), + ("1 != 1", false), + ("1 == 2", false), + ("1 != 2", true), + // + ("true == true", true), + ("false == false", true), + ("true == false", false), + ("true != false", true), + ("false != true", true), + // + ("false && true", false), + ("true && false", false), + ("false && false", false), + ("true && true", true), + // + ("false || true", true), + ("true || false", true), + ("false || false", false), + ("true || true", true), + // + ("(1 < 2) == true", true), + ("(1 < 2) == false", false), + ("(1 > 2) == true", false), + ("(1 > 2) == false", true), + ]; + + for (input, expected) in tests { + let evaluated = test_eval(input); + test_boolean_object(evaluated, expected); + } + } + + #[test] + fn test_bang_operator() { + let tests = vec![ + ("!true", false), + ("!false", true), + ("!5", false), + ("!!true", true), + ("!!false", false), + ("!!5", true), + ]; + + for (input, expected) in tests { + let evaluated = test_eval(input); + test_boolean_object(evaluated, expected); + } + } + + #[test] + fn test_if_else_expression() { + let tests = vec![ + ("if (true) { 10 }", Some(10)), + ("if (false) { 10 }", None), + ("if (1) { 10 }", Some(10)), + ("if (1 < 2) { 10 }", Some(10)), + ("if (1 > 2) { 10 }", None), + ("if (1 > 2) { 10 } else { 20 }", Some(20)), + ("if (1 < 2) { 10 } else { 20 }", Some(10)), + ]; + + for (input, expected) in tests { + let evaluated = test_eval(input); + if let Some(expected) = expected { + test_integer_object(evaluated, expected); + } else { + test_null_object(evaluated); + } + } + } + + #[test] + fn test_return_statements() { + let tests = vec![ + ("return 10;", 10), + ("return 10; 9;", 10), + ("return 2 * 5; 9;", 10), + ("9; return 2 * 5; 9;", 10), + ("if (10 > 1) { return 10; }", 10), + ("if (10 > 1) { if (10 > 1) { return 10; } return 1; }", 10), + ]; + + for (input, expected) in tests { + let evaluated = test_eval(input); + test_integer_object(evaluated, expected); + } + } + + #[test] + fn test_error_handling() { + let tests = vec![ + ("5 + true;", "type mismatch: INTEGER + BOOLEAN"), + ("5 + true; 5;", "type mismatch: INTEGER + BOOLEAN"), + ("-true", "unknown operator: -true"), + ("true + false;", "unknown operator: BOOLEAN + BOOLEAN"), + ("5; true + false; 5", "unknown operator: BOOLEAN + BOOLEAN"), + ( + "if (10 > 1) { true + false; }", + "unknown operator: BOOLEAN + BOOLEAN", + ), + ( + r#" + if (10 > 1) { + if (10 > 1) { + return true + false; + } + return 1; + }"#, + "unknown operator: BOOLEAN + BOOLEAN", + ), + ("foobar", "identifier not found: foobar"), + (r#""Hello" - "World""#, "unknown operator: STRING - STRING"), + ( + r#"{"name": "Monkey"}[fn(x) { x }];"#, + "unusable as hash key: FUNCTION", + ), + ]; + + for (input, expected) in tests { + let evaluated = test_eval(input); + test_error_object(evaluated, expected.to_string()); + } + } + + #[test] + fn test_let_stateemtns() { + let tests = vec![ + ("let a = 5; a;", Some(5)), + ("let a = 5 * 5; a;", Some(25)), + ("let a = 5; let b = a; b;", Some(5)), + ("let a = 5; let b = a; let c = a + b + 5; c;", Some(15)), + ("let a = 5;", None), + ]; + + for (input, expected) in tests { + let evaluated = test_eval(input); + match expected { + Some(expected) => test_integer_object(evaluated, expected), + None => test_null_object(evaluated), + } + } + } + + #[test] + fn test_function_object() { + let input = "fn(x) { x + 2; };"; + + let evaluated = test_eval(input); + + match evaluated { + Object::FUNCTION(x) => { + assert_eq!(x.parameters.len(), 1); + assert_eq!(x.parameters[0].to_string(), "x"); + assert_eq!(x.body.to_string(), "(x + 2)\n"); + } + _ => panic!("The object is not a function"), + } + } + + #[test] + fn test_function_application() { + let tests = vec![ + ("let identity = fn(x) { x; }; identity(5);", 5), + ("let identity = fn(x) { return x; }; identity(5);", 5), + ("let double = fn(x) { x * 2; }; double(5);", 10), + ("let add = fn(x, y) { x + y; }; add(5, 11);", 16), + ( + "let add = fn(x, y) { x + y; }; add(5 + 5, add(10, 10));", + 30, + ), + ("fn(x) { x; }(5)", 5), + ]; + for (input, expected) in tests { + let evaluated = test_eval(input); + test_integer_object(evaluated, expected); + } + } + + #[test] + fn test_closures() { + let input = r#" + let newAdder = fn(x) { + fn(y) { x + y }; + }; + + let addTwo = newAdder(2); + addTwo(2);"#; + + test_integer_object(test_eval(input), 4); + } + + #[test] + fn test_string_literal() { + let input = "\"Hello World!\""; + + let evaluated = test_eval(input); + + test_string_object(evaluated, "Hello World!".to_string()); + } + + #[test] + fn test_string_concatenationm() { + let input = "\"Hello\" + \" \" + \"World!\""; + + let evaluated = test_eval(input); + + test_string_object(evaluated, "Hello World!".to_string()); + } + + #[test] + fn test_builttin_len_function() { + let tests_striung = vec![ + (r#"len("")"#, 0), + (r#"len("four")"#, 4), + (r#"len("hello world")"#, 11), + (r#"len([1,2,3,4,5])"#, 5), + ]; + + for (input, expected) in tests_striung { + test_integer_object(test_eval(input), expected); + } + } + + #[test] + fn test_builttin_len_function_errors() { + let tests_striung = vec![ + (r#"len(1)"#, "argument to `len` not supported, got INTEGER"), + ( + r#"len("one", "two")"#, + "wrong number of arguments. got=2, want=1", + ), + ]; + + for (input, expected) in tests_striung { + test_error_object(test_eval(input), expected.to_string()); + } + } + + #[test] + fn test_array_literals() { + let input = "[1, 2 * 2, 3 + 3]"; + + let evaluated = test_eval(input); + + match evaluated { + Object::ARRAY(x) => { + assert_eq!(x.len(), 3); + test_integer_object(x[0].clone(), 1); + test_integer_object(x[1].clone(), 4); + test_integer_object(x[2].clone(), 6); + } + _ => panic!("The object is not an array"), + } + } + + #[test] + fn test_array_index_expression() { + let tests = vec![ + ("[1, 2, 3][0]", Some(1)), + ("[1, 2, 3][1]", Some(2)), + ("[1, 2, 3][2]", Some(3)), + ("let i = 0; [1][i];", Some(1)), + ("[1, 2, 3][1 + 1];", Some(3)), + ("let myArray = [1, 2, 3]; myArray[2];", Some(3)), + ( + "let myArray = [1, 2, 3]; myArray[0] + myArray[1] + myArray[2];", + Some(6), + ), + ( + "let myArray = [1, 2, 3]; let i = myArray[0]; myArray[i]", + Some(2), + ), + ("[1, 2, 3][3]", None), + ("[1, 2, 3][-1]", None), + ]; + + for (input, expected) in tests { + match expected { + Some(x) => test_integer_object(test_eval(input), x), + None => test_null_object(test_eval(input)), + } + } + } + + #[test] + fn test_first_function() { + let tests = vec![ + ("first([1, 2, 3])", Some(1)), + ("first([1])", Some(1)), + ("first([])", None), + ("first(1)", None), + ("first([1, 2, 3], [4, 5, 6])", None), + ]; + + for (input, expected) in tests { + println!("{input}"); + match expected { + Some(x) => test_integer_object(test_eval(input), x), + None => test_null_object(test_eval(input)), + } + } + } + + #[test] + fn test_last_function() { + let tests = vec![ + ("last([1, 2, 3])", Some(3)), + ("last([1])", Some(1)), + ("last([])", None), + ("last(1)", None), + ("last([1, 2, 3], [4, 5, 6])", None), + ]; + + for (input, expected) in tests { + println!("{input}"); + match expected { + Some(x) => test_integer_object(test_eval(input), x), + None => test_null_object(test_eval(input)), + } + } + } + + #[test] + fn test_rest_function() { + let tests = vec![ + ("rest([1, 2, 3])", Some(vec![2, 3])), + ("rest([1])", Some(Vec::new())), + ("rest([])", None), + ("rest(1)", None), + ("rest([1, 2, 3], [4, 5, 6])", None), + ]; + + for (input, expected) in tests { + println!("{input}"); + match expected { + Some(x) => { + let evaluated = test_eval(input); + test_array_object(evaluated, x); + } + None => test_null_object(test_eval(input)), + } + } + } + + #[test] + fn test_push_function() { + let tests = vec![ + ("push([], 1)", Some(vec![1])), + ("push([1], 2)", Some(vec![1, 2])), + ("push([1,2], 3)", Some(vec![1, 2, 3])), + ("push(1, 1)", None), + ("push([1,2], 3, 4)", None), + ]; + + for (input, expected) in tests { + println!("{input}"); + match expected { + Some(x) => test_array_object(test_eval(input), x), + None => test_null_object(test_eval(input)), + } + } + } + + #[test] + fn test_array_functions_together() { + let input = r#" + let map = fn(arr, f) { + let iter = fn(arr, accumulated) { + if (len(arr) == 0) { + accumulated + } else { + iter(rest(arr), push(accumulated, f(first(arr)))); + } + }; + iter(arr, []); + }; + let a = [1, 2, 3, 4]; + let double = fn(x) { x * 2 }; + map(a, double); + "#; + + let expected = vec![2, 4, 6, 8]; + + test_array_object(test_eval(input), expected); + } + + #[test] + fn test_evaluate_hash_literals() { + let input = r#" + let two = "two"; + { + "one": 10 - 9, + two: 1 + 1, + "thr" + "ee": 6 / 2, + 4: 4, + true: 5, + false: 6 + } + "#; + + let mut expected = HashMap::new(); + expected.insert(Object::STRING("one".to_string()), Object::INTEGER(1)); + expected.insert(Object::STRING("two".to_string()), Object::INTEGER(2)); + expected.insert(Object::STRING("three".to_string()), Object::INTEGER(3)); + expected.insert(Object::INTEGER(4), Object::INTEGER(4)); + expected.insert(Object::BOOLEAN(true), Object::INTEGER(5)); + expected.insert(Object::BOOLEAN(false), Object::INTEGER(6)); + + let evaluated = test_eval(input); + match evaluated { + Object::HASHMAP(hash) => { + assert_eq!(hash.len(), expected.len()); + + for (expected_key, expected_value) in expected { + match hash.get(&expected_key) { + Some(value) => assert_eq!(value, &expected_value), + None => panic!("No pair for given key in Pairs"), + } + } + } + _ => panic!("The object is not a hash"), + } + } + + #[test] + fn test_hash_index_expressions() { + let tests = vec![ + (r#"{"foo": 5}["foo"]"#, Some(5)), + (r#"{"foo": 5}["bar"]"#, None), + (r#"let key = "foo"; {"foo": 5}[key]"#, Some(5)), + (r#"{}["foo"]"#, None), + (r#"{5: 5}[5]"#, Some(5)), + (r#"{true: 5}[true]"#, Some(5)), + (r#"{false: 5}[false]"#, Some(5)), + ]; + + for (input, expected) in tests { + println!("{input}"); + match expected { + Some(x) => test_integer_object(test_eval(input), x), + None => test_null_object(test_eval(input)), + } + } + } + + #[test] + fn test_while_statements() { + let tests = vec![ + ("let a = 0; while (a < 10) { let a = a + 1; }; a", Some(10)), + ( + "let a = 100; while (a < 10) { let a = a + 1; }; a", + Some(100), + ), + ("while (false) { 1 }", None), + ]; + + for (input, expected) in tests { + println!("{input}"); + match expected { + Some(x) => test_integer_object(test_eval(input), x), + None => test_null_object(test_eval(input)), + } + } + } + + fn test_eval(input: &str) -> Object { + let lexer = Lexer::new(input); + let mut parser = Parser::new(lexer); + let program = parser.parse_program(); + let mut evaluator = Evaluator::new(); + evaluator.eval(program) + } + + fn test_integer_object(object: Object, expected: i64) { + match object { + Object::INTEGER(x) => assert_eq!(x, expected), + x => panic!("The object is not an integer, it is {:#?}", x), + } + } + + fn test_boolean_object(object: Object, expected: bool) { + match object { + Object::BOOLEAN(x) => assert_eq!(x, expected), + _ => panic!("The object is not a boolean"), + } + } + + fn test_null_object(object: Object) { + match object { + Object::NULL | Object::ERROR(_) => (), + + _ => panic!("The object is not null"), + } + } + + fn test_error_object(object: Object, expected: String) { + match object { + Object::ERROR(x) => assert_eq!(x, expected), + _ => panic!("The object is not an error"), + } + } + + fn test_string_object(object: Object, expected: String) { + match object { + Object::STRING(s) => assert_eq!(format!("{s}"), expected), + _ => panic!("The object is not an string"), + } + } + + fn test_array_object(object: Object, expected: Vec) { + match object { + Object::ARRAY(x) => { + assert_eq!(x.len(), expected.len()); + for (i, v) in x.iter().enumerate() { + test_integer_object(v.clone(), expected[i]); + } + } + _ => panic!("The object is not an array"), + } + } +} diff --git a/src/interpreter/mod.rs b/src/interpreter/mod.rs index 86a53ae..ce3595d 100644 --- a/src/interpreter/mod.rs +++ b/src/interpreter/mod.rs @@ -1 +1,2 @@ pub mod evaluator; +mod evaluator_tests; diff --git a/src/lexer/mod.rs b/src/lexer/mod.rs index fff119e..f1f5d43 100644 --- a/src/lexer/mod.rs +++ b/src/lexer/mod.rs @@ -110,6 +110,9 @@ impl Lexer { "if" => Token::If, "else" => Token::Else, "return" => Token::Return, + "while" => Token::While, + "break" => Token::Break, + "continue" => Token::Continue, _ => Token::Ident(ident_string), }; } @@ -220,6 +223,13 @@ mod tests { {"foo": "bar"} true && false || true && false; 12 <= 12 && 12 >= 12; + + while (true) { + return false; + } + + break; + continue; "#; let mut lexer = Lexer::new(input); @@ -336,6 +346,21 @@ mod tests { Token::Int(String::from("12")), Token::Semicolon, // + Token::While, + Token::LParen, + Token::True, + Token::RParen, + Token::LSquirly, + Token::Return, + Token::False, + Token::Semicolon, + Token::RSquirly, + // + Token::Break, + Token::Semicolon, + Token::Continue, + Token::Semicolon, + // Token::Eof, ]; diff --git a/src/lexer/token.rs b/src/lexer/token.rs index 95a7641..358c325 100644 --- a/src/lexer/token.rs +++ b/src/lexer/token.rs @@ -48,6 +48,9 @@ pub enum Token { If, Else, Return, + While, + Break, + Continue } impl Display for Token { @@ -86,6 +89,9 @@ impl Display for Token { Token::If => write!(f, "if"), Token::Else => write!(f, "else"), Token::Return => write!(f, "return"), + Token::While => write!(f, "while"), + Token::Break => write!(f, "break"), + Token::Continue => write!(f, "continue") } } } diff --git a/src/parser/ast.rs b/src/parser/ast.rs index 3faa19b..b287945 100644 --- a/src/parser/ast.rs +++ b/src/parser/ast.rs @@ -60,6 +60,7 @@ impl Expression { Token::Function => FunctionLiteral::parse(parser).map(Expression::FunctionLiteral), Token::LSquare => ArrayLiteral::parse(parser).map(Expression::ArrayLiteral), Token::LSquirly => HashMapLiteral::parse(parser).map(Expression::HashMapLiteral), + _ => Err(format!( "There is no prefix parser for the token {}", parser.current_token @@ -297,7 +298,7 @@ impl Display for BlockStatement { } impl BlockStatement { - fn parse(parser: &mut Parser) -> Self { + pub(crate) fn parse(parser: &mut Parser) -> Self { parser.next_token(); let mut statements: Vec = Vec::new(); while !parser.current_token_is(&Token::RSquirly) && !parser.current_token_is(&Token::Eof) { @@ -406,6 +407,8 @@ pub enum Statement { Let(LetStatement), Return(ReturnStatement), Expression(Expression), + While(WhileStatement), + LoopStatements(LoopStatement), } impl Display for Statement { @@ -414,6 +417,8 @@ impl Display for Statement { Statement::Let(statement) => write!(f, "{statement}"), Statement::Return(statement) => write!(f, "{statement}"), Statement::Expression(expression) => write!(f, "{expression}"), + Statement::While(statement) => write!(f, "{statement}"), + Statement::LoopStatements(statement) => write!(f, "{statement}"), } } } @@ -477,6 +482,18 @@ impl Display for ReturnStatement { } } +#[derive(PartialEq, Debug, Clone)] +pub struct WhileStatement { + pub condition: Expression, + pub body: BlockStatement, +} + +impl Display for WhileStatement { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "while {} {{\n{}}}", self.condition, self.body) + } +} + #[derive(PartialEq, Debug, Clone)] pub struct ArrayLiteral { pub elements: Vec, @@ -572,6 +589,34 @@ impl HashMapLiteral { } } +#[derive(PartialEq, Debug, Clone)] +pub enum LoopStatement { + Break, + Continue, +} + +impl Display for LoopStatement { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + LoopStatement::Break => write!(f, "break"), + LoopStatement::Continue => write!(f, "continue"), + } + } +} + +impl LoopStatement { + pub fn parse(parser: &mut Parser) -> Result { + match parser.current_token { + Token::Break => Ok(Self::Break), + Token::Continue => Ok(Self::Continue), + _ => Err(format!( + "Expected a loop statement keyword (break, continue), got {}", + parser.current_token + )), + } + } +} + #[derive(PartialEq, PartialOrd, Clone, Copy)] pub enum Precedence { Lowest = 0, diff --git a/src/parser/mod.rs b/src/parser/mod.rs index d1b92a1..0d781fb 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -1,4 +1,6 @@ pub mod ast; +pub mod parser_errors; +mod parser_tests; use crate::{ lexer::{token::Token, Lexer}, @@ -6,56 +8,11 @@ use crate::{ Expression, Identifier, LetStatement, Precedence, Program, ReturnStatement, Statement, }, }; -use std::{ - error::Error, - fmt::{Display, Formatter}, -}; - -#[allow(clippy::module_name_repetitions)] -#[derive(Debug)] -pub struct ParserErrors { - pub errors: Vec, -} - -impl Error for ParserErrors {} - -impl Default for ParserErrors { - fn default() -> Self { - Self::new() - } -} - -impl Display for ParserErrors { - fn fmt(&self, f: &mut Formatter) -> std::fmt::Result { - writeln!(f, "Parser errors:")?; - for err in &self.errors { - writeln!(f, "\t{err}")?; - } - Ok(()) - } -} - -impl ParserErrors { - pub fn new() -> ParserErrors { - ParserErrors { errors: vec![] } - } - pub fn add_error(&mut self, err: String) { - self.errors.push(err); - } - - pub fn add_errors(&mut self, mut errors: Vec) { - self.errors.append(&mut errors); - } - - pub fn is_empty(&self) -> bool { - self.errors.is_empty() - } - - pub fn len(&self) -> usize { - self.errors.len() - } -} +use self::{ + ast::{BlockStatement, LoopStatement, WhileStatement}, + parser_errors::ParserErrors, +}; pub struct Parser { lexer: Lexer, @@ -104,6 +61,10 @@ impl Parser { match self.current_token { Token::Let => self.parse_let_statement().map(Statement::Let), Token::Return => self.parse_return_statement().map(Statement::Return), + Token::While => self.parse_while_statement().map(Statement::While), + Token::Break | Token::Continue => self + .parse_loop_statement() + .map(Statement::LoopStatements), _ => self.parse_expression_statement().map(Statement::Expression), } } @@ -164,6 +125,32 @@ impl Parser { Some(ReturnStatement { return_value }) } + fn parse_while_statement(&mut self) -> Option { + self.next_token(); + + let condition = match Expression::parse(self, Precedence::Lowest) { + Ok(x) => x, + Err(s) => { + self.push_error(s); + return None; + } + }; + + if !self.expect_peek(&Token::LSquirly) { + return None; + } + + let body = BlockStatement::parse(self); + + Some(WhileStatement { condition, body }) + } + + fn parse_loop_statement(&mut self) -> Option { + let smt = LoopStatement::parse(self).ok(); + self.next_token(); + smt + } + fn parse_expression_statement(&mut self) -> Option { let expression = Expression::parse(self, Precedence::Lowest); if self.peek_token_is(&Token::Semicolon) { @@ -232,685 +219,3 @@ pub fn parse(input: &str) -> Program { let mut parser = Parser::new(lexer); parser.parse_program() } - -#[cfg(test)] -mod tests { - - use crate::{ - lexer::token::Token, - parser::ast::{ - BlockStatement, Expression, Identifier, LetStatement, Primitive, ReturnStatement, - Statement, - }, - }; - - use super::*; - - #[test] - fn test_let_statements() { - let input = r#"let x = 5; - let y = true; - let foobar = y; - "#; - - let program = generate_program(input); - let expected_statemets = vec![ - Statement::Let(LetStatement { - name: Identifier { - token: Token::Ident("x".to_string()), - value: "x".to_string(), - }, - value: Expression::Primitive(Primitive::IntegerLiteral(5)), - }), - Statement::Let(LetStatement { - name: Identifier { - token: Token::Ident("y".to_string()), - value: "y".to_string(), - }, - value: Expression::Primitive(Primitive::BooleanLiteral(true)), - }), - Statement::Let(LetStatement { - name: Identifier { - token: Token::Ident("foobar".to_string()), - value: "foobar".to_string(), - }, - value: Expression::Identifier(Identifier { - token: Token::Ident("y".to_string()), - value: "y".to_string(), - }), - }), - ]; - - assert_eq!(program.statements.len(), expected_statemets.len()); - - for (i, expected) in expected_statemets.iter().enumerate() { - println!("{} | {} | {} ", i, expected, program.statements[i]); - assert_eq!(program.statements[i], *expected); - } - } - - #[test] - fn test_return_statements() { - let input = r#" - return 5; - return true; - return y; - "#; - - let program = generate_program(input); - let expected = vec![ - Statement::Return(ReturnStatement { - return_value: Expression::Primitive(Primitive::IntegerLiteral(5)), - }), - Statement::Return(ReturnStatement { - return_value: Expression::Primitive(Primitive::BooleanLiteral(true)), - }), - Statement::Return(ReturnStatement { - return_value: Expression::Identifier(Identifier { - token: Token::Ident("y".to_string()), - value: "y".to_string(), - }), - }), - ]; - - assert_eq!(program.statements.len(), 3); - - for (i, expected) in expected.iter().enumerate() { - assert_eq!(program.statements[i], *expected); - } - } - - fn check_parse_errors(parser: &Parser) { - let len = parser.errors.len(); - - if len > 0 { - println!("Parser has {} errors", parser.errors.len()); - println!("Parser errors: {:?}", parser.errors); - } - assert_eq!(len, 0); - } - - #[test] - fn test_errors() { - let input = r#" - let x 5; - let = 10; - let 838383; - let x = 838383; - "#; - - let lexer = Lexer::new(input); - let mut parser = Parser::new(lexer); - - parser.parse_program(); - - assert_ne!(parser.errors.len(), 0); - } - - #[test] - fn test_identifier_expression() { - let input = "foobar;"; - let program = generate_program(input); - - assert_eq!(program.statements.len(), 1); - - let statement = &program.statements[0]; - assert_eq!( - statement, - &Statement::Expression(Expression::Identifier(Identifier { - token: Token::Ident("foobar".to_string()), - value: "foobar".to_string(), - })) - ); - } - - #[test] - fn test_integer_literal_expression() { - let input = "5;"; - let program = generate_program(input); - - assert_eq!(program.statements.len(), 1); - - let statement = &program.statements[0]; - assert_eq!( - statement, - &Statement::Expression(Expression::Primitive(Primitive::IntegerLiteral(5))) - ); - } - - #[test] - fn test_parsing_prefix_expressions() { - let tests = vec![ - ("!5", "!", "5"), - ("-15", "-", "15"), - ("!true;", "!", "true"), - ("!false;", "!", "false"), - ]; - - for (input, operator, value) in tests { - let program = generate_program(input); - - assert_eq!(program.statements.len(), 1); - match &program.statements[0] { - Statement::Expression(exp) => check_prefix_expression(exp, operator, value), - _ => panic!("It is not an expression statement"), - }; - } - } - - #[test] - fn test_parsing_infix_expressions() { - let tests = vec![ - ("5 + 5;", "5", "+", "5"), - ("5 - 5;", "5", "-", "5"), - ("5 * 5;", "5", "*", "5"), - ("5 / 5;", "5", "/", "5"), - ("5 > 5;", "5", ">", "5"), - ("5 >= 5;", "5", ">=", "5"), - ("5 < 5;", "5", "<", "5"), - ("5 <= 5;", "5", "<=", "5"), - ("5 == 5;", "5", "==", "5"), - ("5 != 5;", "5", "!=", "5"), - ("true == true", "true", "==", "true"), - ("true != false", "true", "!=", "false"), - ("false == false", "false", "==", "false"), - ("false && true", "false", "&&", "true"), - ("true || false", "true", "||", "false"), - ]; - - for (input, left, operator, right) in tests { - let program = generate_program(input); - - assert_eq!(program.statements.len(), 1); - match &program.statements[0] { - Statement::Expression(exp) => check_infix_expression(exp, left, operator, right), - _ => panic!("It is not an expression statement"), - } - } - } - - #[test] - fn test_operator_precedence_parsing() { - let test = vec![ - ("-a * b", "((-a) * b)"), - ("!-a", "(!(-a))"), - ("a + b + c", "((a + b) + c)"), - ("a + b - c", "((a + b) - c)"), - ("a * b * c", "((a * b) * c)"), - ("a * b / c", "((a * b) / c)"), - ("a + b / c", "(a + (b / c))"), - ("a + b * c + d / e - f", "(((a + (b * c)) + (d / e)) - f)"), - ("3 + 4; -5 * 5", "(3 + 4)\n((-5) * 5)"), - ("5 > 4 == 3 < 4", "((5 > 4) == (3 < 4))"), - ("5 < 4 != 3 > 4", "((5 < 4) != (3 > 4))"), - ( - "3 + 4 * 5 == 3 * 1 + 4 * 5", - "((3 + (4 * 5)) == ((3 * 1) + (4 * 5)))", - ), - ("true", "true"), - ("false", "false"), - ("3 > 5 == false", "((3 > 5) == false)"), - ("3 < 5 == true", "((3 < 5) == true)"), - ("1 + (2 + 3) + 4", "((1 + (2 + 3)) + 4)"), - ("(5 + 5) * 2", "((5 + 5) * 2)"), - ("2 / (5 + 5)", "(2 / (5 + 5))"), - ("-(5 + 5)", "(-(5 + 5))"), - ("!(true == true)", "(!(true == true))"), - ("a + add(b * c) + d", "((a + add((b * c))) + d)"), - ( - "add(a, b, 1, 2 * 3, 4 + 5, add(6, 7 * 8))", - "add(a, b, 1, (2 * 3), (4 + 5), add(6, (7 * 8)))", - ), - ( - "add(a + b + c * d / f + g)", - "add((((a + b) + ((c * d) / f)) + g))", - ), - ( - "a * [1, 2, 3, 4][b * c] * d", - "((a * ([1, 2, 3, 4][(b * c)])) * d)", - ), - ( - "add(a * b[2], b[1], 2 * [1, 2][1])", - "add((a * (b[2])), (b[1]), (2 * ([1, 2][1])))", - ), - ]; - - for (input, expected) in test { - let program = generate_program(input); - print!("{program}"); - assert_ne!(program.statements.len(), 0); - assert_eq!(program.to_string(), format!("{expected}\n")); - } - } - - #[test] - fn test_boolean_expression() { - let tests = vec![("true;", true), ("false;", false)]; - - for (input, expected) in tests { - let program = generate_program(input); - - assert_eq!(program.statements.len(), 1); - match &program.statements[0] { - Statement::Expression(exp) => check_primitive_literal(exp, &expected.to_string()), - _ => panic!("It is not an expression statement"), - } - } - } - - #[test] - fn test_if_statement() { - let (input, condition, consequence, alternative) = ("if (x < y) { x }", "x < y", "x", None); - let program = generate_program(input); - - assert_eq!(program.statements.len(), 1); - match &program.statements[0] { - Statement::Expression(exp) => { - check_conditional_expression(exp, condition, consequence, alternative); - } - _ => panic!("It is not an expression statement"), - } - } - - #[test] - fn test_if_else_statement() { - let (input, condition, consequence, alternative) = - ("if (x < y) { x } else {y}", "x < y", "x", Some("y")); - let lexer = Lexer::new(input); - let mut parser = Parser::new(lexer); - let program = parser.parse_program(); - - check_parse_errors(&parser); - - assert_eq!(program.statements.len(), 1); - match &program.statements[0] { - Statement::Expression(exp) => { - check_conditional_expression(exp, condition, consequence, alternative); - } - _ => panic!("It is not an expression statement"), - } - } - - #[test] - fn test_function_literal_parsing() { - let input = "fn(x, y) { x + y; }"; - let program = generate_program(input); - - assert_eq!(program.statements.len(), 1); - match &program.statements[0] { - Statement::Expression(exp) => check_function_literal(exp, vec!["x", "y"], "(x + y)"), - _ => panic!("It is not an expression statement"), - } - } - - #[test] - fn test_parse_funtion_arguments() { - let tests = vec![ - ("fn() {}", Vec::new()), - ("fn(x) {}", vec!["x"]), - ("fn(x,y,z) {}", vec!["x", "y", "z"]), - ]; - - for (input, expected) in tests { - let program = generate_program(input); - - assert_eq!(program.statements.len(), 1); - match &program.statements[0] { - Statement::Expression(exp) => check_function_literal(exp, expected, ""), - _ => panic!("It is not an expression statement"), - } - } - } - - #[test] - fn test_function_call_parsing() { - let (input, name, argumnets) = ( - "add(1, 2 * 3, 4 + 5);", - "add", - vec!["1", "(2 * 3)", "(4 + 5)"], - ); - - let program = generate_program(input); - - assert_eq!(program.statements.len(), 1); - match &program.statements[0] { - Statement::Expression(exp) => check_function_call(exp, name, argumnets), - _ => panic!("It is not an expression statement"), - } - } - - #[test] - fn test_function_call_parameter_parsing() { - let tests = vec![ - ("add();", "add", vec![]), - ("add(1);", "add", vec!["1"]), - ( - "add(1, 2 * 3, 4 + 5);", - "add", - vec!["1", "(2 * 3)", "(4 + 5)"], - ), - ]; - - for (input, name, argumnets) in tests { - let program = generate_program(input); - - assert_eq!(program.statements.len(), 1); - match &program.statements[0] { - Statement::Expression(exp) => check_function_call(exp, name, argumnets), - _ => panic!("It is not an expression statement"), - } - } - } - - #[test] - fn test_string_literal_expression() { - let input = "\"hello world\";"; - - let program = generate_program(input); - - assert_eq!(program.statements.len(), 1); - match &program.statements[0] { - Statement::Expression(exp) => check_primitive_literal(exp, "hello world"), - _ => panic!("It is not an expression statement"), - } - } - - #[test] - fn test_array_literal() { - let input = "[1,2*2,3+3]"; - - let program = generate_program(input); - - assert_eq!(program.statements.len(), 1); - let expressions = match &program.statements[0] { - Statement::Expression(exp) => match exp { - Expression::ArrayLiteral(a) => &a.elements, - _ => panic!("It is not an array literal"), - }, - _ => panic!("It is not an expression statement"), - }; - - assert_eq!(expressions.len(), 3); - check_primitive_literal(&expressions[0], "1"); - check_infix_expression(&expressions[1], "2", "*", "2"); - check_infix_expression(&expressions[2], "3", "+", "3"); - } - - #[test] - fn test_parsing_index_expression_complete() { - let input = "myArray[1+1]"; - - let program = generate_program(input); - - assert_eq!(program.statements.len(), 1); - match &program.statements[0] { - Statement::Expression(exp) => match exp { - Expression::IndexExpression(i) => { - assert_eq!(i.left.to_string(), "myArray"); - check_infix_expression(&i.index, "1", "+", "1"); - } - _ => panic!("It is not an index expression"), - }, - _ => panic!("It is not an expression statement"), - } - } - - #[test] - fn test_parsing_index_expression_string_conversion() { - let tests = vec![ - ("myArray[1]", "myArray", "1"), - ("myArray[\"hello\"]", "myArray", "\"hello\""), - ("[1,2,3,4][2]", "[1, 2, 3, 4]", "2"), - ("test()[call()]", "test()", "call()"), - ]; - - for (input, left, index) in tests { - let program = generate_program(input); - - assert_eq!(program.statements.len(), 1); - match &program.statements[0] { - Statement::Expression(exp) => check_index_expression(exp, left, index), - - _ => panic!("It is not an expression statement"), - } - } - } - - #[test] - fn test_parsing_hash_map_literal_string_keys() { - let input = "{\"one\": 1, \"two\": 2, \"three\": 3}"; - - let program = generate_program(input); - - assert_eq!(program.statements.len(), 1); - match &program.statements[0] { - Statement::Expression(exp) => match exp { - Expression::HashMapLiteral(h) => { - assert_eq!(h.pairs.len(), 3); - let expected = vec![("one", "1"), ("two", "2"), ("three", "3")]; - for (i, (key, value)) in expected.iter().enumerate() { - let pair = h.pairs.get(i).unwrap(); - check_primitive_literal(&pair.0, key); - check_primitive_literal(&pair.1, value); - } - } - _ => panic!("It is not an hash literal"), - }, - _ => panic!("It is not an expression statement"), - } - } - - #[test] - fn test_parsing_empty_hash_map() { - let input = "{}"; - - let program = generate_program(input); - assert_eq!(program.statements.len(), 1); - match &program.statements[0] { - Statement::Expression(exp) => match exp { - Expression::HashMapLiteral(h) => { - assert_eq!(h.pairs.len(), 0); - } - _ => panic!("It is not an hash literal"), - }, - _ => panic!("It is not an expression statement"), - } - } - - #[test] - fn test_parsing_hash_map_literal_integer_values() { - let input = "{\"one\": 1 + 34, \"two\": 2/5, \"three\": 3-1}"; - - let program = generate_program(input); - - assert_eq!(program.statements.len(), 1); - match &program.statements[0] { - Statement::Expression(exp) => match exp { - Expression::HashMapLiteral(h) => { - assert_eq!(h.pairs.len(), 3); - let expected = vec![ - ("\"one\"", "(1 + 34)"), - ("\"two\"", "(2 / 5)"), - ("\"three\"", "(3 - 1)"), - ]; - for (i, (key, value)) in expected.iter().enumerate() { - let pair = h.pairs.get(i).unwrap(); - assert_eq!(pair.0.to_string(), **key); - assert_eq!(pair.1.to_string(), **value); - } - } - _ => panic!("It is not an hash literal"), - }, - _ => panic!("It is not an expression statement"), - } - } - - #[test] - fn test_parsing_hash_map_literal_mixed_keys() { - let input = "{1:true, 2: \"Hi\", \"three\": 3-1}"; - - let program = generate_program(input); - - assert_eq!(program.statements.len(), 1); - match &program.statements[0] { - Statement::Expression(exp) => match exp { - Expression::HashMapLiteral(h) => { - assert_eq!(h.pairs.len(), 3); - let expected = vec![("1", "true"), ("2", "\"Hi\""), ("\"three\"", "(3 - 1)")]; - for (i, (key, value)) in expected.iter().enumerate() { - let pair = h.pairs.get(i).unwrap(); - assert_eq!(pair.0.to_string(), **key); - assert_eq!(pair.1.to_string(), **value); - } - } - _ => panic!("It is not an hash literal"), - }, - _ => panic!("It is not an expression statement"), - } - } - - #[test] - fn test_parsing_function_literal_with_name() { - let input = "let myFunction = fn(){};"; - - let program = generate_program(input); - - assert_eq!(program.statements.len(), 1); - match program.statements[0].clone() { - Statement::Let(l) => match l.value { - Expression::FunctionLiteral(f) => { - assert_eq!(f.name, Some("myFunction".to_string())); - } - _ => panic!("It is not a function literal"), - }, - _ => panic!("It is not a let statement"), - } - } - - #[test] - fn test_parsing_function_literal_without_name() { - let input = "fn(){};"; - - let program = generate_program(input); - - assert_eq!(program.statements.len(), 1); - match program.statements[0].clone() { - Statement::Expression(exp) => match exp { - Expression::FunctionLiteral(f) => { - assert!(f.name.is_none()); - } - _ => panic!("It is not a function literal"), - }, - _ => panic!("It is not an expression"), - } - } - - fn generate_program(input: &str) -> Program { - let lexer = Lexer::new(input); - let mut parser = Parser::new(lexer); - let program = parser.parse_program(); - - check_parse_errors(&parser); - program - } - - fn check_identifier(exp: &Identifier, value: &str) { - assert_eq!(exp.value, value); - } - - fn check_prefix_expression(exp: &Expression, operator: &str, right: &str) { - match exp { - Expression::Prefix(p) => { - assert_eq!(p.token.to_string(), operator); - assert_eq!(p.right.to_string(), right); - } - _ => panic!("It is not an prefix operator"), - } - } - - fn check_primitive_literal(exp: &Expression, value: &str) { - match exp { - Expression::Primitive(p) => match p { - Primitive::IntegerLiteral(i) => assert_eq!(i.to_string(), value), - Primitive::BooleanLiteral(b) => assert_eq!(b.to_string(), value), - Primitive::StringLiteral(s) => assert_eq!(s, value), - }, - _ => panic!("It is not a literal"), - } - } - - fn check_infix_expression(exp: &Expression, left: &str, operator: &str, right: &str) { - match exp { - Expression::Infix(p) => { - check_primitive_literal(p.left.as_ref(), left); - assert_eq!(operator, p.token.to_string()); - check_primitive_literal(p.right.as_ref(), right); - } - _ => panic!("It is not an infix expression"), - } - } - - fn check_conditional_expression( - exp: &Expression, - condition: &str, - consequence: &str, - alternative: Option<&str>, - ) { - match exp { - Expression::Conditional(p) => { - assert_eq!(format!("({condition})"), p.condition.as_ref().to_string()); - check_block_statement(&p.consequence, consequence); - match alternative { - Some(a) => check_block_statement(p.alternative.as_ref().unwrap(), a), - None => assert!(p.alternative.is_none()), - } - } - _ => panic!("It is not a conditional expression"), - } - } - - fn check_block_statement(statement: &BlockStatement, expected: &str) { - if expected.is_empty() { - assert_eq!(statement.to_string(), ""); // Empty block statement does not contain a - // newline - } else { - assert_eq!(statement.to_string(), format!("{expected}\n")); - } - } - - fn check_function_literal(exp: &Expression, params: Vec<&str>, body: &str) { - match exp { - Expression::FunctionLiteral(p) => { - assert_eq!(p.parameters.len(), params.len()); - for (i, param) in params.iter().enumerate() { - check_identifier(&p.parameters[i], param); - } - check_block_statement(&p.body, body); - } - _ => panic!("It is not a function literal"), - } - } - - fn check_function_call(exp: &Expression, name: &str, arguments: Vec<&str>) { - match exp { - Expression::FunctionCall(p) => { - assert_eq!(p.function.to_string(), name); - assert_eq!(p.arguments.len(), arguments.len()); - for (i, arg) in arguments.iter().enumerate() { - assert_eq!(p.arguments[i].to_string(), arg.to_owned().to_string()); - } - } - _ => panic!("It is not a function call"), - } - } - - fn check_index_expression(exp: &Expression, left: &str, index: &str) { - match exp { - Expression::IndexExpression(p) => { - assert_eq!(p.left.to_string(), left); - assert_eq!(p.index.to_string(), index); - } - _ => panic!("It is not an index expression"), - } - } -} diff --git a/src/parser/parser_errors.rs b/src/parser/parser_errors.rs new file mode 100644 index 0000000..53741ce --- /dev/null +++ b/src/parser/parser_errors.rs @@ -0,0 +1,50 @@ +use std::{ + error::Error, + fmt::{Display, Formatter}, +}; + +#[allow(clippy::module_name_repetitions)] +#[derive(Debug)] +pub struct ParserErrors { + pub errors: Vec, +} + +impl Error for ParserErrors {} + +impl Default for ParserErrors { + fn default() -> Self { + Self::new() + } +} + +impl Display for ParserErrors { + fn fmt(&self, f: &mut Formatter) -> std::fmt::Result { + writeln!(f, "Parser errors:")?; + for err in &self.errors { + writeln!(f, "\t{err}")?; + } + Ok(()) + } +} + +impl ParserErrors { + pub fn new() -> ParserErrors { + ParserErrors { errors: vec![] } + } + + pub fn add_error(&mut self, err: String) { + self.errors.push(err); + } + + pub fn add_errors(&mut self, mut errors: Vec) { + self.errors.append(&mut errors); + } + + pub fn is_empty(&self) -> bool { + self.errors.is_empty() + } + + pub fn len(&self) -> usize { + self.errors.len() + } +} diff --git a/src/parser/parser_tests.rs b/src/parser/parser_tests.rs new file mode 100644 index 0000000..8485c65 --- /dev/null +++ b/src/parser/parser_tests.rs @@ -0,0 +1,798 @@ +#[cfg(test)] +mod tests { + + use crate::{ + lexer::{token::Token, Lexer}, + parser::{ + ast::{ + BlockStatement, Conditional, LoopStatement, Expression, FunctionCall, Identifier, + InfixOperator, LetStatement, Primitive, Program, ReturnStatement, Statement, + WhileStatement, + }, + Parser, + }, + }; + + #[test] + fn test_let_statements() { + let input = r#"let x = 5; + let y = true; + let foobar = y; + "#; + + let program = generate_program(input); + let expected_statemets = vec![ + Statement::Let(LetStatement { + name: Identifier { + token: Token::Ident("x".to_string()), + value: "x".to_string(), + }, + value: Expression::Primitive(Primitive::IntegerLiteral(5)), + }), + Statement::Let(LetStatement { + name: Identifier { + token: Token::Ident("y".to_string()), + value: "y".to_string(), + }, + value: Expression::Primitive(Primitive::BooleanLiteral(true)), + }), + Statement::Let(LetStatement { + name: Identifier { + token: Token::Ident("foobar".to_string()), + value: "foobar".to_string(), + }, + value: Expression::Identifier(Identifier { + token: Token::Ident("y".to_string()), + value: "y".to_string(), + }), + }), + ]; + + assert_eq!(program.statements.len(), expected_statemets.len()); + + for (i, expected) in expected_statemets.iter().enumerate() { + println!("{} | {} | {} ", i, expected, program.statements[i]); + assert_eq!(program.statements[i], *expected); + } + } + + #[test] + fn test_return_statements() { + let input = r#" + return 5; + return true; + return y; + "#; + + let program = generate_program(input); + let expected = vec![ + Statement::Return(ReturnStatement { + return_value: Expression::Primitive(Primitive::IntegerLiteral(5)), + }), + Statement::Return(ReturnStatement { + return_value: Expression::Primitive(Primitive::BooleanLiteral(true)), + }), + Statement::Return(ReturnStatement { + return_value: Expression::Identifier(Identifier { + token: Token::Ident("y".to_string()), + value: "y".to_string(), + }), + }), + ]; + + assert_eq!(program.statements.len(), 3); + + for (i, expected) in expected.iter().enumerate() { + assert_eq!(program.statements[i], *expected); + } + } + + fn check_parse_errors(parser: &Parser) { + let len = parser.errors.len(); + + if len > 0 { + println!("Parser has {} errors", parser.errors.len()); + println!("Parser errors: {:?}", parser.errors); + } + assert_eq!(len, 0); + } + + #[test] + fn test_errors() { + let input = r#" + let x 5; + let = 10; + let 838383; + let x = 838383; + "#; + + let lexer = Lexer::new(input); + let mut parser = Parser::new(lexer); + + parser.parse_program(); + + assert_ne!(parser.errors.len(), 0); + } + + #[test] + fn test_identifier_expression() { + let input = "foobar;"; + let program = generate_program(input); + + assert_eq!(program.statements.len(), 1); + + let statement = &program.statements[0]; + assert_eq!( + statement, + &Statement::Expression(Expression::Identifier(Identifier { + token: Token::Ident("foobar".to_string()), + value: "foobar".to_string(), + })) + ); + } + + #[test] + fn test_integer_literal_expression() { + let input = "5;"; + let program = generate_program(input); + + assert_eq!(program.statements.len(), 1); + + let statement = &program.statements[0]; + assert_eq!( + statement, + &Statement::Expression(Expression::Primitive(Primitive::IntegerLiteral(5))) + ); + } + + #[test] + fn test_parsing_prefix_expressions() { + let tests = vec![ + ("!5", "!", "5"), + ("-15", "-", "15"), + ("!true;", "!", "true"), + ("!false;", "!", "false"), + ]; + + for (input, operator, value) in tests { + let program = generate_program(input); + + assert_eq!(program.statements.len(), 1); + match &program.statements[0] { + Statement::Expression(exp) => check_prefix_expression(exp, operator, value), + _ => panic!("It is not an expression statement"), + }; + } + } + + #[test] + fn test_parsing_infix_expressions() { + let tests = vec![ + ("5 + 5;", "5", "+", "5"), + ("5 - 5;", "5", "-", "5"), + ("5 * 5;", "5", "*", "5"), + ("5 / 5;", "5", "/", "5"), + ("5 > 5;", "5", ">", "5"), + ("5 >= 5;", "5", ">=", "5"), + ("5 < 5;", "5", "<", "5"), + ("5 <= 5;", "5", "<=", "5"), + ("5 == 5;", "5", "==", "5"), + ("5 != 5;", "5", "!=", "5"), + ("true == true", "true", "==", "true"), + ("true != false", "true", "!=", "false"), + ("false == false", "false", "==", "false"), + ("false && true", "false", "&&", "true"), + ("true || false", "true", "||", "false"), + ]; + + for (input, left, operator, right) in tests { + let program = generate_program(input); + + assert_eq!(program.statements.len(), 1); + match &program.statements[0] { + Statement::Expression(exp) => check_infix_expression(exp, left, operator, right), + _ => panic!("It is not an expression statement"), + } + } + } + + #[test] + fn test_operator_precedence_parsing() { + let test = vec![ + ("-a * b", "((-a) * b)"), + ("!-a", "(!(-a))"), + ("a + b + c", "((a + b) + c)"), + ("a + b - c", "((a + b) - c)"), + ("a * b * c", "((a * b) * c)"), + ("a * b / c", "((a * b) / c)"), + ("a + b / c", "(a + (b / c))"), + ("a + b * c + d / e - f", "(((a + (b * c)) + (d / e)) - f)"), + ("3 + 4; -5 * 5", "(3 + 4)\n((-5) * 5)"), + ("5 > 4 == 3 < 4", "((5 > 4) == (3 < 4))"), + ("5 < 4 != 3 > 4", "((5 < 4) != (3 > 4))"), + ( + "3 + 4 * 5 == 3 * 1 + 4 * 5", + "((3 + (4 * 5)) == ((3 * 1) + (4 * 5)))", + ), + ("true", "true"), + ("false", "false"), + ("3 > 5 == false", "((3 > 5) == false)"), + ("3 < 5 == true", "((3 < 5) == true)"), + ("1 + (2 + 3) + 4", "((1 + (2 + 3)) + 4)"), + ("(5 + 5) * 2", "((5 + 5) * 2)"), + ("2 / (5 + 5)", "(2 / (5 + 5))"), + ("-(5 + 5)", "(-(5 + 5))"), + ("!(true == true)", "(!(true == true))"), + ("a + add(b * c) + d", "((a + add((b * c))) + d)"), + ( + "add(a, b, 1, 2 * 3, 4 + 5, add(6, 7 * 8))", + "add(a, b, 1, (2 * 3), (4 + 5), add(6, (7 * 8)))", + ), + ( + "add(a + b + c * d / f + g)", + "add((((a + b) + ((c * d) / f)) + g))", + ), + ( + "a * [1, 2, 3, 4][b * c] * d", + "((a * ([1, 2, 3, 4][(b * c)])) * d)", + ), + ( + "add(a * b[2], b[1], 2 * [1, 2][1])", + "add((a * (b[2])), (b[1]), (2 * ([1, 2][1])))", + ), + ]; + + for (input, expected) in test { + let program = generate_program(input); + print!("{program}"); + assert_ne!(program.statements.len(), 0); + assert_eq!(program.to_string(), format!("{expected}\n")); + } + } + + #[test] + fn test_boolean_expression() { + let tests = vec![("true;", true), ("false;", false)]; + + for (input, expected) in tests { + let program = generate_program(input); + + assert_eq!(program.statements.len(), 1); + match &program.statements[0] { + Statement::Expression(exp) => check_primitive_literal(exp, &expected.to_string()), + _ => panic!("It is not an expression statement"), + } + } + } + + #[test] + fn test_if_statement() { + let (input, condition, consequence, alternative) = ("if (x < y) { x }", "x < y", "x", None); + let program = generate_program(input); + + assert_eq!(program.statements.len(), 1); + match &program.statements[0] { + Statement::Expression(exp) => { + check_conditional_expression(exp, condition, consequence, alternative); + } + _ => panic!("It is not an expression statement"), + } + } + + #[test] + fn test_if_else_statement() { + let (input, condition, consequence, alternative) = + ("if (x < y) { x } else {y}", "x < y", "x", Some("y")); + let lexer = Lexer::new(input); + let mut parser = Parser::new(lexer); + let program = parser.parse_program(); + + check_parse_errors(&parser); + + assert_eq!(program.statements.len(), 1); + match &program.statements[0] { + Statement::Expression(exp) => { + check_conditional_expression(exp, condition, consequence, alternative); + } + _ => panic!("It is not an expression statement"), + } + } + + #[test] + fn test_function_literal_parsing() { + let input = "fn(x, y) { x + y; }"; + let program = generate_program(input); + + assert_eq!(program.statements.len(), 1); + match &program.statements[0] { + Statement::Expression(exp) => check_function_literal(exp, vec!["x", "y"], "(x + y)"), + _ => panic!("It is not an expression statement"), + } + } + + #[test] + fn test_parse_funtion_arguments() { + let tests = vec![ + ("fn() {}", Vec::new()), + ("fn(x) {}", vec!["x"]), + ("fn(x,y,z) {}", vec!["x", "y", "z"]), + ]; + + for (input, expected) in tests { + let program = generate_program(input); + + assert_eq!(program.statements.len(), 1); + match &program.statements[0] { + Statement::Expression(exp) => check_function_literal(exp, expected, ""), + _ => panic!("It is not an expression statement"), + } + } + } + + #[test] + fn test_function_call_parsing() { + let (input, name, argumnets) = ( + "add(1, 2 * 3, 4 + 5);", + "add", + vec!["1", "(2 * 3)", "(4 + 5)"], + ); + + let program = generate_program(input); + + assert_eq!(program.statements.len(), 1); + match &program.statements[0] { + Statement::Expression(exp) => check_function_call(exp, name, argumnets), + _ => panic!("It is not an expression statement"), + } + } + + #[test] + fn test_function_call_parameter_parsing() { + let tests = vec![ + ("add();", "add", vec![]), + ("add(1);", "add", vec!["1"]), + ( + "add(1, 2 * 3, 4 + 5);", + "add", + vec!["1", "(2 * 3)", "(4 + 5)"], + ), + ]; + + for (input, name, argumnets) in tests { + let program = generate_program(input); + + assert_eq!(program.statements.len(), 1); + match &program.statements[0] { + Statement::Expression(exp) => check_function_call(exp, name, argumnets), + _ => panic!("It is not an expression statement"), + } + } + } + + #[test] + fn test_string_literal_expression() { + let input = "\"hello world\";"; + + let program = generate_program(input); + + assert_eq!(program.statements.len(), 1); + match &program.statements[0] { + Statement::Expression(exp) => check_primitive_literal(exp, "hello world"), + _ => panic!("It is not an expression statement"), + } + } + + #[test] + fn test_array_literal() { + let input = "[1,2*2,3+3]"; + + let program = generate_program(input); + + assert_eq!(program.statements.len(), 1); + let expressions = match &program.statements[0] { + Statement::Expression(exp) => match exp { + Expression::ArrayLiteral(a) => &a.elements, + _ => panic!("It is not an array literal"), + }, + _ => panic!("It is not an expression statement"), + }; + + assert_eq!(expressions.len(), 3); + check_primitive_literal(&expressions[0], "1"); + check_infix_expression(&expressions[1], "2", "*", "2"); + check_infix_expression(&expressions[2], "3", "+", "3"); + } + + #[test] + fn test_parsing_index_expression_complete() { + let input = "myArray[1+1]"; + + let program = generate_program(input); + + assert_eq!(program.statements.len(), 1); + match &program.statements[0] { + Statement::Expression(exp) => match exp { + Expression::IndexExpression(i) => { + assert_eq!(i.left.to_string(), "myArray"); + check_infix_expression(&i.index, "1", "+", "1"); + } + _ => panic!("It is not an index expression"), + }, + _ => panic!("It is not an expression statement"), + } + } + + #[test] + fn test_parsing_index_expression_string_conversion() { + let tests = vec![ + ("myArray[1]", "myArray", "1"), + ("myArray[\"hello\"]", "myArray", "\"hello\""), + ("[1,2,3,4][2]", "[1, 2, 3, 4]", "2"), + ("test()[call()]", "test()", "call()"), + ]; + + for (input, left, index) in tests { + let program = generate_program(input); + + assert_eq!(program.statements.len(), 1); + match &program.statements[0] { + Statement::Expression(exp) => check_index_expression(exp, left, index), + + _ => panic!("It is not an expression statement"), + } + } + } + + #[test] + fn test_parsing_hash_map_literal_string_keys() { + let input = "{\"one\": 1, \"two\": 2, \"three\": 3}"; + + let program = generate_program(input); + + assert_eq!(program.statements.len(), 1); + match &program.statements[0] { + Statement::Expression(exp) => match exp { + Expression::HashMapLiteral(h) => { + assert_eq!(h.pairs.len(), 3); + let expected = vec![("one", "1"), ("two", "2"), ("three", "3")]; + for (i, (key, value)) in expected.iter().enumerate() { + let pair = h.pairs.get(i).unwrap(); + check_primitive_literal(&pair.0, key); + check_primitive_literal(&pair.1, value); + } + } + _ => panic!("It is not an hash literal"), + }, + _ => panic!("It is not an expression statement"), + } + } + + #[test] + fn test_parsing_empty_hash_map() { + let input = "{}"; + + let program = generate_program(input); + assert_eq!(program.statements.len(), 1); + match &program.statements[0] { + Statement::Expression(exp) => match exp { + Expression::HashMapLiteral(h) => { + assert_eq!(h.pairs.len(), 0); + } + _ => panic!("It is not an hash literal"), + }, + _ => panic!("It is not an expression statement"), + } + } + + #[test] + fn test_parsing_hash_map_literal_integer_values() { + let input = "{\"one\": 1 + 34, \"two\": 2/5, \"three\": 3-1}"; + + let program = generate_program(input); + + assert_eq!(program.statements.len(), 1); + match &program.statements[0] { + Statement::Expression(exp) => match exp { + Expression::HashMapLiteral(h) => { + assert_eq!(h.pairs.len(), 3); + let expected = vec![ + ("\"one\"", "(1 + 34)"), + ("\"two\"", "(2 / 5)"), + ("\"three\"", "(3 - 1)"), + ]; + for (i, (key, value)) in expected.iter().enumerate() { + let pair = h.pairs.get(i).unwrap(); + assert_eq!(pair.0.to_string(), **key); + assert_eq!(pair.1.to_string(), **value); + } + } + _ => panic!("It is not an hash literal"), + }, + _ => panic!("It is not an expression statement"), + } + } + + #[test] + fn test_parsing_hash_map_literal_mixed_keys() { + let input = "{1:true, 2: \"Hi\", \"three\": 3-1}"; + + let program = generate_program(input); + + assert_eq!(program.statements.len(), 1); + match &program.statements[0] { + Statement::Expression(exp) => match exp { + Expression::HashMapLiteral(h) => { + assert_eq!(h.pairs.len(), 3); + let expected = vec![("1", "true"), ("2", "\"Hi\""), ("\"three\"", "(3 - 1)")]; + for (i, (key, value)) in expected.iter().enumerate() { + let pair = h.pairs.get(i).unwrap(); + assert_eq!(pair.0.to_string(), **key); + assert_eq!(pair.1.to_string(), **value); + } + } + _ => panic!("It is not an hash literal"), + }, + _ => panic!("It is not an expression statement"), + } + } + + #[test] + fn test_parsing_function_literal_with_name() { + let input = "let myFunction = fn(){};"; + + let program = generate_program(input); + + assert_eq!(program.statements.len(), 1); + match program.statements[0].clone() { + Statement::Let(l) => match l.value { + Expression::FunctionLiteral(f) => { + assert_eq!(f.name, Some("myFunction".to_string())); + } + _ => panic!("It is not a function literal"), + }, + _ => panic!("It is not a let statement"), + } + } + + #[test] + fn test_parsing_function_literal_without_name() { + let input = "fn(){};"; + + let program = generate_program(input); + + assert_eq!(program.statements.len(), 1); + match program.statements[0].clone() { + Statement::Expression(exp) => match exp { + Expression::FunctionLiteral(f) => { + assert!(f.name.is_none()); + } + _ => panic!("It is not a function literal"), + }, + _ => panic!("It is not an expression"), + } + } + + #[test] + fn test_parsing_while_statements() { + let input = "while(x < 3){ + let x = x + 3; + puts(x); + }"; + + let expected = WhileStatement { + condition: Expression::Infix(InfixOperator { + token: Token::LT, + left: Box::new(Expression::Identifier(Identifier { + token: Token::Ident("x".to_string()), + value: "x".to_string(), + })), + right: Box::new(Expression::Primitive(Primitive::IntegerLiteral(3))), + }), + body: BlockStatement { + statements: vec![ + Statement::Let(LetStatement { + name: Identifier { + token: Token::Ident("x".to_string()), + value: "x".to_string(), + }, + value: Expression::Infix(InfixOperator { + token: Token::Plus, + left: Box::new(Expression::Identifier(Identifier { + token: Token::Ident("x".to_string()), + value: "x".to_string(), + })), + right: Box::new(Expression::Primitive(Primitive::IntegerLiteral(3))), + }), + }), + Statement::Expression(Expression::FunctionCall(FunctionCall { + function: Box::new(Expression::Identifier(Identifier { + token: Token::Ident("puts".to_string()), + value: "puts".to_string(), + })), + arguments: vec![Expression::Identifier(Identifier { + token: Token::Ident("x".to_string()), + value: "x".to_string(), + })], + })), + ], + }, + }; + + println!("Input:\n{input}"); + let program = generate_program(input); + println!("Parsed:\n{program}"); + + assert_eq!(program.statements.len(), 1); + + match program.statements[0].clone() { + Statement::While(smt) => { + assert_eq!(smt, expected); + } + _ => panic!("It is not an expression"), + } + } + + #[test] + fn test_parse_while_loop_statements() { + let input = "while(x < 3){ + if (x == 2){ + break; + } else { + continue; + } + }"; + + let expected = WhileStatement { + condition: Expression::Infix(InfixOperator { + token: Token::LT, + left: Box::new(Expression::Identifier(Identifier { + token: Token::Ident("x".to_string()), + value: "x".to_string(), + })), + right: Box::new(Expression::Primitive(Primitive::IntegerLiteral(3))), + }), + body: BlockStatement { + statements: vec![Statement::Expression(Expression::Conditional( + Conditional { + condition: Box::new(Expression::Infix(InfixOperator { + token: Token::Equal, + left: Box::new(Expression::Identifier(Identifier { + token: Token::Ident("x".to_string()), + value: "x".to_string(), + })), + right: Box::new(Expression::Primitive(Primitive::IntegerLiteral(2))), + })), + consequence: BlockStatement { + statements: vec![Statement::LoopStatements(LoopStatement::Break)], + }, + alternative: Some(BlockStatement { + statements: vec![Statement::LoopStatements(LoopStatement::Continue)], + }), + }, + ))], + }, + }; + + println!("Input:\n{input}"); + let program = generate_program(input); + println!("Parsed:\n{program}"); + + assert_eq!(program.statements.len(), 1); + + match program.statements[0].clone() { + Statement::While(smt) => { + assert_eq!(smt, expected); + } + _ => panic!("It is not an expression"), + } + } + + fn generate_program(input: &str) -> Program { + let lexer = Lexer::new(input); + let mut parser = Parser::new(lexer); + let program = parser.parse_program(); + + check_parse_errors(&parser); + program + } + + fn check_identifier(exp: &Identifier, value: &str) { + assert_eq!(exp.value, value); + } + + fn check_prefix_expression(exp: &Expression, operator: &str, right: &str) { + match exp { + Expression::Prefix(p) => { + assert_eq!(p.token.to_string(), operator); + assert_eq!(p.right.to_string(), right); + } + _ => panic!("It is not an prefix operator"), + } + } + + fn check_primitive_literal(exp: &Expression, value: &str) { + match exp { + Expression::Primitive(p) => match p { + Primitive::IntegerLiteral(i) => assert_eq!(i.to_string(), value), + Primitive::BooleanLiteral(b) => assert_eq!(b.to_string(), value), + Primitive::StringLiteral(s) => assert_eq!(s, value), + }, + _ => panic!("It is not a literal"), + } + } + + fn check_infix_expression(exp: &Expression, left: &str, operator: &str, right: &str) { + match exp { + Expression::Infix(p) => { + check_primitive_literal(p.left.as_ref(), left); + assert_eq!(operator, p.token.to_string()); + check_primitive_literal(p.right.as_ref(), right); + } + _ => panic!("It is not an infix expression"), + } + } + + fn check_conditional_expression( + exp: &Expression, + condition: &str, + consequence: &str, + alternative: Option<&str>, + ) { + match exp { + Expression::Conditional(p) => { + assert_eq!(format!("({condition})"), p.condition.as_ref().to_string()); + check_block_statement(&p.consequence, consequence); + match alternative { + Some(a) => check_block_statement(p.alternative.as_ref().unwrap(), a), + None => assert!(p.alternative.is_none()), + } + } + _ => panic!("It is not a conditional expression"), + } + } + + fn check_block_statement(statement: &BlockStatement, expected: &str) { + if expected.is_empty() { + assert_eq!(statement.to_string(), ""); // Empty block statement does not contain a + // newline + } else { + assert_eq!(statement.to_string(), format!("{expected}\n")); + } + } + + fn check_function_literal(exp: &Expression, params: Vec<&str>, body: &str) { + match exp { + Expression::FunctionLiteral(p) => { + assert_eq!(p.parameters.len(), params.len()); + for (i, param) in params.iter().enumerate() { + check_identifier(&p.parameters[i], param); + } + check_block_statement(&p.body, body); + } + _ => panic!("It is not a function literal"), + } + } + + fn check_function_call(exp: &Expression, name: &str, arguments: Vec<&str>) { + match exp { + Expression::FunctionCall(p) => { + assert_eq!(p.function.to_string(), name); + assert_eq!(p.arguments.len(), arguments.len()); + for (i, arg) in arguments.iter().enumerate() { + assert_eq!(p.arguments[i].to_string(), arg.to_owned().to_string()); + } + } + _ => panic!("It is not a function call"), + } + } + + fn check_index_expression(exp: &Expression, left: &str, index: &str) { + match exp { + Expression::IndexExpression(p) => { + assert_eq!(p.left.to_string(), left); + assert_eq!(p.index.to_string(), index); + } + _ => panic!("It is not an index expression"), + } + } +} diff --git a/src/repl/mod.rs b/src/repl/mod.rs index d3ce150..ad606b5 100644 --- a/src/repl/mod.rs +++ b/src/repl/mod.rs @@ -11,7 +11,7 @@ use crate::{ builtins::BuiltinFunction, {Object, NULL}, }, - parser::{Parser, ParserErrors}, + parser::{parser_errors::ParserErrors, Parser}, repl::errors::{CompilerError, LexerErrors, RuntimeError}, vm::{GLOBALS_SIZE, VM}, }; diff --git a/src/vm/vm_tests.rs b/src/vm/vm_tests.rs index 917ff4c..cefc1b0 100644 --- a/src/vm/vm_tests.rs +++ b/src/vm/vm_tests.rs @@ -1132,12 +1132,13 @@ mod tests { input: r#" let f = fn(a){ let a = 1; + let a = a + 1; a }; f(1) "# .to_string(), - expected: Object::INTEGER(1), + expected: Object::INTEGER(2), }, VmTestCase { input: r#" @@ -1179,6 +1180,19 @@ mod tests { .to_string(), expected: Object::INTEGER(11), }, + VmTestCase { + input: r#" + let a = 10; + let f = fn(a){ + let a = 1; + let a = a + 1; + a + }; + f(1) + a + "# + .to_string(), + expected: Object::INTEGER(12), + }, VmTestCase { input: r#" let a = 10; @@ -1199,6 +1213,7 @@ mod tests { let a = 10; let f = fn(){ let a = 1; + let a = a + 1; let h = fn(){ let a = 2; a @@ -1208,10 +1223,306 @@ mod tests { f() + a "# .to_string(), - expected: Object::INTEGER(13), + expected: Object::INTEGER(14), + }, + ]; + + run_vm_tests(tests); + } + + #[test] + fn test_while_statements_without_break_or_continue() { + let tests = vec![ + VmTestCase { + input: r#" + let a = 1; + while (a < 100){ + let a = a + 1; + } + a + "# + .to_string(), + expected: Object::INTEGER(100), + }, + VmTestCase { + input: r#" + let a = 1; + while (a < 0){ + let a = 100; + } + a + "# + .to_string(), + expected: Object::INTEGER(1), + }, + VmTestCase { + input: r#" + let a = 1; + while(false){ + let a = 100; + } + a + "# + .to_string(), + expected: Object::INTEGER(1), + }, + ]; + + run_vm_tests(tests); + } + + #[test] + fn test_while_clean_up() { + // This tests makes sure that a while statement clears the stack correctly (which is + // different from the conditional behavior) + let tests = vec![VmTestCase { + input: r#" + let a = 0; + while (a < 10000){ + let a = a + 1; + puts(a); + } + a + "# + .to_string(), + expected: Object::INTEGER(10000), + }]; + + run_vm_tests(tests); + } + + #[test] + fn test_break_from_while() { + let tests = vec![ + VmTestCase { + input: r#" + let a = 0; + while (a < 10) { + if (a == 5) { + break; + } + let a = a + 1; + }; + a"# + .to_string(), + expected: Object::INTEGER(5), + }, + VmTestCase { + input: r#" + let a = 0; + let c = 0; + while (a < 10) { + let b = 0; + while (b < 10) { + if (b == 5) { + break; + } + let b = b + 1; + let c = c + 1; + } + let a = a + 1; + }; + c"# + .to_string(), + expected: Object::INTEGER(50), + }, + VmTestCase { + input: r#" + let a = 0; + let c = 0; + while (a < 10) { + if (a == 5) { + break; + } + + let b = 0; + while (b < 10) { + if (b == 5) { + break; + } + let b = b + 1; + let c = c + 1; + } + let a = a + 1; + }; + c"# + .to_string(), + expected: Object::INTEGER(25), + }, + // The next tests will take care of the possible interference between the break and a function + VmTestCase { + input: r#" + let f = fn (a) { + let c = 0; + while (a < 10) { + if (a == 5) { + break; + } + + let b = 0; + while (b < 10) { + if (b == 5) { + break; + } + let b = b + 1; + let c = c + 1; + } + let a = a + 1; + } + c + }; + f(0)"# + .to_string(), + expected: Object::INTEGER(25), + }, + VmTestCase { + input: r#" + let a = 0; + let c = 0; + while (a < 10) { + if (a == 5) { + break; + } + + let f = fn () { + let c = 0; + let b = 0; + while (b < 10) { + if (b == 5) { + break; + } + let b = b + 1; + let c = c + 1; + } + c + } + + let a = a + 1; + let c = c + f(); + }; + c"# + .to_string(), + expected: Object::INTEGER(25), + }, + ]; + + run_vm_tests(tests); + } + + #[test] + fn test_continue_from_while() { + let tests = vec![ + VmTestCase { + input: r#" + let a = 0; + let c = 0; + while (a < 10) { + let a = a + 1; + if (a == 5) { + let c = c + 2; + continue; + } + let c = c + 1; + }; + c"# + .to_string(), + expected: Object::INTEGER(11), + }, + VmTestCase { + input: r#" + let a = 0; + let c = 0; + while (a < 10) { + let b = 0; + while (b < 10) { + let b = b + 1; + if (b == 5) { + let c = c + 3; + continue; + } + let c = c + 1; + } + let a = a + 1; + }; + c"# + .to_string(), + expected: Object::INTEGER(120), + }, + // The next tests will take care of the possible interference between the continue and a function + VmTestCase { + input: r#" + let f = fn (a) { + let c = 0; + while (a < 10) { + let b = 0; + while (b < 10) { + let b = b + 1; + if (b == 5) { + let c = c + 3; + continue; + } + let c = c + 1; + } + let a = a + 1; + } + c + }; + f(0)"# + .to_string(), + expected: Object::INTEGER(120), + }, + VmTestCase { + input: r#" + let a = 0; + let c = 0; + while (a < 10) { + let f = fn () { + let c = 0; + let b = 0; + while (b < 10) { + let b = b + 1; + if (b == 5) { + let c = c + 3; + continue; + } + let c = c + 1; + } + c + } + + let a = a + 1; + let c = c + f(); + }; + c"# + .to_string(), + expected: Object::INTEGER(120), }, ]; run_vm_tests(tests); } + + #[test] + fn test_continue_and_break_in_while() { + let tests = vec![VmTestCase { + input: r#" + let a = 0; + let c = 0; + while (a < 10) { + let a = a + 1; + if (a == 5) { + let c = c + 3; + continue; + } + if (a == 7) { + break; + } + let c = c + 1; + } + c"# + .to_string(), + expected: Object::INTEGER(8), + }]; + run_vm_tests(tests); + } }