about summary refs log tree commit diff
diff options
context:
space:
mode:
authorVincent Ambo <mail@tazj.in>2021-03-05T15·48+0200
committertazjin <mail@tazj.in>2021-03-06T11·52+0000
commit29b2a547055ba1adaf3f0d79055b7d7657eb3a5e (patch)
treee417b41322e4ba655484daa2a5f0b986381d1a3e
parentb7b94335cc3b2d5650a385e1f4a439a7ef6d30ff (diff)
feat(tazjin/rlox): Implement global variable definition r/2273
identifier_str might look a bit overengineered, but we want to reuse
this bit of code and it needs a reference to the token from which to
pick the identifier.

The problem with this is that the token would be owned by self, but
the function needs to mutate (the interner), so this implementation is
the most straightforward way of acquiring and working with an
immutable reference to the token before interning the identifier.

Change-Id: I618ce8f789cb59b3a9c5b79a13111ea6d00b2424
Reviewed-on: https://cl.tvl.fyi/c/depot/+/2592
Reviewed-by: tazjin <mail@tazj.in>
Tested-by: BuildkiteCI
-rw-r--r--users/tazjin/rlox/src/bytecode/compiler.rs74
-rw-r--r--users/tazjin/rlox/src/bytecode/interner/mod.rs2
-rw-r--r--users/tazjin/rlox/src/bytecode/opcode.rs3
-rw-r--r--users/tazjin/rlox/src/bytecode/value.rs2
-rw-r--r--users/tazjin/rlox/src/bytecode/vm.rs14
5 files changed, 80 insertions, 15 deletions
diff --git a/users/tazjin/rlox/src/bytecode/compiler.rs b/users/tazjin/rlox/src/bytecode/compiler.rs
index f2fdeb0e9f..f993e35557 100644
--- a/users/tazjin/rlox/src/bytecode/compiler.rs
+++ b/users/tazjin/rlox/src/bytecode/compiler.rs
@@ -1,6 +1,6 @@
 use super::chunk::Chunk;
 use super::errors::{Error, ErrorKind, LoxResult};
-use super::interner::Interner;
+use super::interner::{InternedStr, Interner};
 use super::opcode::OpCode;
 use super::value::Value;
 use crate::scanner::{self, Token, TokenKind};
@@ -182,8 +182,30 @@ impl<T: Iterator<Item = Token>> Compiler<T> {
         self.parse_precedence(Precedence::Assignment)
     }
 
