From d08ce44f23bec52a558540e7acbfc7fb57edc790 Mon Sep 17 00:00:00 2001 From: Yago Iglesias Date: Thu, 13 Jul 2023 20:29:19 +0200 Subject: [PATCH] Feat: Add support for HashMaps on the compiler and VM --- crates/compiler/src/code.rs | 5 ++- crates/compiler/src/compiler.rs | 69 +++++++++++++++++++++++++++++++ crates/compiler/src/test_utils.rs | 3 +- crates/vm/src/vm.rs | 63 +++++++++++++++++++++++++++- 4 files changed, 135 insertions(+), 5 deletions(-) diff --git a/crates/compiler/src/code.rs b/crates/compiler/src/code.rs index 9269a2b..8a9cdce 100644 --- a/crates/compiler/src/code.rs +++ b/crates/compiler/src/code.rs @@ -104,6 +104,7 @@ pub enum Opcode { // Custom types Array, + HashMap, // Stack Pop, @@ -133,6 +134,7 @@ impl Display for Opcode { Opcode::SetGlobal => "OpSetGlobal", Opcode::GetGlobal => "OpGetGlobal", Opcode::Array => "OpArray", + Opcode::HashMap => "OpHashMap", Opcode::Pop => "OpPop", }; write!(f, "{op}") @@ -147,7 +149,8 @@ impl Opcode { | Opcode::JumpNotTruthy | Opcode::SetGlobal | Opcode::GetGlobal - | Opcode::Array => vec![2], + | Opcode::Array + | Opcode::HashMap => vec![2], _ => vec![], } } diff --git a/crates/compiler/src/compiler.rs b/crates/compiler/src/compiler.rs index 4c7bed9..b03a8b2 100644 --- a/crates/compiler/src/compiler.rs +++ b/crates/compiler/src/compiler.rs @@ -117,6 +117,15 @@ impl Compiler { } self.emit(Opcode::Array, vec![len]); } + + Expression::HashMapLiteral(hasmap) => { + let len = i32::from_usize(hasmap.pairs.len()).ok_or("Invalid hashmap length")?; + for (key, value) in hasmap.pairs { + self.compile_expression(key)?; + self.compile_expression(value)?; + } + self.emit(Opcode::HashMap, vec![len * 2]); + } _ => unimplemented!(), } @@ -731,4 +740,64 @@ pub mod tests { run_compiler(tests); } + + #[test] + fn test_hash_expression() { + let tests = vec![ + CompilerTestCase { + input: "{}".to_string(), + expected_constants: vec![], + expected_instructions: flatten_instructions(vec![ + Opcode::HashMap.make(vec![0]), + Opcode::Pop.make(vec![]), + ]), + }, + CompilerTestCase { + input: "{1: 2, 3: 4, 5: 6}".to_string(), + expected_constants: vec![ + Object::INTEGER(1), + Object::INTEGER(2), + Object::INTEGER(3), + Object::INTEGER(4), + Object::INTEGER(5), + Object::INTEGER(6), + ], + expected_instructions: flatten_instructions(vec![ + Opcode::Constant.make(vec![0]), + Opcode::Constant.make(vec![1]), + Opcode::Constant.make(vec![2]), + Opcode::Constant.make(vec![3]), + Opcode::Constant.make(vec![4]), + Opcode::Constant.make(vec![5]), + Opcode::HashMap.make(vec![6]), + Opcode::Pop.make(vec![]), + ]), + }, + CompilerTestCase { + input: "{1: 2 + 3, 4: 5 * 6}".to_string(), + expected_constants: vec![ + Object::INTEGER(1), + Object::INTEGER(2), + Object::INTEGER(3), + Object::INTEGER(4), + Object::INTEGER(5), + Object::INTEGER(6), + ], + expected_instructions: flatten_instructions(vec![ + Opcode::Constant.make(vec![0]), + Opcode::Constant.make(vec![1]), + Opcode::Constant.make(vec![2]), + Opcode::Add.make(vec![]), + Opcode::Constant.make(vec![3]), + Opcode::Constant.make(vec![4]), + Opcode::Constant.make(vec![5]), + Opcode::Mul.make(vec![]), + Opcode::HashMap.make(vec![4]), + Opcode::Pop.make(vec![]), + ]), + }, + ]; + + run_compiler(tests); + } } diff --git a/crates/compiler/src/test_utils.rs b/crates/compiler/src/test_utils.rs index ecf5d4c..b99b58e 100644 --- a/crates/compiler/src/test_utils.rs +++ b/crates/compiler/src/test_utils.rs @@ -34,8 +34,7 @@ pub fn check_constants(constants: &Vec, expected: &Vec>) { for (expected_constant, constant) in expected.iter().zip(constants.iter()) { assert_eq!( **expected_constant, *constant, - "constant not equal. got={:?}, want={:?}", - constant, expected_constant + "constant not equal. got={constant:?}, want={expected_constant:?}" ); } } diff --git a/crates/vm/src/vm.rs b/crates/vm/src/vm.rs index 722fd46..94b38c2 100644 --- a/crates/vm/src/vm.rs +++ b/crates/vm/src/vm.rs @@ -4,7 +4,7 @@ use compiler::{ }; use interpreter::object::Object; use num_traits::FromPrimitive; -use std::rc::Rc; +use std::{collections::HashMap, rc::Rc}; const STACK_SIZE: usize = 2048; pub const GLOBALS_SIZE: usize = 65536; @@ -125,6 +125,13 @@ impl VM { self.sp -= num_elements; self.push(array)?; } + Opcode::HashMap => { + let num_elements = read_u16(&self.instructions.data[ip + 1..]) as usize; + ip += 2; + let hashmap = self.build_hashmap(self.sp - num_elements, self.sp)?; + self.sp -= num_elements; + self.push(hashmap)?; + } } ip += 1; } @@ -269,11 +276,28 @@ impl VM { fn build_array(&self, start_index: usize, end_index: usize) -> Result, String> { let mut elements: Vec = Vec::new(); for i in start_index..end_index { - elements.push((*self.stack[i]).clone()); // TODO: Chnage this + elements + .push((**(self.stack.get(i).ok_or("Unable to get element".to_string()))?).clone()); } Ok(Rc::new(Object::ARRAY(elements))) } + fn build_hashmap(&self, start_index: usize, end_index: usize) -> Result, String> { + let mut elements: HashMap = HashMap::new(); + for i in (start_index..end_index).step_by(2) { + let key = (**(self.stack.get(i).ok_or("Unable to get element".to_string()))?).clone(); + let value = (**(self + .stack + .get(i + 1) + .ok_or("Unable to get element".to_string()))?) + .clone(); + if !Object::is_hashable(&key) { + return Err(format!("Unusable as a hashmap key: {key:?}",)); + } + elements.insert(key, value); + } + Ok(Rc::new(Object::HASHMAP(elements))) + } fn native_boolean_to_boolean_object(&self, input: bool) -> Rc { if input { Rc::new(TRUE) @@ -329,6 +353,8 @@ impl VM { #[cfg(test)] mod tests { + use std::collections::HashMap; + use compiler::{ compiler::Compiler, test_utils::{check_constants, parse}, @@ -659,6 +685,39 @@ mod tests { }, ]; + run_vm_tests(tests); + } + #[test] + fn test_hashmap_expressions() { + let tests = vec![ + VmTestCase { + input: "{}".to_string(), + expected: Object::HASHMAP(HashMap::new()), + }, + VmTestCase { + input: "{1:2, 2:3}".to_string(), + expected: Object::HASHMAP( + vec![ + (Object::INTEGER(1), Object::INTEGER(2)), + (Object::INTEGER(2), Object::INTEGER(3)), + ] + .into_iter() + .collect(), + ), + }, + VmTestCase { + input: "{1+1:2, 2*2:3}".to_string(), + expected: Object::HASHMAP( + vec![ + (Object::INTEGER(2), Object::INTEGER(2)), + (Object::INTEGER(4), Object::INTEGER(3)), + ] + .into_iter() + .collect(), + ), + }, + ]; + run_vm_tests(tests); } }