[rlox] Implement Assignment (for global variables)

This commit is contained in:
ctsk
2024-11-28 09:04:09 +01:00
parent 59d5984e3d
commit 86626ebd23
3 changed files with 88 additions and 9 deletions

View File

@@ -26,6 +26,7 @@ pub enum Op {
DefineGlobal { offset: u8 },
GetGlobal { offset: u8 },
SetGlobal { offset: u8 },
}
#[derive(Clone, Debug, PartialEq)]

View File

@@ -468,7 +468,14 @@ impl<'src> Parser<'src> {
}
TokenType::Identifier => {
let offset = self.add_string(chunk, token.span);
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);
}
}

View File

@@ -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;
@@ -206,9 +206,32 @@ impl VM {
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::<ObjString>().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(())
}
}