Skip to content

Commit

Permalink
Merge pull request #20 from froth/global-variables
Browse files Browse the repository at this point in the history
Implement global variables
  • Loading branch information
froth authored Dec 18, 2024
2 parents 69d0563 + d6e362e commit 8b565ab
Show file tree
Hide file tree
Showing 12 changed files with 213 additions and 20 deletions.
1 change: 0 additions & 1 deletion src/args.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ use std::path::PathBuf;
#[derive(Debug, Parser)]
#[command(version, about)]
pub struct Args {
///
#[arg()]
pub file: Option<String>,

Expand Down
10 changes: 3 additions & 7 deletions src/chunk.rs
Original file line number Diff line number Diff line change
Expand Up @@ -92,14 +92,10 @@ impl Chunk {
}

match op {
Op::Constant(const_index) => {
let const_index: usize = (*const_index).into();
Op::Constant(idx) | Op::DefineGlobal(idx) | Op::GetGlobal(idx) | Op::SetGlobal(idx) => {
let const_index: usize = (*idx).into();
let constant = self.constants[const_index];
write!(
&mut result,
"{:<16} {:<4} '{}'",
"CONSTANT", const_index, constant
)?;
write!(&mut result, "{:<16} {:<4} '{}'", op, const_index, constant)?;
}
op => write!(&mut result, "{op}")?,
}
Expand Down
76 changes: 69 additions & 7 deletions src/compiler.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use miette::{ByteOffset, Diagnostic, Error, LabeledSpan, NamedSource, Report, Result, SourceSpan};
use miette::{ByteOffset, Diagnostic, LabeledSpan, NamedSource, Report, Result, SourceSpan};
use tracing::debug;

use crate::{
Expand All @@ -8,6 +8,7 @@ use crate::{
match_token,
op::Op,
scanner::Scanner,
source_span_extensions::SourceSpanExtensions,
token::{Precedence, Token, TokenType},
types::value::Value,
};
Expand Down Expand Up @@ -79,17 +80,56 @@ impl<'a, 'gc> Compiler<'a, 'gc> {
}

fn declaration(&mut self) -> Result<()> {
let res = self.statement();
let res = if let Some(var_token) = match_token!(self.scanner, TokenType::Var)? {
self.var_declaration(var_token.location)
} else {
self.statement()
};

if let Err(err) = res {
self.errors.push(err);
self.synchronize();
}
Ok(())
}

fn var_declaration(&mut self, location: SourceSpan) -> Result<()> {
let global = self.parse_variable()?;
if (match_token!(self.scanner, TokenType::Equal)?).is_some() {
self.expression()?;
} else {
self.chunk.write(Op::Nil, location);
}
let semicolon_location = consume!(self, TokenType::Semicolon, "Expected ';' after value");
self.define_variable(global, location.until(semicolon_location));
Ok(())
}

fn define_variable(&mut self, const_idx: u8, location: SourceSpan) {
self.chunk.write(Op::DefineGlobal(const_idx), location);
}

fn parse_variable(&mut self) -> Result<u8> {
let next = self.scanner.advance()?;
if let TokenType::Identifier(id) = next.token_type {
Ok(self.identifier_constant(id))
} else {
miette::bail!(
labels = vec![LabeledSpan::at(next.location, "here")],
"Expected variable name but got `{}`",
next.token_type
)
}
}

fn identifier_constant(&mut self, name: &str) -> u8 {
self.chunk
.add_constant(Value::Obj(self.gc.manage_str(name)))
}

fn statement(&mut self) -> Result<()> {
if let Some(print) = match_token!(self.scanner, TokenType::Print) {
self.print_statement(print?.location)
if let Some(print) = match_token!(self.scanner, TokenType::Print)? {
self.print_statement(print.location)
} else {
self.expression_statement()
}
Expand Down Expand Up @@ -117,8 +157,9 @@ impl<'a, 'gc> Compiler<'a, 'gc> {
// parse everything at the given precedence or higher
fn parse_precedence(&mut self, precedence: Precedence) -> Result<()> {
let token = self.scanner.advance()?;
let can_assign = precedence <= Precedence::Assignment;
if token.token_type.is_prefix() {
self.prefix(token)?
self.prefix(token, can_assign)?
} else {
miette::bail!(
labels = vec![LabeledSpan::at(token.location, "here")],
Expand All @@ -132,6 +173,15 @@ impl<'a, 'gc> Compiler<'a, 'gc> {
self.infix(token)?;
}

if can_assign {
if let Some(equal) = match_token!(self.scanner, TokenType::Equal)? {
miette::bail!(
labels = vec![LabeledSpan::at(equal.location, "here")],
"Invalid assignment target",
)
}
}

Ok(())
}

Expand Down Expand Up @@ -161,7 +211,18 @@ impl<'a, 'gc> Compiler<'a, 'gc> {
self.chunk.write(Op::Constant(idx), location);
}

fn prefix(&mut self, token: Token) -> Result<()> {
fn named_variable(&mut self, name: &str, can_assign: bool, location: SourceSpan) -> Result<()> {
let arg = self.identifier_constant(name);
if can_assign && match_token!(self.scanner, TokenType::Equal)?.is_some() {
self.expression()?;
self.chunk.write(Op::SetGlobal(arg), location);
} else {
self.chunk.write(Op::GetGlobal(arg), location);
}
Ok(())
}

fn prefix(&mut self, token: Token, can_assign: bool) -> Result<()> {
match token.token_type {
TokenType::LeftParen => self.grouping()?,
TokenType::Minus => self.unary(Op::Negate, token.location)?,
Expand All @@ -174,7 +235,8 @@ impl<'a, 'gc> Compiler<'a, 'gc> {
let obj = self.gc.manage_str(s);
self.emit_constant(Value::Obj(obj), token.location)
}
_ => unreachable!(), // guarded by is_prefix
TokenType::Identifier(name) => self.named_variable(name, can_assign, token.location)?,
_ => unreachable!(), // guarded by is_prefix TODO: benchmark unreachable_unsafe
}
Ok(())
}
Expand Down
1 change: 1 addition & 0 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ mod gc;
mod op;
mod printer;
mod scanner;
mod source_span_extensions;
mod token;
mod types;
mod vm;
Expand Down
3 changes: 3 additions & 0 deletions src/op.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,7 @@ pub enum Op {
Less,
Print,
Pop,
DefineGlobal(u8),
GetGlobal(u8),
SetGlobal(u8),
}
8 changes: 4 additions & 4 deletions src/scanner.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,12 +41,12 @@ macro_rules! consume {
macro_rules! match_token {
($self:expr, $pattern:pat $(if $guard:expr)?) => {{
match $self.peek() {
Some(Err(_)) => $self.next(),
Some(Err(_)) => $self.next().transpose(),
Some(Ok(a)) => match a.token_type {
$pattern $(if $guard)? => $self.next(),
_ => None
$pattern $(if $guard)? => $self.next().transpose(),
_ => Ok(None)
},
None => None,
None => Ok(None),
}
}};
}
Expand Down
49 changes: 49 additions & 0 deletions src/source_span_extensions.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
use std::cmp::max;

use miette::SourceSpan;

pub trait SourceSpanExtensions {
fn until(&self, right: Self) -> Self;
}

impl SourceSpanExtensions for SourceSpan {
fn until(&self, right: Self) -> Self {
assert!(self.offset() <= right.offset());
let len = max(right.offset() - self.offset() + right.len(), self.len());
(self.offset(), len).into()
}
}

#[cfg(test)]
mod source_span_extension_tests {
use miette::SourceSpan;

use super::SourceSpanExtensions;

#[test]
fn non_overlapping() {
let left: SourceSpan = (1, 1).into();
let right: SourceSpan = (3, 2).into();
let combined = left.until(right);
assert_eq!(combined.offset(), 1);
assert_eq!(combined.len(), 4);
}

#[test]
fn overlapping() {
let left: SourceSpan = (1, 10).into();
let right: SourceSpan = (8, 2).into();
let combined = left.until(right);
assert_eq!(combined.offset(), 1);
assert_eq!(combined.len(), 10);
}

#[test]
fn strang() {
let left: SourceSpan = (6, 1).into();
let right: SourceSpan = (10, 1).into();
let combined = left.until(right);
assert_eq!(combined.offset(), 6);
assert_eq!(combined.len(), 5);
}
}
2 changes: 1 addition & 1 deletion src/token.rs
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ impl TokenType<'_> {
use TokenType::*;
matches!(
self,
LeftParen | Minus | Bang | Number(_) | Nil | True | False | String(_)
LeftParen | Minus | Bang | Number(_) | Nil | True | False | String(_) | Identifier(_)
)
}

Expand Down
33 changes: 33 additions & 0 deletions src/vm.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ use tracing::debug;
use crate::{
chunk::Chunk,
compiler::Compiler,
datastructures::hash_table::HashTable,
error::InterpreterError,
gc::Gc,
op::Op,
Expand All @@ -19,6 +20,7 @@ pub struct VM {
stack: Box<[Value; STACK_SIZE]>,
stack_top: *mut Value,
gc: Gc,
globals: HashTable,
printer: Box<dyn Printer>,
}

Expand All @@ -44,10 +46,12 @@ impl VM {
let mut stack = Box::new([Value::Nil; STACK_SIZE]);
let stack_top = stack.as_mut_ptr();
let gc = Gc::new();
let globals = HashTable::new();
Self {
stack,
stack_top,
gc,
globals,
printer: Box::new(ConsolePrinter),
}
}
Expand Down Expand Up @@ -119,6 +123,35 @@ impl VM {
Op::Pop => {
self.pop();
}
Op::DefineGlobal(index) => {
let name = chunk.constants[*index as usize];
self.globals.insert(name, self.peek(0));
self.pop();
}
Op::GetGlobal(index) => {
let name = chunk.constants[*index as usize];
if let Some(v) = self.globals.get(name) {
self.push(v)
} else {
miette::bail!(
labels = vec![LabeledSpan::at(chunk.locations[i], "here")],
"Undefined variable {}",
name
)
}
}
Op::SetGlobal(index) => {
let name = chunk.constants[*index as usize];
let inserted = self.globals.insert(name, self.peek(0));
if inserted {
self.globals.delete(name);
miette::bail!(
labels = vec![LabeledSpan::at(chunk.locations[i], "here")],
"Undefined variable {}",
name
)
}
}
}
}
Ok(())
Expand Down
31 changes: 31 additions & 0 deletions tests/invalid_assignment_target.lox
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
error
a * b = c + d;
----
----
{
"causes": [],
"filename": "",
"labels": [],
"message": "Parser Error",
"related": [
{
"causes": [],
"filename": "tests/invalid_assignment_target.lox",
"labels": [
{
"label": "here",
"span": {
"length": 1,
"offset": 6
}
}
],
"message": "Invalid assignment target",
"related": [],
"severity": "error"
}
],
"severity": "error"
}
----
---- (no newline)
8 changes: 8 additions & 0 deletions tests/variable_assignment.lox
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
interpret
var breakfast = "beignets";
var beverage = "cafe au lait";
breakfast = "beignets with " + beverage;

print breakfast;
----
beignets with cafe au lait
11 changes: 11 additions & 0 deletions tests/variable_definition.lox
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
interpret
var a = 5;
var b = "123123";
var c;
print a;
print b;
print c;
----
5
123123
Nil

0 comments on commit 8b565ab

Please sign in to comment.