Add block scoping of variables
parent
11df867c06
commit
6f7e2eb499
1
build.rs
1
build.rs
|
@ -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>"),
|
||||||
]);
|
]);
|
||||||
|
|
23
src/eval.rs
23
src/eval.rs
|
@ -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(
|
||||||
|
|
|
@ -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"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
16
src/parse.rs
16
src/parse.rs
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue