diff --git a/src/environment.rs b/src/environment.rs --- a/src/environment.rs +++ b/src/environment.rs @@ -30,17 +30,31 @@ Self(Rc::new(RefCell::new(inner))) } - /// Define or re-define a new variable in the current environment pub fn define(&self, name: String, value: Value) { self.0.borrow_mut().define(name, value); } - /// Assign a new value to an existing variable in the current environment or a parent #[must_use = "you should check the assignment was successful"] - pub fn assign(&self, name: String, new_val: Value) -> bool { - self.0.borrow_mut().assign(name, new_val) + pub fn assign_at_depth(&self, name: String, value: Value, depth: usize) -> bool { + self.0.borrow_mut().assign_at_depth(name, value, depth) } + #[must_use = "you should check the assignment was successful"] + pub fn assign_global(&self, name: String, value: Value) -> bool { + self.0.borrow_mut().assign_global(name, value) + } + + pub fn get_at_depth(&self, name: &str, depth: usize) -> Option { + self.0.borrow().get_at_depth(name, depth) + } + + pub fn get_global(&self, name: &str) -> Option { + self.0.borrow().get_global(name) + } +} + +#[cfg(test)] +impl Env { /// Get the value of a variable in the current environment or a parent pub fn get(&self, name: &str) -> Option { self.0.borrow().get(name) @@ -68,31 +82,53 @@ } } - /// Define or re-define a new variable in the current environment - pub fn define(&mut self, name: String, value: Value) { + fn define(&mut self, name: String, value: Value) { self.values.insert(name, value); } - /// Assign a new value to an existing variable the current environment or a parent - pub fn assign(&mut self, name: String, value: Value) -> bool { - match self.values.get_mut(&name) { - Some(entry) => { - *entry = value; - true - } - None => match self.parent.as_ref() { - Some(parent) => parent.assign(name, value), - None => false, - }, + fn assign(&mut self, name: String, value: Value) -> bool { + self.values + .get_mut(&name) + .map(|entry| *entry = value) + .is_some() + } + + fn assign_at_depth(&mut self, name: String, value: Value, depth: usize) -> bool { + if depth == 0 { + self.assign(name, value) + } else { + self.parent + .as_ref() + .expect("depth exceeded") + .assign_at_depth(name, value, depth - 1) } } - /// Get the value of a variable in the current environment or a parent - pub fn get(&self, name: &str) -> Option { - self.values - .get(name) - .cloned() - .or_else(|| self.parent.as_ref().and_then(|parent| parent.get(name))) + fn assign_global(&mut self, name: String, value: Value) -> bool { + self.parent + .as_ref() + .map(|parent| parent.assign_global(name.clone(), value.clone())) + .unwrap_or_else(|| self.assign(name, value)) + } + + fn get(&self, name: &str) -> Option { + self.values.get(name).cloned() + } + + fn get_at_depth(&self, name: &str, depth: usize) -> Option { + (depth == 0).then(|| self.get(name)).flatten().or_else(|| { + self.parent + .as_ref() + .expect("depth exceeded") + .get_at_depth(name, depth - 1) + }) + } + + fn get_global(&self, name: &str) -> Option { + self.parent + .as_ref() + .and_then(|parent| parent.get_global(name)) + .or_else(|| self.get(name)) } } @@ -125,46 +161,72 @@ } #[test] - fn reassigns_value() { - let e = Env::default(); - - e.define("foo".to_string(), Value::Number(1.0)); - assert!(e.assign("foo".to_string(), Value::Bool(true))); - - assert_some_eq!(e.get("foo"), Value::Bool(true)); - } - - #[test] - fn gets_value_from_parent() { + fn assigns_at_depth() { let parent = Env::default(); let child = parent.new_child(); - parent.define("foo".to_string(), Value::Number(1.0)); - - assert_some_eq!(child.get("foo"), Value::Number(1.0)); + parent.define("foo".to_string(), Value::String("definition".to_string())); + assert!(child.assign_at_depth( + "foo".to_string(), + Value::String("assignment".to_string()), + 1 + )); + assert!(!child.assign_at_depth("foo".to_string(), Value::String("invalid".to_string()), 0)); + assert_some_eq!(parent.get("foo"), Value::String("assignment".to_string())); } #[test] - fn shadows_in_child() { - let parent = Env::default(); - let child = parent.new_child(); + fn assigns_global() { + let global = Env::default(); + let child = global.new_child(); - parent.define("foo".to_string(), Value::Number(1.0)); - child.define("foo".to_string(), Value::Bool(true)); + global.define( + "foo".to_string(), + Value::String("global definition".to_string()), + ); + child.define( + "foo".to_string(), + Value::String("child definition".to_string()), + ); - assert_some_eq!(parent.get("foo"), Value::Number(1.0)); - assert_some_eq!(child.get("foo"), Value::Bool(true)); + assert!(child.assign_global("foo".to_string(), Value::String("assignment".to_string()))); + assert_some_eq!(global.get("foo"), Value::String("assignment".to_string())); } #[test] - fn assigns_in_parent_from_child() { - let parent = Env::default(); + fn gets_value_at_depth() { + let grandparent = Env::default(); + let parent = grandparent.new_child(); let child = parent.new_child(); - parent.define("foo".to_string(), Value::Number(1.0)); - assert!(child.assign("foo".to_string(), Value::Bool(true))); + grandparent.define("foo".to_string(), Value::String("depth 2".to_string())); + parent.define("foo".to_string(), Value::String("depth 1".to_string())); + child.define("foo".to_string(), Value::String("depth 0".to_string())); - assert_some_eq!(parent.get("foo"), Value::Bool(true)); - assert_some_eq!(child.get("foo"), Value::Bool(true)); + assert_some_eq!( + child.get_at_depth("foo", 2), + Value::String("depth 2".to_string()) + ); + assert_some_eq!( + child.get_at_depth("foo", 1), + Value::String("depth 1".to_string()) + ); + assert_some_eq!( + child.get_at_depth("foo", 0), + Value::String("depth 0".to_string()) + ); + } + + #[test] + fn gets_global() { + let grandparent = Env::default(); + let parent = grandparent.new_child(); + let child = parent.new_child(); + + grandparent.define("foo".to_string(), Value::String("global".to_string())); + parent.define("foo".to_string(), Value::String("parent".to_string())); + child.define("foo".to_string(), Value::String("child".to_string())); + + assert_some_eq!(child.get_global("foo"), Value::String("global".to_string())); } } diff --git a/src/interpreter.rs b/src/interpreter.rs --- a/src/interpreter.rs +++ b/src/interpreter.rs @@ -2,10 +2,10 @@ use miette::SourceSpan; -use crate::callable::LoxFunction; use crate::{ ast::{Ast, BinaryOp, Expr, LogicalOp, Lval, Stmt, UnaryOp}, callable::Callable, + callable::LoxFunction, environment::Env, span::{Span, Spanned}, value::Value, @@ -137,22 +137,39 @@ Ok(Value::from(lit.as_ref())) } Expr::Unary { op, expr } => self.interpret_unary(op, expr, env), - Expr::Var { name } => env - .get(name) - .ok_or_else(|| RuntimeError::undefined_var(name)), + Expr::Var { name } => { + name.depth() + .and_then(|depth| env.get_at_depth(name.as_ref().as_ref(), depth)) + .or_else(|| env.get_global(name.as_ref().as_ref())) + .ok_or_else(|| RuntimeError::undefined_var(name.as_ref())) + /* + let state = name.state(); + let name = name.as_ref(); + match state { + ResolutionState::Global => env.get_global(name.as_ref()), + ResolutionState::Resolved(depth) => env.get_at_depth(name.as_ref(), depth), + } + .ok_or_else(|| RuntimeError::undefined_var(name)) + */ + } Expr::Assignment { target, value } => { let name = match target.as_ref() { - Lval::Ident { name } => name.clone(), + Lval::Ident { name } => name, }; let value = self.interpret_expr(value, env)?; - if env.assign(name.clone(), value.clone()) { - Ok(value) - } else { - Err(RuntimeError::undefined_var(&Spanned::new( - name, - target.span(), - ))) - } + + name.depth() + .map(|depth| { + env.assign_at_depth(name.as_ref().to_string(), value.clone(), depth) + }) + .unwrap_or_else(|| env.assign_global(name.as_ref().to_string(), value.clone())) + .then_some(value) + .ok_or_else(|| { + RuntimeError::undefined_var(&Spanned::new( + name.as_ref().to_string(), + target.span(), + )) + }) } Expr::Logical { left, op, right } => { let left_val = self.interpret_expr(left, env)?; @@ -424,6 +441,7 @@ #[cfg(test)] mod test { use super::*; + use crate::resolver::Resolver; use crate::{ parser::{parse, parse_expr}, scanner::scan, @@ -436,9 +454,11 @@ fn execute_stmts(input: &str) -> (Env, String) { let tokens = scan(input).unwrap_or_else(|e| panic!("input `{input}` should scan. error: {e:?}")); - let ast = + let mut ast = parse(tokens).unwrap_or_else(|e| panic!("input `{input}` should parse. error: {e:?}")); - + Resolver::default() + .resolve(&mut ast) + .unwrap_or_else(|e| panic!("input `{input}` should resolve. error: {e:?}")); let mut output = vec![0; TEST_OUTPUT_BUFFER_SIZE]; let mut i = Interpreter::with_output(Box::new(output.as_mut_slice())); diff --git a/src/lib.rs b/src/lib.rs --- a/src/lib.rs +++ b/src/lib.rs @@ -5,6 +5,7 @@ mod logging; mod match_token; mod parser; +mod resolver; mod runners; mod scanner; mod span; diff --git a/src/resolver.rs b/src/resolver.rs new file mode 100644 --- /dev/null +++ b/src/resolver.rs @@ -0,0 +1,238 @@ +use crate::ast::{Ast, Expr, Lval, Stmt}; +use crate::span::{Span, Spanned}; +use std::collections::HashMap; + +#[derive(Clone, Debug, Default)] +pub struct Resolver { + scopes: Vec, +} + +impl Resolver { + pub fn resolve(&mut self, ast: &mut Ast) -> Result<(), ResolverError> { + for stmt in &mut ast.0 { + self.resolve_stmt(stmt)?; + } + Ok(()) + } + + fn begin_scope(&mut self) { + self.scopes.push(Scope::default()) + } + + fn end_scope(&mut self) { + self.scopes.pop(); + } + + fn declare(&mut self, name: String) { + if let Some(scope) = self.scopes.last_mut() { + scope.declare(name) + } + } + + fn is_declared(&self, name: &str) -> bool { + if let Some(scope) = self.scopes.last() { + scope.is_declared(name) + } else { + // assume it's a global variable + true + } + } + + fn define(&mut self, name: String) { + if let Some(scope) = self.scopes.last_mut() { + scope.define(name); + } + } + + fn is_defined(&self, name: &str) -> bool { + if let Some(scope) = self.scopes.last() { + scope.is_defined(name) + } else { + // assume it's a global variable + true + } + } + + fn resolution_distance(&self, name: &str) -> Option { + self.scopes + .iter() + .rev() + .enumerate() + .find_map(|(i, scope)| scope.is_defined(name).then_some(i)) + } + + fn resolve_stmt(&mut self, stmt: &mut Spanned) -> Result<(), ResolverError> { + match stmt.as_mut() { + Stmt::Block(stmts) => { + self.begin_scope(); + for stmt in stmts { + self.resolve_stmt(stmt)?; + } + self.end_scope(); + } + Stmt::Function { name, params, body } => { + self.declare(name.clone()); + self.define(name.clone()); + + self.begin_scope(); + for param in params { + self.declare(param.clone()); + self.define(param.clone()); + } + for stmt in body { + self.resolve_stmt(stmt)?; + } + self.end_scope(); + } + Stmt::VarDecl { name, initializer } => { + self.declare(name.clone()); + if let Some(initializer) = initializer { + self.resolve_expr(initializer)? + } + self.define(name.clone()) + } + + Stmt::Expr(expr) => self.resolve_expr(expr)?, + Stmt::Print(expr) => self.resolve_expr(expr)?, + Stmt::If { + condition, + then, + otherwise, + } => { + self.resolve_expr(condition)?; + self.resolve_stmt(then)?; + if let Some(otherwise) = otherwise { + self.resolve_stmt(otherwise)?; + } + } + Stmt::While { condition, body } => { + self.resolve_expr(condition)?; + self.resolve_stmt(body)?; + } + Stmt::Return(expr) => { + if let Some(expr) = expr { + self.resolve_expr(expr)?; + } + } + } + + Ok(()) + } + + fn resolve_expr(&mut self, expr: &mut Spanned) -> Result<(), ResolverError> { + match expr.as_mut() { + Expr::Var { name } => { + if self.is_declared(name.as_ref()) && !self.is_defined(name.as_ref()) { + return Err(ResolverError::ReferencedVarInOwnInit { + name: name.as_ref().to_string(), + reference: name.as_ref().span(), + }); + } + + if let Some(distance) = self.resolution_distance(name.as_ref().as_ref()) { + name.resolve(distance); + } + } + Expr::Assignment { target, value } => { + self.resolve_expr(value)?; + match target.as_mut() { + Lval::Ident { name } => { + if let Some(distance) = self.resolution_distance(name.as_ref().as_ref()) { + name.resolve(distance) + } + } + } + } + + Expr::Call { callee, arguments } => { + self.resolve_expr(callee.as_mut())?; + for arg in arguments { + self.resolve_expr(arg)?; + } + } + Expr::Binary { left, right, .. } => { + self.resolve_expr(left)?; + self.resolve_expr(right)?; + } + Expr::Grouping { inner } => self.resolve_expr(inner)?, + Expr::Literal { .. } => {} // noop + Expr::Unary { expr, .. } => self.resolve_expr(expr)?, + Expr::Logical { left, right, .. } => { + self.resolve_expr(left)?; + self.resolve_expr(right)?; + } + } + + Ok(()) + } +} + +#[derive(Clone, Debug, Default)] +struct Scope(HashMap); + +impl Scope { + fn declare(&mut self, name: String) { + self.0.insert(name, false); + } + + fn is_declared(&self, name: &str) -> bool { + self.0.contains_key(name) + } + + fn define(&mut self, name: String) { + self.0.insert(name, true); + } + + fn is_defined(&self, name: &str) -> bool { + self.0.get(name).is_some_and(|is_defined| *is_defined) + } +} + +#[derive(Clone, Copy, Debug)] +pub struct MaybeResolved { + inner: T, + state: ResolutionState, +} + +#[derive(Clone, Copy, Debug)] +pub enum ResolutionState { + Global, + Resolved(usize), +} + +impl MaybeResolved { + pub fn global(inner: T) -> Self { + Self { + inner, + state: ResolutionState::Global, + } + } + + pub fn depth(&self) -> Option { + if let ResolutionState::Resolved(depth) = self.state { + Some(depth) + } else { + None + } + } + + fn resolve(&mut self, distance: usize) { + self.state = ResolutionState::Resolved(distance); + } +} + +impl AsRef for MaybeResolved { + fn as_ref(&self) -> &T { + &self.inner + } +} + +#[derive(Clone, Debug, thiserror::Error, miette::Diagnostic)] +pub enum ResolverError { + #[error("Cannot reference variable in its own initializer")] + ReferencedVarInOwnInit { + name: String, + #[label("`{name}` is being referenced here in its own declaration")] + reference: Span, + }, +} diff --git a/src/span.rs b/src/span.rs --- a/src/span.rs +++ b/src/span.rs @@ -43,6 +43,10 @@ &self.0 } + pub fn as_mut(&mut self) -> &mut T { + &mut self.0 + } + pub fn span(&self) -> Span { self.1 } diff --git a/src/ast/expr.rs b/src/ast/expr.rs --- a/src/ast/expr.rs +++ b/src/ast/expr.rs @@ -1,3 +1,4 @@ +use crate::resolver::MaybeResolved; use crate::{ span::{Span, Spanned}, token::{Token, TokenKind}, @@ -21,7 +22,7 @@ expr: Box>, }, Var { - name: Spanned, + name: MaybeResolved>, }, Assignment { target: Spanned, @@ -81,7 +82,9 @@ pub fn var(name: Spanned) -> Spanned { let span = name.span(); - let e = Self::Var { name }; + let e = Self::Var { + name: MaybeResolved::global(name), + }; Spanned::new(e, span) } @@ -170,7 +173,7 @@ pub fn unwrap_var(&self) -> &String { match self { - Expr::Var { name } => name, + Expr::Var { name } => name.as_ref(), other => panic!("expected var expression, got {:?}", other.as_str()), } } @@ -302,7 +305,9 @@ #[derive(Clone, Debug)] pub enum Lval { - Ident { name: String }, + Ident { + name: MaybeResolved>, + }, } impl Lval { @@ -310,9 +315,7 @@ let span = target.span(); match target.as_ref() { Expr::Var { name } => { - let lval = Lval::Ident { - name: name.as_ref().clone(), - }; + let lval = Lval::Ident { name: name.clone() }; Ok(Spanned::new(lval, span)) } _ => Err(target), diff --git a/src/runners/file.rs b/src/runners/file.rs --- a/src/runners/file.rs +++ b/src/runners/file.rs @@ -3,6 +3,7 @@ use miette::{IntoDiagnostic, Report, Result, WrapErr}; use crate::interpreter::interpret; +use crate::resolver::Resolver; use crate::{parser::parse, scanner::scan}; #[tracing::instrument(skip_all, fields(path = %path.display()))] @@ -15,8 +16,10 @@ tracing::debug!("scanning"); let tokens = scan(&contents).map_err(|e| Report::new(e).with_source_code(contents.clone()))?; - let ast = parse(tokens).map_err(|e| Report::new(e).with_source_code(contents.clone()))?; - + let mut ast = parse(tokens).map_err(|e| Report::new(e).with_source_code(contents.clone()))?; + Resolver::default() + .resolve(&mut ast) + .map_err(|e| Report::new(e).with_source_code(contents.clone()))?; interpret(&ast).map_err(|e| Report::new(e).with_source_code(contents.clone()))?; Ok(()) diff --git a/src/runners/repl.rs b/src/runners/repl.rs --- a/src/runners/repl.rs +++ b/src/runners/repl.rs @@ -2,6 +2,7 @@ use miette::{IntoDiagnostic, Report, Result, WrapErr}; +use crate::resolver::Resolver; use crate::{interpreter::Interpreter, parser::parse, scanner::scan}; #[tracing::instrument] @@ -19,6 +20,7 @@ }; let mut interpreter = Interpreter::new(); + let mut resolver = Resolver::default(); let stdin = std::io::stdin(); prompt()?; @@ -49,7 +51,7 @@ continue; } - let ast = match parse(tokens) { + let mut ast = match parse(tokens) { Ok(ast) => ast, Err(e) => { let report = Report::new(e).with_source_code(line.clone()); @@ -59,6 +61,14 @@ continue; } }; + + if let Err(e) = resolver.resolve(&mut ast) { + let report = Report::new(e).with_source_code(line.clone()); + eprintln!("{report:?}"); + + prompt()?; + continue; + } if let Err(e) = interpreter.interpret(&ast) { let report = Report::new(e).with_source_code(line.clone());