Skip to content

Commit

Permalink
Feat: Add support for HashMaps on the compiler and VM
Browse files Browse the repository at this point in the history
  • Loading branch information
Yag000 committed Jul 13, 2023
1 parent 24e0ce5 commit d08ce44
Show file tree
Hide file tree
Showing 4 changed files with 135 additions and 5 deletions.
5 changes: 4 additions & 1 deletion crates/compiler/src/code.rs
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,7 @@ pub enum Opcode {

// Custom types
Array,
HashMap,

// Stack
Pop,
Expand Down Expand Up @@ -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}")
Expand All @@ -147,7 +149,8 @@ impl Opcode {
| Opcode::JumpNotTruthy
| Opcode::SetGlobal
| Opcode::GetGlobal
| Opcode::Array => vec![2],
| Opcode::Array
| Opcode::HashMap => vec![2],
_ => vec![],
}
}
Expand Down
69 changes: 69 additions & 0 deletions crates/compiler/src/compiler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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!(),
}

Expand Down Expand Up @@ -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);
}
}
3 changes: 1 addition & 2 deletions crates/compiler/src/test_utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,7 @@ pub fn check_constants(constants: &Vec<Object>, expected: &Vec<Rc<Object>>) {
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:?}"
);
}
}
63 changes: 61 additions & 2 deletions crates/vm/src/vm.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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;
}
Expand Down Expand Up @@ -269,11 +276,28 @@ impl VM {
fn build_array(&self, start_index: usize, end_index: usize) -> Result<Rc<Object>, String> {
let mut elements: Vec<Object> = 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<Rc<Object>, String> {
let mut elements: HashMap<Object, Object> = 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<Object> {
if input {
Rc::new(TRUE)
Expand Down Expand Up @@ -329,6 +353,8 @@ impl VM {
#[cfg(test)]
mod tests {

use std::collections::HashMap;

use compiler::{
compiler::Compiler,
test_utils::{check_constants, parse},
Expand Down Expand Up @@ -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);
}
}

0 comments on commit d08ce44

Please sign in to comment.