From 1aff1693ecc2fcc7b751ed4b32d8241257ad1778 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vid=20Drobni=C4=8D?= Date: Mon, 3 Jun 2024 13:50:04 +0200 Subject: [PATCH] feat: implement assign to globals --- runtime/src/bytecode.rs | 9 + runtime/src/{compiler.rs => compiler/mod.rs} | 121 ++++++++-- runtime/src/compiler/symbol_table.rs | 38 +++ .../{test/compiler.rs => compiler/test.rs} | 221 +++++++++++++++++- runtime/src/error.rs | 18 ++ runtime/src/lib.rs | 5 +- runtime/src/test/mod.rs | 2 - runtime/src/vm/mod.rs | 73 ++++++ runtime/src/{test/vm.rs => vm/test.rs} | 90 +++++-- 9 files changed, 527 insertions(+), 50 deletions(-) rename runtime/src/{compiler.rs => compiler/mod.rs} (59%) create mode 100644 runtime/src/compiler/symbol_table.rs rename runtime/src/{test/compiler.rs => compiler/test.rs} (64%) delete mode 100644 runtime/src/test/mod.rs rename runtime/src/{test/vm.rs => vm/test.rs} (66%) diff --git a/runtime/src/bytecode.rs b/runtime/src/bytecode.rs index e15943d..fef9312 100644 --- a/runtime/src/bytecode.rs +++ b/runtime/src/bytecode.rs @@ -14,6 +14,15 @@ pub enum Instruction { Jump(usize), JumpNotTruthy(usize), + + // Puts all array values on stack, where + // array should be given size long. + UnpackArray(usize), + + IndexSet, + + StoreGlobal(usize), + LoadGlobal(usize), } #[derive(Debug, PartialEq, Clone)] diff --git a/runtime/src/compiler.rs b/runtime/src/compiler/mod.rs similarity index 59% rename from runtime/src/compiler.rs rename to runtime/src/compiler/mod.rs index 819b647..3dc22a9 100644 --- a/runtime/src/compiler.rs +++ b/runtime/src/compiler/mod.rs @@ -2,6 +2,7 @@ use std::rc::Rc; use crate::{ bytecode::{Bytecode, Instruction}, + error::{Error, ErrorKind}, object::Object, }; @@ -10,6 +11,13 @@ use parser::{ position::Range, }; +use self::symbol_table::{Symbol, SymbolTable}; + +mod symbol_table; + +#[cfg(test)] +mod test; + #[derive(Debug, Default)] struct Scope { instructions: Vec, @@ -20,6 +28,8 @@ struct Scope { pub struct Compiler { constants: Vec, + symbol_table: SymbolTable, + scopes: Vec, scope_index: usize, } @@ -28,6 +38,7 @@ impl Compiler { pub fn new() -> Self { Self { constants: vec![], + symbol_table: SymbolTable::new(), scopes: vec![Scope::default()], scope_index: 0, } @@ -50,9 +61,9 @@ impl Compiler { scope.instructions.len() - 1 } - pub fn compile(mut self, program: &ast::Program) -> Bytecode { + pub fn compile(mut self, program: &ast::Program) -> Result { for node in &program.statements { - self.compile_node(node); + self.compile_node(node)?; if node.kind() == ast::NodeKind::Expression { self.emit(Instruction::Pop, node.range); @@ -62,16 +73,16 @@ impl Compiler { // If compiler works correctly, we should have one scope. let scope = self.scopes.pop().expect("Invalid number of scopes"); - Bytecode { + Ok(Bytecode { constants: self.constants, instructions: scope.instructions, ranges: scope.ranges, - } + }) } - fn compile_node(&mut self, node: &ast::Node) { + fn compile_node(&mut self, node: &ast::Node) -> Result<(), Error> { match &node.value { - ast::NodeValue::Identifier(_) => todo!(), + ast::NodeValue::Identifier(ident) => self.compile_ident(ident, node.range)?, ast::NodeValue::IntegerLiteral(int) => { self.compile_constant(Object::Integer(*int), node.range); } @@ -84,14 +95,17 @@ impl Compiler { ast::NodeValue::StringLiteral(string) => { self.compile_constant(Object::String(Rc::new(string.to_string())), node.range); } - ast::NodeValue::ArrayLiteral(arr) => self.compile_array(arr, node.range), - ast::NodeValue::HashLiteral(elements) => self.compile_hash_map(elements, node.range), - ast::NodeValue::PrefixOperator { .. } => self.compile_prefix_operator(node), + ast::NodeValue::ArrayLiteral(arr) => self.compile_array(arr, node.range)?, + ast::NodeValue::HashLiteral(elements) => self.compile_hash_map(elements, node.range)?, + ast::NodeValue::PrefixOperator { .. } => self.compile_prefix_operator(node)?, ast::NodeValue::InfixOperator { .. } => todo!(), - ast::NodeValue::Assign { .. } => todo!(), + ast::NodeValue::Assign { ident, value } => { + self.compile_node(value)?; + self.compile_assign(ident, node.range)?; + } ast::NodeValue::Index { .. } => todo!(), ast::NodeValue::If(_) => todo!(), - ast::NodeValue::While { .. } => self.compile_while(node), + ast::NodeValue::While { .. } => self.compile_while(node)?, ast::NodeValue::For { .. } => todo!(), ast::NodeValue::Break => todo!(), ast::NodeValue::Continue => todo!(), @@ -100,6 +114,8 @@ impl Compiler { ast::NodeValue::Return(_) => todo!(), ast::NodeValue::Use(_) => todo!(), } + + Ok(()) } fn compile_constant(&mut self, constant: Object, range: Range) { @@ -107,62 +123,121 @@ impl Compiler { self.emit(Instruction::Constant(const_idx), range); } - fn compile_array(&mut self, arr: &[ast::Node], range: Range) { + fn compile_array(&mut self, arr: &[ast::Node], range: Range) -> Result<(), Error> { for node in arr { - self.compile_node(node); + self.compile_node(node)?; } self.emit(Instruction::Array(arr.len()), range); + Ok(()) } - fn compile_hash_map(&mut self, elements: &[ast::HashLiteralPair], range: Range) { + fn compile_hash_map( + &mut self, + elements: &[ast::HashLiteralPair], + range: Range, + ) -> Result<(), Error> { for elt in elements { - self.compile_node(&elt.key); - self.compile_node(&elt.value); + self.compile_node(&elt.key)?; + self.compile_node(&elt.value)?; } self.emit(Instruction::HashMap(elements.len() * 2), range); + + Ok(()) } - fn compile_prefix_operator(&mut self, node: &ast::Node) { + fn compile_prefix_operator(&mut self, node: &ast::Node) -> Result<(), Error> { let ast::NodeValue::PrefixOperator { operator, right } = &node.value else { panic!("Expected prefix operator node, got: {node:?}"); }; - self.compile_node(right); + self.compile_node(right)?; match operator { PrefixOperatorKind::Not => self.emit(Instruction::Bang, node.range), PrefixOperatorKind::Negative => self.emit(Instruction::Minus, node.range), }; + + Ok(()) } - fn compile_while(&mut self, node: &ast::Node) { + fn compile_while(&mut self, node: &ast::Node) -> Result<(), Error> { let ast::NodeValue::While { condition, body } = &node.value else { panic!("Expected while node, got: {node:?}"); }; let start_index = self.current_scope().instructions.len(); - self.compile_node(condition); + self.compile_node(condition)?; // Jump position will be fixed after let jump_index = self.emit(Instruction::JumpNotTruthy(0), condition.range); - self.compile_block(body); + self.compile_block(body)?; self.emit(Instruction::Jump(start_index), body.range); let end_index = self.current_scope().instructions.len(); self.current_scope().instructions[jump_index] = Instruction::JumpNotTruthy(end_index); + + Ok(()) } - fn compile_block(&mut self, block: &ast::Block) { + fn compile_block(&mut self, block: &ast::Block) -> Result<(), Error> { for node in &block.nodes { - self.compile_node(node); + self.compile_node(node)?; if node.kind() == ast::NodeKind::Expression { self.emit(Instruction::Pop, node.range); } } + + Ok(()) + } + + fn compile_ident(&mut self, ident: &str, range: Range) -> Result<(), Error> { + let Some(symbol) = self.symbol_table.resolve(ident) else { + return Err(Error { + kind: ErrorKind::UndefinedSymbol(ident.to_string()), + range, + }); + }; + + match symbol { + Symbol::Global(index) => self.emit(Instruction::LoadGlobal(index), range), + }; + + Ok(()) + } + + fn compile_assign(&mut self, ident: &ast::Node, range: Range) -> Result<(), Error> { + match &ident.value { + ast::NodeValue::Identifier(identifier) => { + let symbol = self.symbol_table.define(identifier.to_string()); + self.compile_store_instruction(symbol, range); + } + ast::NodeValue::Index { left, index } => { + self.compile_node(left)?; + self.compile_node(index)?; + self.emit(Instruction::IndexSet, range); + } + ast::NodeValue::ArrayLiteral(arr) => { + self.emit(Instruction::UnpackArray(arr.len()), range); + + for node in arr { + self.compile_assign(node, range)?; + } + } + + _ => panic!("Invalid asignee: {ident:?}"), + } + + Ok(()) + } + + fn compile_store_instruction(&mut self, symbol: Symbol, range: Range) { + match symbol { + Symbol::Global(index) => self.emit(Instruction::StoreGlobal(index), range), + }; } } diff --git a/runtime/src/compiler/symbol_table.rs b/runtime/src/compiler/symbol_table.rs new file mode 100644 index 0000000..0a48126 --- /dev/null +++ b/runtime/src/compiler/symbol_table.rs @@ -0,0 +1,38 @@ +use std::collections::HashMap; + +#[derive(Debug, PartialEq, Eq, Clone, Copy)] +pub enum Symbol { + Global(usize), +} + +#[derive(Debug)] +pub struct SymbolTable { + store: HashMap, + num_definitions: usize, +} + +impl SymbolTable { + pub fn new() -> Self { + Self { + store: HashMap::new(), + num_definitions: 0, + } + } + + pub fn define(&mut self, name: String) -> Symbol { + if let Some(symbol) = self.store.get(&name) { + return *symbol; + } + + let symbol = Symbol::Global(self.num_definitions); + + self.store.insert(name, symbol); + self.num_definitions += 1; + + symbol + } + + pub fn resolve(&self, name: &str) -> Option { + self.store.get(name).copied() + } +} diff --git a/runtime/src/test/compiler.rs b/runtime/src/compiler/test.rs similarity index 64% rename from runtime/src/test/compiler.rs rename to runtime/src/compiler/test.rs index 7418194..c5aa108 100644 --- a/runtime/src/test/compiler.rs +++ b/runtime/src/compiler/test.rs @@ -75,7 +75,7 @@ fn constants() { for (input, expected) in tests { let program = parse(input).unwrap(); let compiler = Compiler::new(); - let bytecode = compiler.compile(&program); + let bytecode = compiler.compile(&program).unwrap(); assert_eq!(bytecode, expected); } } @@ -160,7 +160,7 @@ fn arrays() { for (input, expected) in tests { let program = parse(input).unwrap(); let compiler = Compiler::new(); - let bytecode = compiler.compile(&program); + let bytecode = compiler.compile(&program).unwrap(); assert_eq!(bytecode, expected); } @@ -189,7 +189,7 @@ fn arrays() { for (input, expected) in tests { let program = parse(input).unwrap(); let compiler = Compiler::new(); - let mut bytecode = compiler.compile(&program); + let mut bytecode = compiler.compile(&program).unwrap(); bytecode.ranges = vec![]; assert_eq!(bytecode, expected); @@ -248,7 +248,7 @@ fn hash_map() { for (input, expected) in tests { let program = parse(input).unwrap(); let compiler = Compiler::new(); - let bytecode = compiler.compile(&program); + let bytecode = compiler.compile(&program).unwrap(); assert_eq!(bytecode, expected); } @@ -279,7 +279,7 @@ fn hash_map() { for (input, expected) in tests { let program = parse(input).unwrap(); let compiler = Compiler::new(); - let mut bytecode = compiler.compile(&program); + let mut bytecode = compiler.compile(&program).unwrap(); bytecode.ranges = vec![]; assert_eq!(bytecode, expected); @@ -394,7 +394,7 @@ fn prefix_operator() { for (input, expected) in tests { let program = parse(input).unwrap(); let compiler = Compiler::new(); - let bytecode = compiler.compile(&program); + let bytecode = compiler.compile(&program).unwrap(); assert_eq!(bytecode, expected); } @@ -468,7 +468,214 @@ fn while_loop() { for (input, expected) in tests { let program = parse(input).unwrap(); let compiler = Compiler::new(); - let bytecode = compiler.compile(&program); + let bytecode = compiler.compile(&program).unwrap(); + + assert_eq!(bytecode, expected); + } +} + +#[test] +fn assign() { + let tests = [ + ( + "foo = -1", + Bytecode { + constants: vec![Object::Integer(1)], + instructions: vec![ + Instruction::Constant(0), + Instruction::Minus, + Instruction::StoreGlobal(0), + ], + ranges: vec![ + Range { + start: Position::new(0, 7), + end: Position::new(0, 8), + }, + Range { + start: Position::new(0, 6), + end: Position::new(0, 8), + }, + Range { + start: Position::new(0, 0), + end: Position::new(0, 8), + }, + ], + }, + ), + ( + "[foo] = [1]", + Bytecode { + constants: vec![Object::Integer(1)], + instructions: vec![ + Instruction::Constant(0), + Instruction::Array(1), + Instruction::UnpackArray(1), + Instruction::StoreGlobal(0), + ], + ranges: vec![ + Range { + start: Position::new(0, 9), + end: Position::new(0, 10), + }, + Range { + start: Position::new(0, 8), + end: Position::new(0, 11), + }, + Range { + start: Position::new(0, 0), + end: Position::new(0, 11), + }, + Range { + start: Position::new(0, 0), + end: Position::new(0, 11), + }, + ], + }, + ), + ( + "[foo, bar] = [1, 2]", + Bytecode { + constants: vec![Object::Integer(1), Object::Integer(2)], + instructions: vec![ + Instruction::Constant(0), + Instruction::Constant(1), + Instruction::Array(2), + Instruction::UnpackArray(2), + Instruction::StoreGlobal(0), + Instruction::StoreGlobal(1), + ], + ranges: vec![ + Range { + start: Position::new(0, 14), + end: Position::new(0, 15), + }, + Range { + start: Position::new(0, 17), + end: Position::new(0, 18), + }, + Range { + start: Position::new(0, 13), + end: Position::new(0, 19), + }, + Range { + start: Position::new(0, 0), + end: Position::new(0, 19), + }, + Range { + start: Position::new(0, 0), + end: Position::new(0, 19), + }, + Range { + start: Position::new(0, 0), + end: Position::new(0, 19), + }, + ], + }, + ), + ( + "foo = []\nfoo[0] = 1", + Bytecode { + constants: vec![Object::Integer(1), Object::Integer(0)], + instructions: vec![ + Instruction::Array(0), + Instruction::StoreGlobal(0), + Instruction::Constant(0), + Instruction::LoadGlobal(0), + Instruction::Constant(1), + Instruction::IndexSet, + ], + ranges: vec![ + Range { + start: Position::new(0, 6), + end: Position::new(0, 8), + }, + Range { + start: Position::new(0, 0), + end: Position::new(0, 8), + }, + Range { + start: Position::new(1, 9), + end: Position::new(1, 10), + }, + Range { + start: Position::new(1, 0), + end: Position::new(1, 3), + }, + Range { + start: Position::new(1, 4), + end: Position::new(1, 5), + }, + Range { + start: Position::new(1, 0), + end: Position::new(1, 10), + }, + ], + }, + ), + ]; + + for (input, expected) in tests { + let program = parse(input).unwrap(); + let compiler = Compiler::new(); + let bytecode = compiler.compile(&program).unwrap(); + + assert_eq!(bytecode, expected); + } + + // Without positions + let tests = [ + ( + "[foo, [bar, baz]] = [1, [2, 3]]", + Bytecode { + constants: vec![Object::Integer(1), Object::Integer(2), Object::Integer(3)], + instructions: vec![ + Instruction::Constant(0), + Instruction::Constant(1), + Instruction::Constant(2), + Instruction::Array(2), + Instruction::Array(2), + Instruction::UnpackArray(2), + Instruction::StoreGlobal(0), + Instruction::UnpackArray(2), + Instruction::StoreGlobal(1), + Instruction::StoreGlobal(2), + ], + ranges: vec![], + }, + ), + ( + "foo = {}\n[foo.bar, foo.baz] = [10, 20]", + Bytecode { + constants: vec![ + Object::Integer(10), + Object::Integer(20), + Object::String(Rc::new("bar".to_string())), + Object::String(Rc::new("baz".to_string())), + ], + instructions: vec![ + Instruction::HashMap(0), + Instruction::StoreGlobal(0), + Instruction::Constant(0), + Instruction::Constant(1), + Instruction::Array(2), + Instruction::UnpackArray(2), + Instruction::LoadGlobal(0), + Instruction::Constant(2), + Instruction::IndexSet, + Instruction::LoadGlobal(0), + Instruction::Constant(3), + Instruction::IndexSet, + ], + ranges: vec![], + }, + ), + ]; + + for (input, expected) in tests { + let program = parse(input).unwrap(); + let compiler = Compiler::new(); + let mut bytecode = compiler.compile(&program).unwrap(); + bytecode.ranges = vec![]; assert_eq!(bytecode, expected); } diff --git a/runtime/src/error.rs b/runtime/src/error.rs index 6769834..7c2fb62 100644 --- a/runtime/src/error.rs +++ b/runtime/src/error.rs @@ -10,6 +10,12 @@ pub enum ErrorKind { StackOverflow, NotHashable(DataType), InvalidNegateOperand(DataType), + UndefinedSymbol(String), + NotUnpackable(DataType), + UnpackLengthMismatch { expected: usize, got: usize }, + UnpackTooLarge { max: usize, got: usize }, + NotIndexable(DataType), + InvalidIndexType(DataType), } #[derive(Debug, Error, PartialEq)] @@ -30,6 +36,18 @@ impl Display for ErrorKind { ErrorKind::StackOverflow => write!(f, "Stack overflow"), ErrorKind::NotHashable(data_type) => write!(f, "Data type {data_type} can't be hashed"), ErrorKind::InvalidNegateOperand(dt) => write!(f, "Can not negate {dt}"), + ErrorKind::UndefinedSymbol(ident) => write!(f, "Symbol {ident} is not defined"), + ErrorKind::NotUnpackable(dt) => write!(f, "Data type {dt} can't be unpacked"), + ErrorKind::UnpackLengthMismatch { expected, got } => write!( + f, + "Invalid number of elements to unpack. Expected: {expected}, got: {got}" + ), + ErrorKind::UnpackTooLarge { max, got } => write!( + f, + "Too many elements to unpack. Max allowed: {max}, got: {got}" + ), + ErrorKind::NotIndexable(dt) => write!(f, "Data type {dt} can't be indexed"), + ErrorKind::InvalidIndexType(dt) => write!(f, "Invalid index type: {dt}"), } } } diff --git a/runtime/src/lib.rs b/runtime/src/lib.rs index 34b5d0b..40cde31 100644 --- a/runtime/src/lib.rs +++ b/runtime/src/lib.rs @@ -7,12 +7,9 @@ mod compiler; mod object; mod vm; -#[cfg(test)] -mod test; - pub fn run(program: &ast::Program) -> Result<(), error::Error> { let compiler = compiler::Compiler::new(); - let bytecode = compiler.compile(program); + let bytecode = compiler.compile(program)?; let mut vm = vm::VirtualMachine::new(); vm.run(&bytecode)?; diff --git a/runtime/src/test/mod.rs b/runtime/src/test/mod.rs deleted file mode 100644 index 444abbc..0000000 --- a/runtime/src/test/mod.rs +++ /dev/null @@ -1,2 +0,0 @@ -mod compiler; -mod vm; diff --git a/runtime/src/vm/mod.rs b/runtime/src/vm/mod.rs index b92bdf5..dec00e2 100644 --- a/runtime/src/vm/mod.rs +++ b/runtime/src/vm/mod.rs @@ -10,12 +10,18 @@ use self::gc::GarbageCollector; pub(crate) mod gc; +#[cfg(test)] +mod test; + const STACK_SIZE: usize = 4096; +const GLOBALS_SIZE: usize = 512; #[derive(Debug)] pub struct VirtualMachine { gc: GarbageCollector, + globals: Vec, + stack: Vec, // StackPointer which points to the next value. // Top of the stack is stack[sp-1] @@ -26,6 +32,7 @@ impl VirtualMachine { pub fn new() -> Self { Self { gc: GarbageCollector::new(), + globals: vec![Object::Null; GLOBALS_SIZE], stack: vec![Object::Null; STACK_SIZE], sp: 0, } @@ -91,6 +98,16 @@ impl VirtualMachine { return Ok(index); } } + Instruction::UnpackArray(size) => self.unpack_array(size)?, + Instruction::StoreGlobal(index) => { + let obj = self.pop(); + self.globals[index] = obj; + } + Instruction::LoadGlobal(index) => { + let obj = self.globals[index].clone(); + self.push(obj)?; + } + Instruction::IndexSet => self.index_set()?, } Ok(ip + 1) @@ -150,4 +167,60 @@ impl VirtualMachine { Ok(()) } + + fn unpack_array(&mut self, size: usize) -> Result<(), ErrorKind> { + let obj = self.pop(); + let Object::Array(arr) = obj else { + return Err(ErrorKind::NotUnpackable(obj.into())); + }; + + let rc = arr.0.value.upgrade().unwrap(); + let values = rc.borrow(); + if values.len() != size { + return Err(ErrorKind::UnpackLengthMismatch { + expected: size, + got: values.len(), + }); + } + + if values.len() > 256 { + return Err(ErrorKind::UnpackTooLarge { + max: 256, + got: values.len(), + }); + } + + for obj in values.iter().rev() { + self.push(obj.clone())?; + } + + Ok(()) + } + + fn index_set(&mut self) -> Result<(), ErrorKind> { + let index = self.pop(); + let container = self.pop(); + let value = self.pop(); + + match container { + Object::Array(arr) => { + let Object::Integer(idx) = index else { + return Err(ErrorKind::InvalidIndexType(index.into())); + }; + + let rc = arr.0.value.upgrade().unwrap(); + rc.borrow_mut()[idx as usize] = value; + } + Object::Dictionary(dict) => { + let key: HashKey = index.try_into()?; + + let rc = dict.0.value.upgrade().unwrap(); + rc.borrow_mut().insert(key, value); + } + + _ => return Err(ErrorKind::NotIndexable(container.into())), + } + + Ok(()) + } } diff --git a/runtime/src/test/vm.rs b/runtime/src/vm/test.rs similarity index 66% rename from runtime/src/test/vm.rs rename to runtime/src/vm/test.rs index 468c150..2ea71c3 100644 --- a/runtime/src/test/vm.rs +++ b/runtime/src/vm/test.rs @@ -13,7 +13,7 @@ fn run_test(input: &str, expected: Result) { let program = parser::parse(input).unwrap(); let compiler = Compiler::new(); - let bytecode = compiler.compile(&program); + let bytecode = compiler.compile(&program).unwrap(); let mut vm = VirtualMachine::new(); let obj = vm.run(&bytecode); @@ -93,19 +93,6 @@ fn hash_map() { Object::Integer(1), )]), ), - // ( - // "{1: 2, 2: {3: 4}}", - // HashMap::from([ - // (HashKey::Integer(1), Object::Integer(2)), - // ( - // HashKey::Integer(2), - // Object::Dictionary(Rc::new(HashMap::from([( - // HashKey::Integer(3), - // Object::Integer(4), - // )]))), - // ), - // ]), - // ), ]; for (input, expected) in tests { @@ -189,3 +176,78 @@ fn while_loop() { run_test(input, Ok(expected)); } } + +#[test] +fn assign() { + let tests = [ + ("a = 10\n a", Object::Integer(10)), + ("[a, b] = [1, 2]\n a", Object::Integer(1)), + ("[a, b] = [1, 2]\n b", Object::Integer(2)), + ("[a, [b, c]] = [1, [2, 3]]\n a", Object::Integer(1)), + ("[a, [b, c]] = [1, [2, 3]]\n b", Object::Integer(2)), + ("[a, [b, c]] = [1, [2, 3]]\n c", Object::Integer(3)), + ]; + + for (input, expected) in tests { + run_test(input, Ok(expected)); + } +} + +#[test] +fn assign_array_index() { + let tests = [ + ("a = [0]\n a[0] = 1\n a", vec![Object::Integer(1)]), + ( + "a = [0, 1]\n [a[0], a[1]] = [42, 69]\n a", + vec![Object::Integer(42), Object::Integer(69)], + ), + ]; + + for (input, expected) in tests { + let rc = Rc::new(RefCell::new(expected)); + let arr = Array(gc::Ref { + value: Rc::downgrade(&rc), + id: 0, + }); + run_test(input, Ok(Object::Array(arr))); + } +} + +#[test] +fn assign_dict_index() { + let tests = [ + ( + "a = {}\n a[0] = 1\n a", + HashMap::from([(HashKey::Integer(0), Object::Integer(1))]), + ), + ( + "a = {}\n a.foo = 42\n a", + HashMap::from([( + HashKey::String(Rc::new("foo".to_string())), + Object::Integer(42), + )]), + ), + ( + "a = {}\n [a.foo, a[\"bar\"]] = [42, 69]\n a", + HashMap::from([ + ( + HashKey::String(Rc::new("foo".to_string())), + Object::Integer(42), + ), + ( + HashKey::String(Rc::new("bar".to_string())), + Object::Integer(69), + ), + ]), + ), + ]; + + for (input, expected) in tests { + let rc = Rc::new(RefCell::new(expected)); + let arr = Dictionary(gc::Ref { + value: Rc::downgrade(&rc), + id: 0, + }); + run_test(input, Ok(Object::Dictionary(arr))); + } +}