feat(tazjin/rlox): Support local variables
WIP Change-Id: I78fbc885faaac165c380cbd9aa98b4b64a9b8274 Reviewed-on: https://cl.tvl.fyi/c/depot/+/3685 Tested-by: BuildkiteCI Reviewed-by: tazjin <mail@tazj.in>
This commit is contained in:
parent
ad7e591c80
commit
c318f42c11
6 changed files with 193 additions and 12 deletions
|
@ -1,8 +1,8 @@
|
||||||
{
|
var a = 10;
|
||||||
|
var b = 5;
|
||||||
|
|
||||||
{
|
{
|
||||||
var a = 5;
|
var b = 10;
|
||||||
print a;
|
var c = 2;
|
||||||
}
|
a * b * c;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,12 +8,47 @@ use crate::scanner::{self, Token, TokenKind};
|
||||||
#[cfg(feature = "disassemble")]
|
#[cfg(feature = "disassemble")]
|
||||||
use super::chunk;
|
use super::chunk;
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
enum Depth {
|
||||||
|
Unitialised,
|
||||||
|
At(usize),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Depth {
|
||||||
|
fn above(&self, theirs: usize) -> bool {
|
||||||
|
match self {
|
||||||
|
Depth::Unitialised => false,
|
||||||
|
Depth::At(ours) => *ours > theirs,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn below(&self, theirs: usize) -> bool {
|
||||||
|
match self {
|
||||||
|
Depth::Unitialised => false,
|
||||||
|
Depth::At(ours) => *ours < theirs,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
struct Local {
|
||||||
|
name: Token,
|
||||||
|
depth: Depth,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Default)]
|
||||||
|
struct Locals {
|
||||||
|
locals: Vec<Local>,
|
||||||
|
scope_depth: usize,
|
||||||
|
}
|
||||||
|
|
||||||
struct Compiler<T: Iterator<Item = Token>> {
|
struct Compiler<T: Iterator<Item = Token>> {
|
||||||
tokens: T,
|
tokens: T,
|
||||||
chunk: Chunk,
|
chunk: Chunk,
|
||||||
panic: bool,
|
panic: bool,
|
||||||
errors: Vec<Error>,
|
errors: Vec<Error>,
|
||||||
strings: Interner,
|
strings: Interner,
|
||||||
|
locals: Locals,
|
||||||
|
|
||||||
current: Option<Token>,
|
current: Option<Token>,
|
||||||
previous: Option<Token>,
|
previous: Option<Token>,
|
||||||
|
@ -200,7 +235,14 @@ impl<T: Iterator<Item = Token>> Compiler<T> {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn define_variable(&mut self, var: usize) -> LoxResult<()> {
|
fn define_variable(&mut self, var: usize) -> LoxResult<()> {
|
||||||
self.emit_op(OpCode::OpDefineGlobal(var));
|
if self.locals.scope_depth == 0 {
|
||||||
|
self.emit_op(OpCode::OpDefineGlobal(var));
|
||||||
|
} else {
|
||||||
|
self.locals.locals.last_mut()
|
||||||
|
.expect("fatal: variable not yet added at definition")
|
||||||
|
.depth = Depth::At(self.locals.scope_depth);
|
||||||
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -221,6 +263,11 @@ impl<T: Iterator<Item = Token>> Compiler<T> {
|
||||||
fn statement(&mut self) -> LoxResult<()> {
|
fn statement(&mut self) -> LoxResult<()> {
|
||||||
if self.match_token(&TokenKind::Print) {
|
if self.match_token(&TokenKind::Print) {
|
||||||
self.print_statement()
|
self.print_statement()
|
||||||
|
} else if self.match_token(&TokenKind::LeftBrace) {
|
||||||
|
self.begin_scope();
|
||||||
|
self.block()?;
|
||||||
|
self.end_scope();
|
||||||
|
Ok(())
|
||||||
} else {
|
} else {
|
||||||
self.expression_statement()
|
self.expression_statement()
|
||||||
}
|
}
|
||||||
|
@ -233,6 +280,37 @@ impl<T: Iterator<Item = Token>> Compiler<T> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn begin_scope(&mut self) {
|
||||||
|
self.locals.scope_depth += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn end_scope(&mut self) {
|
||||||
|
debug_assert!(self.locals.scope_depth > 0, "tried to end global scope");
|
||||||
|
self.locals.scope_depth -= 1;
|
||||||
|
|
||||||
|
while self.locals.locals.len() > 0
|
||||||
|
&& self.locals.locals[self.locals.locals.len() - 1].depth.above(self.locals.scope_depth)
|
||||||
|
{
|
||||||
|
self.emit_op(OpCode::OpPop);
|
||||||
|
self.locals.locals.remove(self.locals.locals.len() - 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn block(&mut self) -> LoxResult<()> {
|
||||||
|
while !self.check(&TokenKind::RightBrace)
|
||||||
|
&& !self.check(&TokenKind::Eof)
|
||||||
|
{
|
||||||
|
self.declaration()?;
|
||||||
|
}
|
||||||
|
|
||||||
|
consume!(
|
||||||
|
self,
|
||||||
|
TokenKind::RightBrace,
|
||||||
|
ErrorKind::ExpectedToken("Expected '}' after block.")
|
||||||
|
);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
fn expression_statement(&mut self) -> LoxResult<()> {
|
fn expression_statement(&mut self) -> LoxResult<()> {
|
||||||
self.expression()?;
|
self.expression()?;
|
||||||
self.expect_semicolon("expect ';' after expression")?;
|
self.expect_semicolon("expect ';' after expression")?;
|
||||||
|
@ -341,15 +419,29 @@ impl<T: Iterator<Item = Token>> Compiler<T> {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn named_variable(&mut self) -> LoxResult<()> {
|
fn named_variable(&mut self) -> LoxResult<()> {
|
||||||
let ident = self.identifier_str(Self::previous)?;
|
let local_idx = self.resolve_local();
|
||||||
let constant_id =
|
|
||||||
self.emit_constant(Value::String(ident.into()), false);
|
let ident = if local_idx.is_some() {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
Some(self.identifier_constant()?)
|
||||||
|
};
|
||||||
|
|
||||||
if self.match_token(&TokenKind::Equal) {
|
if self.match_token(&TokenKind::Equal) {
|
||||||
self.expression()?;
|
self.expression()?;
|
||||||
self.emit_op(OpCode::OpSetGlobal(constant_id));
|
match local_idx {
|
||||||
|
Some(idx) => self.emit_op(OpCode::OpSetLocal(idx)),
|
||||||
|
None => {
|
||||||
|
self.emit_op(OpCode::OpSetGlobal(ident.unwrap()));
|
||||||
|
}
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
self.emit_op(OpCode::OpGetGlobal(constant_id));
|
match local_idx {
|
||||||
|
Some(idx) => self.emit_op(OpCode::OpGetLocal(idx)),
|
||||||
|
None => {
|
||||||
|
self.emit_op(OpCode::OpGetGlobal(ident.unwrap()))
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
@ -401,6 +493,59 @@ impl<T: Iterator<Item = Token>> Compiler<T> {
|
||||||
Ok(self.strings.intern(ident))
|
Ok(self.strings.intern(ident))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn identifier_constant(&mut self) -> LoxResult<usize> {
|
||||||
|
let ident = self.identifier_str(Self::previous)?;
|
||||||
|
Ok(self.emit_constant(Value::String(ident.into()), false))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn resolve_local(&mut self) -> Option<usize> {
|
||||||
|
dbg!(&self.locals);
|
||||||
|
for (idx, local) in self.locals.locals.iter().enumerate().rev() {
|
||||||
|
if self.previous().lexeme == local.name.lexeme {
|
||||||
|
if let Depth::Unitialised = local.depth {
|
||||||
|
// TODO(tazjin): *return* err
|
||||||
|
panic!("can't read variable in its own initialiser");
|
||||||
|
}
|
||||||
|
return Some(idx);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
fn add_local(&mut self, name: Token) -> LoxResult<()> {
|
||||||
|
let local = Local {
|
||||||
|
name,
|
||||||
|
depth: Depth::Unitialised,
|
||||||
|
};
|
||||||
|
|
||||||
|
self.locals.locals.push(local);
|
||||||
|
Ok(()) // TODO(tazjin): needed?
|
||||||
|
}
|
||||||
|
|
||||||
|
fn declare_variable(&mut self) -> LoxResult<()> {
|
||||||
|
if self.locals.scope_depth == 0 {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
let name = self.previous().clone();
|
||||||
|
|
||||||
|
for local in self.locals.locals.iter().rev() {
|
||||||
|
if local.depth.below(self.locals.scope_depth) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if name.lexeme == local.name.lexeme {
|
||||||
|
return Err(Error {
|
||||||
|
kind: ErrorKind::VariableShadowed(name.lexeme.into()),
|
||||||
|
line: name.line,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self.add_local(name)
|
||||||
|
}
|
||||||
|
|
||||||
fn parse_variable(&mut self) -> LoxResult<usize> {
|
fn parse_variable(&mut self) -> LoxResult<usize> {
|
||||||
consume!(
|
consume!(
|
||||||
self,
|
self,
|
||||||
|
@ -408,6 +553,11 @@ impl<T: Iterator<Item = Token>> Compiler<T> {
|
||||||
ErrorKind::ExpectedToken("expected identifier")
|
ErrorKind::ExpectedToken("expected identifier")
|
||||||
);
|
);
|
||||||
|
|
||||||
|
self.declare_variable()?;
|
||||||
|
if self.locals.scope_depth > 0 {
|
||||||
|
return Ok(0); // TODO(tazjin): grr sentinel
|
||||||
|
}
|
||||||
|
|
||||||
let id = self.identifier_str(Self::previous)?;
|
let id = self.identifier_str(Self::previous)?;
|
||||||
Ok(self.emit_constant(Value::String(id.into()), false))
|
Ok(self.emit_constant(Value::String(id.into()), false))
|
||||||
}
|
}
|
||||||
|
@ -520,6 +670,7 @@ pub fn compile(code: &str) -> Result<(Interner, Chunk), Vec<Error>> {
|
||||||
panic: false,
|
panic: false,
|
||||||
errors: vec![],
|
errors: vec![],
|
||||||
strings: Interner::with_capacity(1024),
|
strings: Interner::with_capacity(1024),
|
||||||
|
locals: Default::default(),
|
||||||
current: None,
|
current: None,
|
||||||
previous: None,
|
previous: None,
|
||||||
};
|
};
|
||||||
|
|
|
@ -9,6 +9,7 @@ pub enum ErrorKind {
|
||||||
ExpectedToken(&'static str),
|
ExpectedToken(&'static str),
|
||||||
InternalError(&'static str),
|
InternalError(&'static str),
|
||||||
TypeError(String),
|
TypeError(String),
|
||||||
|
VariableShadowed(String),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
|
|
|
@ -34,4 +34,6 @@ pub enum OpCode {
|
||||||
OpDefineGlobal(usize),
|
OpDefineGlobal(usize),
|
||||||
OpGetGlobal(usize),
|
OpGetGlobal(usize),
|
||||||
OpSetGlobal(usize),
|
OpSetGlobal(usize),
|
||||||
|
OpGetLocal(usize),
|
||||||
|
OpSetLocal(usize),
|
||||||
}
|
}
|
||||||
|
|
|
@ -128,6 +128,23 @@ fn global_assignment() {
|
||||||
breakfast = "beignets with " + beverage;
|
breakfast = "beignets with " + beverage;
|
||||||
breakfast;
|
breakfast;
|
||||||
"#,
|
"#,
|
||||||
"beignets with cafe au lait"
|
"beignets with cafe au lait",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn local_variables() {
|
||||||
|
expect_num(
|
||||||
|
r#"
|
||||||
|
var a = 10;
|
||||||
|
var b = 5;
|
||||||
|
|
||||||
|
{
|
||||||
|
var b = 10;
|
||||||
|
var c = 2;
|
||||||
|
a * b * c;
|
||||||
|
}
|
||||||
|
"#,
|
||||||
|
200.0,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -194,6 +194,16 @@ impl VM {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
OpCode::OpGetLocal(local_idx) => {
|
||||||
|
let value = self.stack[*local_idx].clone();
|
||||||
|
self.push(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
OpCode::OpSetLocal(local_idx) => {
|
||||||
|
debug_assert!(self.stack.len() > *local_idx, "stack is not currently large enough for local");
|
||||||
|
self.stack[*local_idx] = self.stack.last().unwrap().clone();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "disassemble")]
|
#[cfg(feature = "disassemble")]
|
||||||
|
|
Loading…
Reference in a new issue