Add block scoping of variables

master
Nick Krichevsky 2024-05-22 14:10:29 -04:00
parent 11df867c06
commit 6f7e2eb499
4 changed files with 202 additions and 11 deletions

View File

@ -59,6 +59,7 @@ fn do_ast_codegen<W: Write>(mut output: W) {
let statement_types = BTreeMap::from([ let statement_types = BTreeMap::from([
("Expression", "expression: Expr"), ("Expression", "expression: Expr"),
("Block", "statements: Vec<Stmt>"),
("Print", "expression: Expr"), ("Print", "expression: Expr"),
("Var", "name: Token, initializer: Option<Expr>"), ("Var", "name: Token, initializer: Option<Expr>"),
]); ]);

View File

@ -1,8 +1,4 @@
use std::{ use std::fmt::{self, Display, Formatter};
fmt::{self, Display, Formatter},
rc::Rc,
string::ParseError,
};
use crate::{ use crate::{
ast::{Expr, ExprVisitor, LiteralValue, Stmt, StmtVisitor}, ast::{Expr, ExprVisitor, LiteralValue, Stmt, StmtVisitor},
@ -174,7 +170,7 @@ impl ExprVisitor<Result<EvaluatedValue, ScriptError>> for InterpreterRunner<'_>
fn visit_variable(&mut self, name: &Token) -> Result<EvaluatedValue, ScriptError> { fn visit_variable(&mut self, name: &Token) -> Result<EvaluatedValue, ScriptError> {
self.interpreter self.interpreter
.env .env
.get(name.lexeme()) .get_value(name.lexeme())
.ok_or_else(|| ScriptError { .ok_or_else(|| ScriptError {
message: format!("Undefined variable: {}", name.lexeme()), message: format!("Undefined variable: {}", name.lexeme()),
location: String::new(), location: String::new(),
@ -200,10 +196,23 @@ impl StmtVisitor<Result<(), ScriptError>> for InterpreterRunner<'_> {
.as_ref() .as_ref()
.map_or(Ok(EvaluatedValue::Nil), |expr| self.visit_expr(expr))?; .map_or(Ok(EvaluatedValue::Nil), |expr| self.visit_expr(expr))?;
self.interpreter.env.set(name.lexeme(), initialized_value); self.interpreter
.env
.set_value(name.lexeme(), initialized_value);
Ok(()) Ok(())
} }
fn visit_block(&mut self, statements: &Vec<Stmt>) -> Result<(), ScriptError> {
self.interpreter.env.enter_scope();
let result = statements
.iter()
.try_for_each(|statement| self.visit_stmt(statement));
self.interpreter.env.exit_scope();
result
}
} }
fn convert_arithmetic_operands( fn convert_arithmetic_operands(

View File

@ -1,22 +1,187 @@
use std::collections::HashMap; use std::collections::{BTreeMap, HashMap};
use super::value::EvaluatedValue; use super::value::EvaluatedValue;
#[derive(Debug, Default)] #[derive(Debug)]
pub struct Environment { pub struct Environment {
scopes: BTreeMap<u32, Scope>,
}
#[derive(Debug)]
struct Scope {
parent: Option<u32>,
values: HashMap<String, EvaluatedValue>, values: HashMap<String, EvaluatedValue>,
} }
impl Default for Environment {
fn default() -> Self {
let mut scopes = BTreeMap::new();
scopes.insert(Self::ROOT_KEY, Scope::new_global_scope());
Self { scopes }
}
}
impl Environment { impl Environment {
const ROOT_KEY: u32 = 0;
pub fn new() -> Self { pub fn new() -> Self {
Self::default() Self::default()
} }
pub fn get(&self, name: &str) -> Option<&EvaluatedValue> { #[must_use]
pub fn root_scope(&self) -> &Scope {
self.scopes
.get(&Self::ROOT_KEY)
.expect("no root environment defined")
}
#[must_use]
pub fn root_scope_mut(&mut self) -> &mut Scope {
self.scopes
.get_mut(&Self::ROOT_KEY)
.expect("no root environment defined")
}
pub fn enter_scope(&mut self) {
let parent_key = self.current_scope_key();
let child_key = self.next_scope_key();
let env = Scope::new_child_scope(parent_key);
let insert_result = self.scopes.insert(child_key, env);
assert!(
insert_result.is_none(),
"collision when inserting environment {child_key}"
);
}
pub fn exit_scope(&mut self) {
let to_remove_key = self.current_scope_key();
assert!(to_remove_key != Self::ROOT_KEY, "cannot leave global scope");
self.scopes.remove(&to_remove_key);
}
pub fn get_value(&self, name: &str) -> Option<&EvaluatedValue> {
let mut cursor = Some(self.current_scope());
while let Some(scope) = cursor {
let value = scope.get(name);
if value.is_some() {
return value;
}
cursor = scope.parent.and_then(|key| self.scopes.get(&key));
}
None
}
pub fn set_value(&mut self, name: &str, value: EvaluatedValue) {
self.current_scope_mut().set(name, value);
}
fn current_scope(&self) -> &Scope {
// this can't fail, given we know where these scope ids come from
self.scopes.get(&self.current_scope_key()).unwrap()
}
fn current_scope_mut(&mut self) -> &mut Scope {
// this can't fail, given we know where these scope ids come from
self.scopes.get_mut(&self.current_scope_key()).unwrap()
}
fn current_scope_key(&self) -> u32 {
self.scopes
.last_key_value()
.map(|(&k, _v)| k)
// this should *NEVER* happen
.expect("no root scope defined")
}
fn next_scope_key(&self) -> u32 {
let parent = self.current_scope_key();
parent.checked_add(1).expect("too many nested scopes")
}
}
impl Scope {
fn new_global_scope() -> Self {
Self {
values: HashMap::new(),
parent: None,
}
}
fn new_child_scope(parent: u32) -> Self {
Self {
values: HashMap::new(),
parent: Some(parent),
}
}
fn get(&self, name: &str) -> Option<&EvaluatedValue> {
self.values.get(name) self.values.get(name)
} }
pub fn set<S: Into<String>>(&mut self, name: S, value: EvaluatedValue) { fn set<S: Into<String>>(&mut self, name: S, value: EvaluatedValue) {
self.values.insert(name.into(), value); self.values.insert(name.into(), value);
} }
} }
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_can_get_value_from_root_scope() {
let mut global = Environment::new();
global.set_value("foo", EvaluatedValue::Number(42_f64));
assert_eq!(
Some(&EvaluatedValue::Number(42_f64)),
global.get_value(r"foo")
);
}
#[test]
fn test_can_get_value_from_parent_scope() {
let mut global = Environment::new();
global.set_value("foo", EvaluatedValue::Number(42_f64));
// Make two child scopes, just so they exist
global.enter_scope();
global.enter_scope();
assert_eq!(
Some(&EvaluatedValue::Number(42_f64)),
global.get_value(r"foo")
);
}
#[test]
fn test_insert_value_into_non_root_scope() {
let mut global = Environment::new();
global.enter_scope();
global.set_value("foo", EvaluatedValue::Number(42_f64));
global.enter_scope();
assert_eq!(
Some(&EvaluatedValue::Number(42_f64)),
global.get_value(r"foo")
);
}
#[test]
fn test_leaving_scope_removes_value() {
let mut global = Environment::new();
global.enter_scope();
global.set_value("foo", EvaluatedValue::Number(42_f64));
global.exit_scope();
assert_eq!(None, global.get_value(r"foo"));
}
}

View File

@ -1,5 +1,7 @@
use std::iter::Peekable; use std::iter::Peekable;
use itertools::Itertools;
use crate::{ use crate::{
ast::{Expr, LiteralValue, Stmt}, ast::{Expr, LiteralValue, Stmt},
lex::{Token, TokenKind}, lex::{Token, TokenKind},
@ -120,6 +122,8 @@ fn parse_var_declaration<I: Iterator<Item = Token>>(
fn parse_statement<I: Iterator<Item = Token>>(iter: &mut Peekable<I>) -> Result<Stmt, ParseError> { fn parse_statement<I: Iterator<Item = Token>>(iter: &mut Peekable<I>) -> Result<Stmt, ParseError> {
if match_token_kind!(iter, TokenKind::Print).is_ok() { if match_token_kind!(iter, TokenKind::Print).is_ok() {
parse_statement_containing_expression(iter, |expression| Stmt::Print { expression }) parse_statement_containing_expression(iter, |expression| Stmt::Print { expression })
} else if match_token_kind!(iter, TokenKind::LeftBrace).is_ok() {
parse_block_statement(iter)
} else { } else {
parse_statement_containing_expression(iter, |expression| Stmt::Expression { expression }) parse_statement_containing_expression(iter, |expression| Stmt::Expression { expression })
} }
@ -138,6 +142,18 @@ fn parse_statement_containing_expression<I: Iterator<Item = Token>, F: Fn(Expr)
}) })
} }
fn parse_block_statement<I: Iterator<Item = Token>>(
iter: &mut Peekable<I>,
) -> Result<Stmt, ParseError> {
let mut statements = vec![];
while match_token_kind!(iter, TokenKind::RightBrace).is_err() {
let statement = parse_declaration(iter)?;
statements.push(statement);
}
Ok(Stmt::Block { statements })
}
fn parse_expression<I: Iterator<Item = Token>>(iter: &mut Peekable<I>) -> Result<Expr, ParseError> { fn parse_expression<I: Iterator<Item = Token>>(iter: &mut Peekable<I>) -> Result<Expr, ParseError> {
parse_equality(iter) parse_equality(iter)
} }