diff --git a/rlox/src/bc.rs b/rlox/src/bc.rs index 58cb15d..c6ce56f 100644 --- a/rlox/src/bc.rs +++ b/rlox/src/bc.rs @@ -26,6 +26,7 @@ pub enum Op { DefineGlobal { offset: u8 }, GetGlobal { offset: u8 }, + SetGlobal { offset: u8 }, } #[derive(Clone, Debug, PartialEq)] diff --git a/rlox/src/lc.rs b/rlox/src/lc.rs index 444a87c..b0b6a76 100644 --- a/rlox/src/lc.rs +++ b/rlox/src/lc.rs @@ -468,7 +468,14 @@ impl<'src> Parser<'src> { } TokenType::Identifier => { let offset = self.add_string(chunk, token.span); - chunk.add_op(Op::GetGlobal {offset}, token.line); + + if self.scanner.peek().is_some_and(|t| t.ttype == TokenType::Equal) { + self.scanner.next(); + self._expression(chunk, Precedence::Assignment)?; + chunk.add_op(Op::SetGlobal {offset}, token.line); + } else { + chunk.add_op(Op::GetGlobal {offset}, token.line); + }; } _ => { return Err(self.error_at(token, ParseErrorKind::IncompleteExpression)); @@ -566,6 +573,7 @@ impl<'src> Parser<'src> { match self.scanner.peek() { Some(token) if token.ttype == TokenType::Equal => { + self.scanner.next(); self.expression(chunk)?; }, _ => { @@ -757,6 +765,7 @@ mod tests { let mut parser = Parser::new(scanner); let mut chunk = Chunk::new(); parser.compile(&mut chunk); + assert_eq!(parser.errors, vec![]); assert!(chunk.instr_eq(expected)); } @@ -897,17 +906,35 @@ mod tests { test_parse_program(source, &expected); } + #[test] fn basic_var_decl_with_initializer() { let source = "var x = 1 + 1;"; use crate::bc::Op::*; let x = GC::new_string("x"); let expected = Chunk::new_with( vec![Constant {offset: 1}, Constant {offset: 2}, Add, DefineGlobal { offset: 0 }], - vec![], + vec![1, 1, 1, 1], vec![x.get_object().into(), Value::from(1.0), Value::from(1.0)], LinkedList::new(), ); test_parse_program(source, &expected); } + + #[test] + fn assign() { + let source = "var x = y = z;"; + use crate::bc::Op::*; + let x = GC::new_string("x"); + let y = GC::new_string("y"); + let z = GC::new_string("z"); + let expected = Chunk::new_with( + vec![GetGlobal { offset: 2 }, SetGlobal { offset: 1 }, DefineGlobal { offset: 0 }], + vec![1, 1, 1], + vec![x.get_object().into(), y.get_object().into(), z.get_object().into()], + LinkedList::new(), + ); + + test_parse_program(source, &expected); + } } diff --git a/rlox/src/vm.rs b/rlox/src/vm.rs index a49bde3..e7d9752 100644 --- a/rlox/src/vm.rs +++ b/rlox/src/vm.rs @@ -1,6 +1,6 @@ use crate::bc::{Chunk, Op, TraceInfo, Value}; use crate::gc::{GcHandle, ObjString, ObjectType, GC}; -use std::collections::{HashMap, LinkedList}; +use std::collections::{hash_map, HashMap, LinkedList}; use std::io; use std::rc::Rc; @@ -203,12 +203,35 @@ impl VM { Ok(()) } else { Err( - VMError::Runtime( - format!("Undefined variable '{}'.", ident).into(), - self.pc, - )) + VMError::Runtime( + format!("Undefined variable '{}'.", ident).into(), + self.pc, + ) + ) }? - } + }, + Op::SetGlobal { offset } => { + let ident = match chunk.constants[offset as usize] { + Value::Obj(object) => object.downcast::().unwrap(), + _ => todo!(), + }; + + match globals.entry(ident) { + hash_map::Entry::Occupied(mut entry) => { + let v = self.stack.last().unwrap(); + entry.insert(v.clone()); + Ok(()) + }, + hash_map::Entry::Vacant(_) => { + Err( + VMError::Runtime( + format!("Undefined variable '{}'.", ident).into(), + self.pc, + ) + ) + }, + }? + }, } } @@ -303,7 +326,7 @@ mod tests { } #[test] - fn read_write_globals() -> Result<(), VMError> { + fn define_read_globals() -> Result<(), VMError> { let var = GC::new_string("global"); use Op::*; let chunk = Chunk::new_with( @@ -326,4 +349,32 @@ mod tests { Ok(()) } + + #[test] + fn define_write_read_globals() -> Result<(), VMError> { + let var = GC::new_string("global"); + use Op::*; + let chunk = Chunk::new_with( + vec![ + Constant { offset: 0 }, + DefineGlobal { offset: 1 }, + GetGlobal { offset: 1 }, + Constant { offset: 2 }, + Add, + SetGlobal { offset: 1 }, + Pop, + GetGlobal { offset: 1 }, + ], + vec![1; 7], + vec![Value::from(5.0), Value::from(var.get_object()), Value::from(6.0)], + LinkedList::new() + ); + + let mut vm = VM::new(); + vm.stdrun(&chunk)?; + + assert_eq!(vm.stack, vec![Value::Number(11.0)]); + + Ok(()) + } }