+    fn var_declaration(&mut self) -> LoxResult<()> {
+        let global = self.parse_variable()?;
+
+        if self.match_token(&TokenKind::Equal) {
+            self.expression()?;
+        } else {
+            self.emit_op(OpCode::OpNil);
+        }
+
+        self.expect_semicolon("expect ';' after variable declaration")?;
+        self.define_variable(global)
+    }
+
+    fn define_variable(&mut self, var: usize) -> LoxResult<()> {
+        self.emit_op(OpCode::OpDefineGlobal(var));
+        Ok(())
+    }
+
     fn declaration(&mut self) -> LoxResult<()> {
-        self.statement()?;
+        if self.match_token(&TokenKind::Var) {
+            self.var_declaration()?;
+        } else {
+            self.statement()?;
+        }
 
         if self.panic {
             self.synchronise();
@@ -202,22 +224,14 @@ impl<T: Iterator<Item = Token>> Compiler<T> {
 
     fn print_statement(&mut self) -> LoxResult<()> {
         self.expression()?;
-        consume!(
-            self,
-            TokenKind::Semicolon,
-            ErrorKind::ExpectedToken("Expected ';' after value")
-        );
+        self.expect_semicolon("expect ';' after print statement")?;
         self.emit_op(OpCode::OpPrint);
         Ok(())
     }
 
     fn expression_statement(&mut self) -> LoxResult<()> {
         self.expression()?;
-        consume!(
-            self,
-            TokenKind::Semicolon,
-            ErrorKind::ExpectedToken("Expected ';' after expression")
-        );
+        self.expect_semicolon("expect ';' after expression")?;
         self.emit_op(OpCode::OpPop);
         Ok(())
     }
@@ -346,6 +360,34 @@ impl<T: Iterator<Item = Token>> Compiler<T> {
         Ok(())
     }
 
+    fn identifier_str(
+        &mut self,
+        token_fn: fn(&Self) -> &Token,
+    ) -> LoxResult<InternedStr> {
+        let ident = match &token_fn(self).kind {
+            TokenKind::Identifier(ident) => ident.to_string(),
+            _ => {
+                return Err(Error {
+                    line: self.current().line,
+                    kind: ErrorKind::ExpectedToken("Expected identifier"),
+                })
+            }
+        };
+
+        Ok(self.strings.intern(ident))
+    }
+
+    fn parse_variable(&mut self) -> LoxResult<usize> {
+        consume!(
+            self,
+            TokenKind::Identifier(_),
+            ErrorKind::ExpectedToken("expected identifier")
+        );
+
+        let id = self.identifier_str(Self::previous)?;
+        Ok(self.emit_constant(Value::String(id.into())))
+    }
+
     fn current_chunk(&mut self) -> &mut Chunk {
         &mut self.chunk
     }
@@ -367,9 +409,10 @@ impl<T: Iterator<Item = Token>> Compiler<T> {
         self.current_chunk().add_op(op, line);
     }
 
-    fn emit_constant(&mut self, val: Value) {
+    fn emit_constant(&mut self, val: Value) -> usize {
         let idx = self.chunk.add_constant(val);
         self.emit_op(OpCode::OpConstant(idx));
+        idx
     }
 
     fn previous(&self) -> &Token {
@@ -430,6 +473,11 @@ impl<T: Iterator<Item = Token>> Compiler<T> {
             }
         }
     }
+
+    fn expect_semicolon(&mut self, msg: &'static str) -> LoxResult<()> {
+        consume!(self, TokenKind::Semicolon, ErrorKind::ExpectedToken(msg));
+        Ok(())
+    }
 }
 
 pub fn compile(code: &str) -> Result<(Interner, Chunk), Vec<Error>> {
diff --git a/users/tazjin/rlox/src/bytecode/interner/mod.rs b/users/tazjin/rlox/src/bytecode/interner/mod.rs
index f5f695904e..1da1a24b2c 100644
--- a/users/tazjin/rlox/src/bytecode/interner/mod.rs
+++ b/users/tazjin/rlox/src/bytecode/interner/mod.rs
@@ -11,7 +11,7 @@ use std::collections::HashMap;
 #[cfg(test)]
 mod tests;
 
-#[derive(Clone, Copy, Debug, PartialEq, Hash)]
+#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
 pub struct InternedStr {
     id: usize,
 }
diff --git a/users/tazjin/rlox/src/bytecode/opcode.rs b/users/tazjin/rlox/src/bytecode/opcode.rs
index 37466b3056..25ce03c3c1 100644
--- a/users/tazjin/rlox/src/bytecode/opcode.rs
+++ b/users/tazjin/rlox/src/bytecode/opcode.rs
@@ -29,4 +29,7 @@ pub enum OpCode {
     // Built in operations
     OpPrint,
     OpPop,
+
+    // Variable definitions
+    OpDefineGlobal(usize),
 }
diff --git a/users/tazjin/rlox/src/bytecode/value.rs b/users/tazjin/rlox/src/bytecode/value.rs
index 19a4dcc92e..4170efadf8 100644
--- a/users/tazjin/rlox/src/bytecode/value.rs
+++ b/users/tazjin/rlox/src/bytecode/value.rs
@@ -8,7 +8,7 @@ pub enum Value {
     String(LoxString),
 }
 
-#[derive(Clone, Debug, PartialEq)]
+#[derive(Clone, Debug, PartialEq, Eq, Hash)]
 pub enum LoxString {
     Heap(String),
     Interned(InternedStr),
diff --git a/users/tazjin/rlox/src/bytecode/vm.rs b/users/tazjin/rlox/src/bytecode/vm.rs
index 9161bdfe53..53431a0837 100644
--- a/users/tazjin/rlox/src/bytecode/vm.rs
+++ b/users/tazjin/rlox/src/bytecode/vm.rs
@@ -1,3 +1,5 @@
+use std::collections::HashMap;
+
 use super::chunk;
 use super::errors::*;
 use super::interner::Interner;
@@ -14,6 +16,8 @@ pub struct VM {
     stack: Vec<Value>,
     strings: Interner,
 
+    globals: HashMap<LoxString, Value>,
+
     // Operations that consume values from the stack without pushing
     // anything leave their last value in this slot, which makes it
     // possible to return values from interpreters that ran code which
@@ -157,6 +161,15 @@ impl VM {
                 OpCode::OpPop => {
                     self.last_drop = Some(self.pop());
                 }
+
+                OpCode::OpDefineGlobal(name_idx) => {
+                    let name = self.chunk.constant(*name_idx);
+                    with_type!(self, name, Value::String(name), {
+                        let name = name.clone();
+                        let val = self.pop();
+                        self.globals.insert(name, val);
+                    });
+                }
             }
 
             #[cfg(feature = "disassemble")]
@@ -197,6 +210,7 @@ pub fn interpret(strings: Interner, chunk: chunk::Chunk) -> LoxResult<Value> {
     let mut vm = VM {
         chunk,
         strings,
+        globals: HashMap::new(),
         ip: 0,
         stack: vec![],
         last_drop: None,