implement chapter 11 in rust

This commit is contained in:
Sebastian Hugentobler 2025-05-25 10:52:20 +02:00
parent 8860a1c639
commit a25b6d1e92
Signed by: shu
SSH key fingerprint: SHA256:ppcx6MlixdNZd5EUM1nkHOKoyQYoJwzuQKXM6J/t66M
16 changed files with 470 additions and 32 deletions

9
lox/fibonacci.lox Normal file
View file

@ -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;
}

View file

@ -0,0 +1,4 @@
fun bad() {
var a = "first";
var a = "second";
}

10
lox/scoping.lox Normal file
View file

@ -0,0 +1,10 @@
var a = "global";
{
fun showA() {
print a;
}
showA();
var a = "block";
showA();
}

1
lox/top_level_return.lox Normal file
View file

@ -0,0 +1 @@
return "at top level";

25
rust/rox/Cargo.lock generated
View file

@ -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",

View file

@ -8,6 +8,7 @@ authors = ["Sebastian Hugentobler <shu@vanwa.ch>"]
[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"] }

View file

@ -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<Value, EnvironmentError> {
@ -64,4 +78,29 @@ impl Environment {
))
}
}
pub fn get_at(&self, distance: usize, token: &Token) -> Result<Value, EnvironmentError> {
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<Environment, EnvironmentError> {
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)
}
}

View file

@ -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,

View file

@ -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<RefCell<Environment>>,
environment: Rc<RefCell<Environment>>,
locals: HashMap<Expression, usize>,
}
/// 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<Option<Value>, 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<Value, InterpreterError> {
let value = self.evaluate(value)?;
fn assign(&mut self, name: &Token, expression: &Expression) -> Result<Value, InterpreterError> {
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,12 +360,31 @@ impl Interpreter {
}
/// Get the value of a variable.
fn var_expression(&mut self, name: &Token) -> Result<Value, InterpreterError> {
fn var_expression(
&mut self,
name: &Token,
expression: &Expression,
) -> Result<Value, InterpreterError> {
self.lookup_var(name, expression)
}
fn lookup_var(
&mut self,
name: &Token,
expression: &Expression,
) -> Result<Value, InterpreterError> {
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.
fn number_op(&self, left: f64, op: TokenType, right: f64) -> f64 {
@ -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))
}

View file

@ -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) {
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);
match parser::ast(tokens) {
Ok(ast) => match interpreter.run(ast) {
Ok(_) => {}
Err(e) => error!("{e}"),
},
Err(e) => error!("{e}"),
}
let mut resolver = Resolver::new(interpreter);
let ast = parser::ast(tokens)?;
resolver.resolve(&ast)?;
interpreter.run(ast)?;
Ok(())
}

View file

@ -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())))
}
}

273
rust/rox/src/resolver.rs Normal file
View file

@ -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<HashMap<String, bool>>,
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<Expression>,
) -> 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<Statement>,
) -> 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(())
}
}

View file

@ -64,6 +64,8 @@ pub fn tokenize(source: &str) -> Vec<Token> {
#[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 {

View file

@ -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<f64>),
/// 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,

View file

@ -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) => {

View file

@ -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<Value>),
Callable(Rc<dyn Callable>),
String(String),
Number(f64),
Number(OrderedFloat<f64>),
Boolean(bool),
Nil,
}