diff --git a/lox/fibonacci.lox b/lox/fibonacci.lox new file mode 100644 index 0000000..630e3b2 --- /dev/null +++ b/lox/fibonacci.lox @@ -0,0 +1,9 @@ +var a = 0; +var temp; + +var b = 1; +for(var b = 1; a < 1000; b = temp + b) { + print a; + temp = a; + a = b; +} diff --git a/lox/name_redefinition.lox b/lox/name_redefinition.lox new file mode 100644 index 0000000..d9948fd --- /dev/null +++ b/lox/name_redefinition.lox @@ -0,0 +1,4 @@ +fun bad() { + var a = "first"; + var a = "second"; +} diff --git a/lox/scoping.lox b/lox/scoping.lox new file mode 100644 index 0000000..f826c86 --- /dev/null +++ b/lox/scoping.lox @@ -0,0 +1,10 @@ +var a = "global"; +{ + fun showA() { + print a; + } + + showA(); + var a = "block"; + showA(); +} diff --git a/lox/top_level_return.lox b/lox/top_level_return.lox new file mode 100644 index 0000000..9dbd1b6 --- /dev/null +++ b/lox/top_level_return.lox @@ -0,0 +1 @@ +return "at top level"; diff --git a/rust/rox/Cargo.lock b/rust/rox/Cargo.lock index 169f10a..347e205 100644 --- a/rust/rox/Cargo.lock +++ b/rust/rox/Cargo.lock @@ -61,6 +61,12 @@ dependencies = [ "windows-sys", ] +[[package]] +name = "autocfg" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" + [[package]] name = "cfg-if" version = "1.0.0" @@ -162,12 +168,30 @@ dependencies = [ "winapi", ] +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + [[package]] name = "once_cell" version = "1.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" +[[package]] +name = "ordered-float" +version = "5.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2c1f9f56e534ac6a9b8a4600bdf0f530fb393b5f393e7b4d03489c3cf0c3f01" +dependencies = [ + "num-traits", +] + [[package]] name = "overload" version = "0.1.1" @@ -248,6 +272,7 @@ version = "0.1.0" dependencies = [ "clap", "lazy_static", + "ordered-float", "thiserror", "tracing", "tracing-subscriber", diff --git a/rust/rox/Cargo.toml b/rust/rox/Cargo.toml index 9b1332a..8a5507d 100644 --- a/rust/rox/Cargo.toml +++ b/rust/rox/Cargo.toml @@ -8,6 +8,7 @@ authors = ["Sebastian Hugentobler "] [dependencies] clap = { version = "4.5.28", features = ["derive"] } lazy_static = "1.5.0" +ordered-float = "5.0.0" thiserror = "2.0.11" tracing = "0.1.41" tracing-subscriber = { version = "0.3.19", features = ["env-filter"] } diff --git a/rust/rox/src/environment.rs b/rust/rox/src/environment.rs index 5f5966b..d73e75e 100644 --- a/rust/rox/src/environment.rs +++ b/rust/rox/src/environment.rs @@ -8,6 +8,8 @@ use crate::{token::Token, value::Value}; pub enum EnvironmentError { #[error("line {0}: undefined variable: {1}")] UndefinedVariable(usize, String), + #[error("invalid environment distance")] + InvalidDistance, } /// Environment mapping variable names to their respective values. Can have an optional enclosing @@ -50,6 +52,18 @@ impl Environment { } } + pub fn assign_at( + &mut self, + distance: usize, + token: &Token, + value: Value, + ) -> Result<(), EnvironmentError> { + self.ancestor(distance)? + .values + .insert(token.lexeme.clone(), value); + Ok(()) + } + /// Get the value of an existing (defined) variable. Error if there is no such variable in /// this environment or any of the enclosing ones. pub fn get(&self, token: &Token) -> Result { @@ -64,4 +78,29 @@ impl Environment { )) } } + + pub fn get_at(&self, distance: usize, token: &Token) -> Result { + if let Some(v) = self.ancestor(distance)?.values.get(token.lexeme.as_str()) { + Ok(v.clone()) + } else { + Err(EnvironmentError::UndefinedVariable( + token.line, + token.lexeme.clone(), + )) + } + } + + fn ancestor(&self, distance: usize) -> Result { + let mut environment = self.clone(); + + for _ in 0..distance { + let enclosing = match &environment.enclosing { + Some(enc) => enc.borrow().clone(), + None => return Err(EnvironmentError::InvalidDistance), + }; + environment = enclosing; + } + + Ok(environment) + } } diff --git a/rust/rox/src/expression.rs b/rust/rox/src/expression.rs index defaba6..bdc1ae6 100644 --- a/rust/rox/src/expression.rs +++ b/rust/rox/src/expression.rs @@ -1,7 +1,7 @@ use crate::token::{self, Token}; /// Enumeration of all types of expressions. -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Clone, PartialEq, Eq, Hash)] pub enum Expression { Assign { name: Token, diff --git a/rust/rox/src/interpreter.rs b/rust/rox/src/interpreter.rs index 239d043..30fd264 100644 --- a/rust/rox/src/interpreter.rs +++ b/rust/rox/src/interpreter.rs @@ -1,5 +1,6 @@ -use std::{cell::RefCell, rc::Rc}; +use std::{cell::RefCell, collections::HashMap, rc::Rc}; +use ordered_float::OrderedFloat; use thiserror::Error; use tracing::error; @@ -39,6 +40,7 @@ pub enum InterpreterError { pub struct Interpreter { pub globals: Rc>, environment: Rc>, + locals: HashMap, } /// Default configuration for the interpreter, with builtin native functions. @@ -57,6 +59,7 @@ impl Default for Interpreter { Self { globals: env.clone(), environment: env.clone(), + locals: HashMap::new(), } } } @@ -74,6 +77,10 @@ impl Interpreter { Ok(()) } + pub fn resolve(&mut self, expression: Expression, depth: usize) { + self.locals.insert(expression, depth); + } + ///Execute a statement. fn execute(&mut self, statement: &Statement) -> Result, InterpreterError> { match statement { @@ -145,7 +152,7 @@ impl Interpreter { operator, right, } => self.binary(left, operator, right), - Expression::Variable { name } => self.var_expression(name), + Expression::Variable { name } => self.var_expression(name, expression), Expression::Assign { name, value } => self.assign(name, value), Expression::Logical { left, @@ -276,13 +283,26 @@ impl Interpreter { } /// Assign the value of an expression to a variable. - fn assign(&mut self, name: &Token, value: &Expression) -> Result { - let value = self.evaluate(value)?; + fn assign(&mut self, name: &Token, expression: &Expression) -> Result { + let value = self.evaluate(expression)?; self.environment .borrow_mut() .assign(name, value.clone()) .map_err(InterpreterError::UndefinedVariable)?; + + if let Some(distance) = self.locals.get(expression) { + self.environment + .borrow_mut() + .assign_at(*distance, name, value.clone()) + .map_err(InterpreterError::UndefinedVariable)?; + } else { + self.globals + .borrow_mut() + .assign(name, value.clone()) + .map_err(InterpreterError::UndefinedVariable)?; + } + Ok(value) } @@ -340,11 +360,30 @@ impl Interpreter { } /// Get the value of a variable. - fn var_expression(&mut self, name: &Token) -> Result { - self.environment - .borrow() - .get(name) - .map_err(InterpreterError::UndefinedVariable) + fn var_expression( + &mut self, + name: &Token, + expression: &Expression, + ) -> Result { + self.lookup_var(name, expression) + } + + fn lookup_var( + &mut self, + name: &Token, + expression: &Expression, + ) -> Result { + if let Some(distance) = self.locals.get(expression) { + self.environment + .borrow() + .get_at(*distance, name) + .map_err(InterpreterError::UndefinedVariable) + } else { + self.globals + .borrow() + .get(name) + .map_err(InterpreterError::UndefinedVariable) + } } /// Calculate number operations. @@ -383,7 +422,11 @@ impl Interpreter { match op.token_type { TokenType::Minus | TokenType::Slash | TokenType::Star | TokenType::Plus => { if let (Value::Number(left), Value::Number(right)) = (left.clone(), right.clone()) { - Ok(Value::Number(self.number_op(left, op.token_type, right))) + Ok(Value::Number(OrderedFloat(self.number_op( + *left, + op.token_type, + *right, + )))) } else if let (Value::String(left), Value::String(right)) = (left, right) { Ok(Value::String(format!("{}{}", left.clone(), right.clone()))) } else { @@ -397,7 +440,11 @@ impl Interpreter { | TokenType::Less | TokenType::LessEqual => { if let (Value::Number(left), Value::Number(right)) = (left, right) { - Ok(Value::Boolean(self.boolean_op(left, op.token_type, right))) + Ok(Value::Boolean(self.boolean_op( + *left, + op.token_type, + *right, + ))) } else { Err(InterpreterError::BinaryExpressionNeedsNumber(op.line)) } diff --git a/rust/rox/src/lib.rs b/rust/rox/src/lib.rs index 81757f9..065fbeb 100644 --- a/rust/rox/src/lib.rs +++ b/rust/rox/src/lib.rs @@ -7,7 +7,10 @@ use std::{ path::Path, }; -use interpreter::Interpreter; +use interpreter::{Interpreter, InterpreterError}; +use parser::ParserError; +use resolver::{Resolver, ResolverError}; +use thiserror::Error; use tracing::error; pub mod callable; @@ -19,6 +22,7 @@ pub mod interpreter; pub mod keywords; pub mod native_functions; pub mod parser; +pub mod resolver; pub mod scanner; pub mod statement; pub mod token; @@ -35,6 +39,16 @@ pub mod tokenizer { } pub mod value; +#[derive(Error, Debug)] +pub enum RoxError { + #[error("parser failed: {0}")] + ParseError(#[from] ParserError), + #[error("resolver failed: {0}")] + ResolverError(#[from] ResolverError), + #[error("interpreter failed: {0}")] + InterpreterError(#[from] InterpreterError), +} + /// Read the source code in a file and scan it to tokens. pub fn compile(source: &Path) -> Result<(), io::Error> { let input = fs::read_to_string(source)?; @@ -64,12 +78,18 @@ pub fn repl() { /// Evaluate a Lox input string and print errors or output. fn run(input: &str, interpreter: &mut Interpreter) { - let tokens = scanner::tokenize(input); - match parser::ast(tokens) { - Ok(ast) => match interpreter.run(ast) { - Ok(_) => {} - Err(e) => error!("{e}"), - }, - Err(e) => error!("{e}"), + if let Err(e) = run_rox(input, interpreter) { + error!("{e}"); } } + +fn run_rox(input: &str, interpreter: &mut Interpreter) -> Result<(), RoxError> { + let tokens = scanner::tokenize(input); + let mut resolver = Resolver::new(interpreter); + + let ast = parser::ast(tokens)?; + resolver.resolve(&ast)?; + interpreter.run(ast)?; + + Ok(()) +} diff --git a/rust/rox/src/native_functions.rs b/rust/rox/src/native_functions.rs index 1268b41..0e6cd08 100644 --- a/rust/rox/src/native_functions.rs +++ b/rust/rox/src/native_functions.rs @@ -3,6 +3,8 @@ use std::{ time::{SystemTime, UNIX_EPOCH}, }; +use ordered_float::OrderedFloat; + use crate::{ callable::{Callable, CallingError}, interpreter::Interpreter, @@ -30,7 +32,7 @@ impl Callable for Clock { .duration_since(UNIX_EPOCH) .map_err(|e| CallingError::CallFailed(e.to_string()))?; - Ok(Value::Number(since_the_epoch.as_secs_f64())) + Ok(Value::Number(OrderedFloat(since_the_epoch.as_secs_f64()))) } } diff --git a/rust/rox/src/resolver.rs b/rust/rox/src/resolver.rs new file mode 100644 index 0000000..7ca71c1 --- /dev/null +++ b/rust/rox/src/resolver.rs @@ -0,0 +1,273 @@ +use crate::{expression::Expression, interpreter::Interpreter, statement::Statement, token::Token}; +use std::collections::HashMap; +use thiserror::Error; + +#[derive(Error, Debug)] +pub enum ResolverError { + #[error("line {0}: can not read local variable in its own initializer.")] + ReadVarInOwnInitializer(usize), + #[error("line {0}: already a variable with this name in this scope.")] + NameRedefinition(usize), + #[error("line {0}: can not return from top-level code.")] + TopLevelReturn(usize), +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +enum FunctionType { + None, + Function, +} + +#[derive(Debug)] +pub struct Resolver<'a> { + scopes: Vec>, + interpreter: &'a mut Interpreter, + current_fun: FunctionType, +} + +impl<'a> Resolver<'a> { + pub fn new(interpreter: &'a mut Interpreter) -> Self { + Self { + scopes: Vec::default(), + interpreter, + current_fun: FunctionType::None, + } + } + + pub fn resolve(&mut self, statements: &[Statement]) -> Result<(), ResolverError> { + for stm in statements { + self.resolve_statement(stm)?; + } + Ok(()) + } + + fn resolve_statement(&mut self, statement: &Statement) -> Result<(), ResolverError> { + match statement { + Statement::Block(statements) => self.resolve_block(statements), + Statement::Print(expression) => self.resolve_expression(expression), + Statement::Expression(expression) => self.resolve_expression(expression), + Statement::Var { name, initializer } => self.resolve_var(name, *initializer.clone()), + Statement::If { + condition, + then_branch, + else_branch, + } => self.resolve_if_stmt( + condition, + then_branch, + else_branch.as_ref().map(|boxed| *boxed.clone()), + ), + Statement::While { condition, body } => self.resolve_while_stmt(condition, body), + Statement::Function { name, params, body } => { + self.resolve_function_stmt(name, params, body) + } + Statement::Return { keyword, value } => { + if self.current_fun == FunctionType::None { + return Err(ResolverError::TopLevelReturn(keyword.line)); + } + if let Some(value) = value { + self.resolve_expression(value) + } else { + Ok(()) + } + } + } + } + + fn resolve_expression(&mut self, expr: &Expression) -> Result<(), ResolverError> { + match expr { + Expression::Assign { name, value } => self.resolve_assign_expr(expr, name, value), + Expression::Binary { + left, + operator: _, + right, + } => self.resolve_binary_expr(left, right), + Expression::Call { + callee, + paren: _, + args, + } => self.resolve_call_expr(callee, args), + Expression::Grouping { expression } => self.resolve_expression(expression), + Expression::Literal { value: _ } => Ok(()), + Expression::Logical { + left, + operator: _, + right, + } => self.resolve_binary_expr(left, right), + Expression::Unary { operator: _, right } => self.resolve_expression(right), + Expression::Variable { name } => self.resolve_var_expr(expr, name), + } + } + + fn begin_scope(&mut self) { + self.scopes.push(HashMap::new()); + } + + fn end_scope(&mut self) { + self.scopes.pop(); + } + + fn declare(&mut self, name: &Token) -> Result<(), ResolverError> { + if let Some(scope) = self.scopes.last_mut() { + if scope.contains_key(&name.lexeme) { + return Err(ResolverError::NameRedefinition(name.line)); + } + + scope.insert(name.lexeme.clone(), false); + } + + Ok(()) + } + + fn define(&mut self, name: &Token) { + if let Some(scope) = self.scopes.last_mut() { + scope.insert(name.lexeme.clone(), true); + } + } + + fn resolve_block(&mut self, statements: &[Statement]) -> Result<(), ResolverError> { + self.begin_scope(); + self.resolve(statements)?; + self.end_scope(); + + Ok(()) + } + + fn resolve_var( + &mut self, + name: &Token, + initializer: Option, + ) -> Result<(), ResolverError> { + self.declare(name)?; + + if let Some(initializer) = initializer { + self.resolve_expression(&initializer)?; + } + + self.define(name); + Ok(()) + } + + fn resolve_var_expr(&mut self, expr: &Expression, name: &Token) -> Result<(), ResolverError> { + if let Some(scope) = self.scopes.last() { + if !scope.get(&name.lexeme).unwrap_or(&true) { + return Err(ResolverError::ReadVarInOwnInitializer(name.line)); + } + } + self.resolve_local(expr, name)?; + Ok(()) + } + + fn resolve_assign_expr( + &mut self, + expr: &Expression, + name: &Token, + val: &Expression, + ) -> Result<(), ResolverError> { + self.resolve_expression(val)?; + self.resolve_local(expr, name)?; + Ok(()) + } + + fn resolve_binary_expr( + &mut self, + left: &Expression, + right: &Expression, + ) -> Result<(), ResolverError> { + self.resolve_expression(left)?; + self.resolve_expression(right)?; + Ok(()) + } + + fn resolve_function_stmt( + &mut self, + name: &Token, + params: &[Token], + body: &[Statement], + ) -> Result<(), ResolverError> { + self.declare(name)?; + self.define(name); + self.resolve_function(params, body, FunctionType::Function)?; + + Ok(()) + } + + fn resolve_call_expr( + &mut self, + callee: &Expression, + args: &[Expression], + ) -> Result<(), ResolverError> { + self.resolve_expression(callee)?; + + for arg in args { + self.resolve_expression(arg)?; + } + + Ok(()) + } + + fn resolve_if_stmt( + &mut self, + condition: &Expression, + then_branch: &Statement, + else_branch: Option, + ) -> Result<(), ResolverError> { + self.resolve_expression(condition)?; + self.resolve_statement(then_branch)?; + + if let Some(stmt) = else_branch { + self.resolve_statement(&stmt)?; + } + + Ok(()) + } + + fn resolve_while_stmt( + &mut self, + condition: &Expression, + body: &Statement, + ) -> Result<(), ResolverError> { + self.resolve_expression(condition)?; + self.resolve_statement(body)?; + + Ok(()) + } + + fn resolve_function( + &mut self, + params: &[Token], + body: &[Statement], + fun_type: FunctionType, + ) -> Result<(), ResolverError> { + let enclosing_fun = self.current_fun; + self.current_fun = fun_type; + + self.begin_scope(); + + for param in params { + self.declare(param)?; + self.define(param); + } + self.resolve(body)?; + + self.end_scope(); + + self.current_fun = enclosing_fun; + + Ok(()) + } + + fn resolve_local( + &mut self, + expression: &Expression, + name: &Token, + ) -> Result<(), ResolverError> { + for (idx, scope) in self.scopes.iter().rev().enumerate() { + if scope.contains_key(&name.lexeme) { + self.interpreter.resolve(expression.clone(), idx); + return Ok(()); + } + } + + Ok(()) + } +} diff --git a/rust/rox/src/scanner.rs b/rust/rox/src/scanner.rs index 01bd42c..bc56202 100644 --- a/rust/rox/src/scanner.rs +++ b/rust/rox/src/scanner.rs @@ -64,6 +64,8 @@ pub fn tokenize(source: &str) -> Vec { #[cfg(test)] mod tests { + use ordered_float::OrderedFloat; + use crate::token::Literal; use crate::token::Token; use crate::token::TokenType::*; @@ -88,13 +90,13 @@ mod tests { vec![Token { token_type: Number, lexeme: "0".into(), - literal: Some(Literal::Number(0.0)), + literal: Some(Literal::Number(OrderedFloat(0.0))), line: 1 },], vec![Token { token_type: Number, lexeme: "0".into(), - literal: Some(Literal::Number(0.0)), + literal: Some(Literal::Number(OrderedFloat(0.0))), line: 1 },] ); @@ -153,7 +155,7 @@ mod tests { Token { token_type: Number, lexeme: "0".into(), - literal: Some(Literal::Number(0.0)), + literal: Some(Literal::Number(OrderedFloat(0.0))), line: 3 }, Token { @@ -213,7 +215,7 @@ mod tests { Token { token_type: Number, lexeme: "1".into(), - literal: Some(Literal::Number(1.0)), + literal: Some(Literal::Number(OrderedFloat(1.0))), line: 6 }, Token { @@ -237,7 +239,7 @@ mod tests { Token { token_type: Number, lexeme: "10000".into(), - literal: Some(Literal::Number(10000.0)), + literal: Some(Literal::Number(OrderedFloat(10000.0))), line: 6 }, Token { diff --git a/rust/rox/src/token.rs b/rust/rox/src/token.rs index 47b8cf8..7b5e7e6 100644 --- a/rust/rox/src/token.rs +++ b/rust/rox/src/token.rs @@ -2,8 +2,10 @@ use std::fmt::Display; +use ordered_float::OrderedFloat; + /// Exhaustive enumeration of all types of different tokens. -#[derive(Copy, Clone, Debug, PartialEq, Eq)] +#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] pub enum TokenType { // Single-character tokens. LeftParen, @@ -55,12 +57,12 @@ pub enum TokenType { } /// Literal value. -#[derive(Clone, Debug, PartialEq)] +#[derive(Clone, Debug, PartialEq, Eq, Hash)] pub enum Literal { /// String literal. String(String), // Number literal, represented as f64 (thus it can be decimal). - Number(f64), + Number(OrderedFloat), /// Boolean literal. Boolean(bool), /// Null literal. @@ -68,7 +70,7 @@ pub enum Literal { } /// Consumed token. -#[derive(Clone, Debug, PartialEq)] +#[derive(Clone, Debug, PartialEq, Eq, Hash)] pub struct Token { /// Type of the token. pub token_type: TokenType, diff --git a/rust/rox/src/tokenizer/number.rs b/rust/rox/src/tokenizer/number.rs index a437bad..17a8b19 100644 --- a/rust/rox/src/tokenizer/number.rs +++ b/rust/rox/src/tokenizer/number.rs @@ -1,4 +1,5 @@ use crate::token::{Literal, Token, TokenType}; +use ordered_float::OrderedFloat; use std::{iter::Peekable, str::CharIndices}; use tracing::error; @@ -29,7 +30,7 @@ impl Tokenizer for Number { Ok(literal) => Some(Token { token_type: TokenType::Number, lexeme, - literal: Some(Literal::Number(literal)), + literal: Some(Literal::Number(OrderedFloat(literal))), line, }), Err(e) => { diff --git a/rust/rox/src/value.rs b/rust/rox/src/value.rs index b272014..d344c4b 100644 --- a/rust/rox/src/value.rs +++ b/rust/rox/src/value.rs @@ -1,5 +1,7 @@ use std::{fmt::Display, rc::Rc}; +use ordered_float::OrderedFloat; + use crate::{callable::Callable, token::Literal}; /// Concrete value in the interpreter. @@ -8,7 +10,7 @@ pub enum Value { Return(Box), Callable(Rc), String(String), - Number(f64), + Number(OrderedFloat), Boolean(bool), Nil, }