[rlox] Implement Assignment (for global variables)
This commit is contained in:
@@ -26,6 +26,7 @@ pub enum Op {
|
||||
|
||||
DefineGlobal { offset: u8 },
|
||||
GetGlobal { offset: u8 },
|
||||
SetGlobal { offset: u8 },
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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::<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(())
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user