From 621c97102a20bb49d59bffadd2367c3e3e6bdf83 Mon Sep 17 00:00:00 2001 From: Sebastian Hugentobler Date: Sun, 25 May 2025 16:04:00 +0200 Subject: [PATCH] implement chapter 12 in rust --- rust/rox/src/class.rs | 58 ++++++++++++++++++++ rust/rox/src/expression.rs | 12 +++++ rust/rox/src/function.rs | 22 ++++++-- rust/rox/src/instance.rs | 49 +++++++++++++++++ rust/rox/src/interpreter.rs | 72 ++++++++++++++++++++++++- rust/rox/src/lib.rs | 2 + rust/rox/src/parser.rs | 55 ++++++++++++++++++- rust/rox/src/resolver.rs | 103 ++++++++++++++++++++++++++++++++---- rust/rox/src/statement.rs | 4 ++ rust/rox/src/value.rs | 6 ++- 10 files changed, 366 insertions(+), 17 deletions(-) create mode 100644 rust/rox/src/class.rs create mode 100644 rust/rox/src/instance.rs diff --git a/rust/rox/src/class.rs b/rust/rox/src/class.rs new file mode 100644 index 0000000..8f82b55 --- /dev/null +++ b/rust/rox/src/class.rs @@ -0,0 +1,58 @@ +use std::{collections::HashMap, fmt::Display}; + +use crate::{ + callable::{Callable, CallingError}, + function::Function, + instance::Instance, + value::Value, +}; + +#[derive(Debug, Clone, PartialEq)] +pub struct Class { + pub name: String, + pub methods: HashMap, +} + +impl Display for Class { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self.name) + } +} + +impl Class { + pub fn new(name: String, methods: HashMap) -> Self { + Self { name, methods } + } + + pub fn find_method(&self, name: &str) -> Option<&Function> { + self.methods.get(name) + } +} + +impl Callable for Class { + fn name(&self) -> String { + self.name.clone() + } + + fn arity(&self) -> usize { + if let Some(initializer) = self.find_method("init") { + initializer.arity() + } else { + 0 + } + } + + fn call( + &self, + interpreter: &mut crate::interpreter::Interpreter, + args: Vec, + ) -> Result { + let class = self.clone(); + let instance = Instance::new(class); + if let Some(initializer) = self.find_method("init") { + initializer.bind(instance.clone()).call(interpreter, args)?; + }; + + Ok(Value::Instance(instance)) + } +} diff --git a/rust/rox/src/expression.rs b/rust/rox/src/expression.rs index bdc1ae6..42474b9 100644 --- a/rust/rox/src/expression.rs +++ b/rust/rox/src/expression.rs @@ -17,6 +17,18 @@ pub enum Expression { paren: Token, args: Vec, }, + Get { + object: Box, + name: Token, + }, + Set { + object: Box, + name: Token, + value: Box, + }, + This { + keyword: Token, + }, Grouping { expression: Box, }, diff --git a/rust/rox/src/function.rs b/rust/rox/src/function.rs index 47e92fe..ce24846 100644 --- a/rust/rox/src/function.rs +++ b/rust/rox/src/function.rs @@ -1,6 +1,7 @@ use crate::{ callable::{Callable, CallingError}, environment::Environment, + instance::Instance, interpreter::Interpreter, statement::Statement, token::Token, @@ -13,6 +14,7 @@ use std::{cell::RefCell, fmt::Display, rc::Rc}; #[derive(Debug, Clone)] pub struct Function { pub name: Token, + pub is_initializer: bool, pub params: Vec, pub body: Vec, pub closure: Rc>, @@ -52,10 +54,24 @@ impl Callable for Function { env.define(param.lexeme.clone(), arg); } - let result = interpreter + Ok(interpreter .block(&self.body, env) .map_err(|e| CallingError::CallInterpretationFailed(e.to_string()))? - .unwrap_or(Value::Nil); - Ok(result) + .unwrap_or(Value::Nil)) + } +} + +impl Function { + pub fn bind(&self, instance: Rc>) -> Self { + let mut env = Environment::with_enclosing(self.closure.clone()); + env.define("this".to_string(), Value::Instance(instance)); + + Self { + name: self.name.clone(), + is_initializer: self.is_initializer, + params: self.params.clone(), + body: self.body.clone(), + closure: Rc::new(RefCell::new(env)), + } } } diff --git a/rust/rox/src/instance.rs b/rust/rox/src/instance.rs new file mode 100644 index 0000000..4139866 --- /dev/null +++ b/rust/rox/src/instance.rs @@ -0,0 +1,49 @@ +use std::{cell::RefCell, collections::HashMap, fmt::Display, rc::Rc}; + +use thiserror::Error; + +use crate::{class::Class, token::Token, value::Value}; + +#[derive(Error, Debug)] +pub enum InstanceError { + #[error("line {0}: no property named {1}.")] + NoSuchProperty(usize, String), +} + +#[derive(Debug, Clone, PartialEq)] +pub struct Instance { + class: Class, + fields: HashMap, +} + +impl Display for Instance { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{} instance", self.class.name) + } +} + +impl Instance { + pub fn new(class: Class) -> Rc> { + Rc::new(RefCell::new(Self { + class, + fields: HashMap::default(), + })) + } + + pub fn get(&self, name: &Token, instance: Rc>) -> Result { + if let Some(field) = self.fields.get(name.lexeme.as_str()) { + Ok(field.clone()) + } else if let Some(method) = self.class.find_method(name.lexeme.as_str()) { + Ok(Value::Callable(Rc::new(method.bind(instance)))) + } else { + Err(InstanceError::NoSuchProperty( + name.line, + name.lexeme.clone(), + )) + } + } + + pub fn set(&mut self, name: &Token, value: Value) { + self.fields.insert(name.lexeme.clone(), value); + } +} diff --git a/rust/rox/src/interpreter.rs b/rust/rox/src/interpreter.rs index 819092c..0901fca 100644 --- a/rust/rox/src/interpreter.rs +++ b/rust/rox/src/interpreter.rs @@ -6,9 +6,10 @@ use tracing::error; use crate::{ callable::CallingError, + class::Class, environment::{Environment, EnvironmentError}, expression::Expression, - function::Function, + function::{self, Function}, native_functions, statement::Statement, token::{Literal, Token, TokenType}, @@ -33,6 +34,10 @@ pub enum InterpreterError { NotACallable(usize, Value), #[error("line {0}: {1}.")] FailedToCall(usize, CallingError), + #[error("line {0}: only instances have fields.")] + OnlyInstancesHaveFields(usize), + #[error("{0}")] + InstanceError(#[from] crate::instance::InstanceError), } /// Interpreter for the Lox language. @@ -106,6 +111,7 @@ impl Interpreter { self.function_statement(name, params, body) } Statement::Return { keyword: _, value } => self.return_statement(value), + Statement::Class { name, methods } => self.class(name, methods), } } @@ -138,6 +144,39 @@ impl Interpreter { Ok(None) } + fn class( + &mut self, + name: &Token, + methods: &[Statement], + ) -> Result, InterpreterError> { + self.environment + .borrow_mut() + .define(name.lexeme.clone(), Value::Nil); + + let mut class_methods: HashMap = HashMap::new(); + + for method in methods { + if let Statement::Function { name, params, body } = method { + let function = function::Function { + name: name.clone(), + is_initializer: name.lexeme == "init", + params: params.clone(), + body: body.clone(), + closure: self.environment.clone(), + }; + class_methods.insert(name.lexeme.clone(), function); + } + } + + let class = Class::new(name.lexeme.clone(), class_methods); + self.environment + .borrow_mut() + .assign(name, Value::Callable(Rc::new(class))) + .map_err(InterpreterError::UndefinedVariable)?; + + Ok(None) + } + /// Evaluate an expression and return its value. fn evaluate(&mut self, expression: &Expression) -> Result { match expression { @@ -164,6 +203,13 @@ impl Interpreter { paren, args, } => self.call(callee, paren, args), + Expression::Get { object, name } => self.get(object, name), + Expression::Set { + object, + name, + value, + } => self.set(object, name, value), + Expression::This { keyword } => self.lookup_var(keyword, expression), } } @@ -190,6 +236,29 @@ impl Interpreter { } } + fn get(&mut self, object: &Expression, name: &Token) -> Result { + match self.evaluate(object)? { + Value::Instance(instance) => Ok(instance.borrow().get(name, instance.clone())?), + _ => Err(InterpreterError::OnlyInstancesHaveFields(name.line)), + } + } + fn set( + &mut self, + object: &Expression, + name: &Token, + value: &Expression, + ) -> Result { + match self.evaluate(object)? { + Value::Instance(instance) => { + let value = self.evaluate(value)?; + instance.borrow_mut().set(name, value.clone()); + + Ok(value) + } + _ => Err(InterpreterError::OnlyInstancesHaveFields(name.line)), + } + } + /// Define a new function. fn function_statement( &mut self, @@ -200,6 +269,7 @@ impl Interpreter { let fn_name = name.lexeme.clone(); let fun = Function { name: name.clone(), + is_initializer: false, params: params.to_vec(), body: body.to_vec(), closure: self.environment.clone(), diff --git a/rust/rox/src/lib.rs b/rust/rox/src/lib.rs index 065fbeb..7cebf0b 100644 --- a/rust/rox/src/lib.rs +++ b/rust/rox/src/lib.rs @@ -14,10 +14,12 @@ use thiserror::Error; use tracing::error; pub mod callable; +pub mod class; pub mod cli; pub mod environment; pub mod expression; pub mod function; +pub mod instance; pub mod interpreter; pub mod keywords; pub mod native_functions; diff --git a/rust/rox/src/parser.rs b/rust/rox/src/parser.rs index 6952a5c..d199ec9 100644 --- a/rust/rox/src/parser.rs +++ b/rust/rox/src/parser.rs @@ -25,6 +25,8 @@ pub enum ParserError { SemicolonAfterValueExpected(usize), #[error("line {0}: expected ';' after expression.")] SemicolonAfterExpressionExpected(usize), + #[error("line {0}: expected class name.")] + ClassNameExpected(usize), #[error("line {0}: expected variable name.")] VariableNameExpected(usize), #[error("line {0}: invalid assignment target.")] @@ -57,6 +59,12 @@ pub enum ParserError { LeftBraceBeforeFunctionBodyExpected(usize), #[error("line {0}: expected ';' after return value.")] SemicolonAfterReturnExpected(usize), + #[error("line {0}: expected '{{' before class body.")] + LeftBraceBeforeClassExpected(usize), + #[error("line {0}: expected '}}' after class body.")] + RightBraceAfterClassExpected(usize), + #[error("line {0}: expected property name after '.'.")] + PropertyNameAfterDotExpected(usize), } /// Parse the Lox language. @@ -174,7 +182,9 @@ impl Parser { } fn declaration(&mut self) -> Result { - if self.matches(&[Fun]) { + if self.matches(&[Class]) { + self.class_declaration() + } else if self.matches(&[Fun]) { self.function() } else if self.matches(&[Var]) { self.var_declaration() @@ -297,6 +307,28 @@ impl Parser { Ok(Statement::Return { keyword, value }) } + fn class_declaration(&mut self) -> Result { + let line = self.current_token.line; + let name = self + .consume(&Identifier) + .ok_or(ParserError::ClassNameExpected(line))? + .clone(); + + self.consume(&LeftBrace) + .ok_or(ParserError::LeftBraceBeforeClassExpected(line))?; + + let mut methods = Vec::new(); + while !self.check(&RightBrace) && !self.is_at_end() { + let method = self.function()?; + methods.push(method); + } + + self.consume(&RightBrace) + .ok_or(ParserError::RightBraceAfterClassExpected(line))?; + + Ok(Statement::Class { name, methods }) + } + fn var_declaration(&mut self) -> Result { let line = self.current_token.line; let name = self @@ -409,6 +441,12 @@ impl Parser { name, value: Box::new(value), }) + } else if let Expression::Get { object, name } = expr { + Ok(Expression::Set { + object, + name, + value: Box::new(value), + }) } else { Err(ParserError::InvalidAssignmentTarget(equals.line)) } @@ -490,6 +528,17 @@ impl Parser { loop { if self.matches(&[LeftParen]) { expr = self.finish_call(expr)?; + } else if self.matches(&[Dot]) { + let line = self.current_token.line; + + let name = self + .consume(&Identifier) + .ok_or(ParserError::PropertyNameAfterDotExpected(line))? + .clone(); + expr = Expression::Get { + object: Box::new(expr), + name, + } } else { break; } @@ -536,6 +585,10 @@ impl Parser { Ok(Expression::Literal { value: token::Literal::Nil, }) + } else if self.matches(&[This]) { + Ok(Expression::This { + keyword: self.previous()?.clone(), + }) } else if self.matches(&[Identifier]) { let prev = self.previous()?.clone(); Ok(Expression::Variable { name: prev }) diff --git a/rust/rox/src/resolver.rs b/rust/rox/src/resolver.rs index 7ca71c1..3be87da 100644 --- a/rust/rox/src/resolver.rs +++ b/rust/rox/src/resolver.rs @@ -10,12 +10,24 @@ pub enum ResolverError { NameRedefinition(usize), #[error("line {0}: can not return from top-level code.")] TopLevelReturn(usize), + #[error("line {0}: can not use 'this' outside of a class.")] + ThisOutsideClass(usize), + #[error("line {0}: can not return a value from an initializer.")] + ReturnFromInitializer(usize), } #[derive(Debug, Clone, Copy, PartialEq, Eq)] enum FunctionType { None, Function, + Initializer, + Method, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +enum ClassType { + None, + Class, } #[derive(Debug)] @@ -23,6 +35,7 @@ pub struct Resolver<'a> { scopes: Vec>, interpreter: &'a mut Interpreter, current_fun: FunctionType, + current_class: ClassType, } impl<'a> Resolver<'a> { @@ -31,6 +44,7 @@ impl<'a> Resolver<'a> { scopes: Vec::default(), interpreter, current_fun: FunctionType::None, + current_class: ClassType::None, } } @@ -60,16 +74,8 @@ impl<'a> Resolver<'a> { 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(()) - } - } + Statement::Return { keyword, value } => self.resolve_return_stmt(keyword, value), + Statement::Class { name, methods } => self.resolve_class_stmt(name, methods), } } @@ -95,6 +101,19 @@ impl<'a> Resolver<'a> { } => self.resolve_binary_expr(left, right), Expression::Unary { operator: _, right } => self.resolve_expression(right), Expression::Variable { name } => self.resolve_var_expr(expr, name), + Expression::Get { object, name: _ } => self.resolve_expression(object), + Expression::Set { + object, + name: _, + value, + } => self.resolve_set_expression(object, value), + Expression::This { keyword } => { + if self.current_class == ClassType::None { + return Err(ResolverError::ThisOutsideClass(keyword.line)); + } + + self.resolve_local(expr, keyword) + } } } @@ -132,6 +151,59 @@ impl<'a> Resolver<'a> { Ok(()) } + fn resolve_class_stmt( + &mut self, + name: &Token, + methods: &[Statement], + ) -> Result<(), ResolverError> { + let enclosing_class = self.current_class; + self.current_class = ClassType::Class; + + self.declare(name)?; + self.define(name); + + self.begin_scope(); + + if let Some(scope) = self.scopes.last_mut() { + scope.insert("this".to_string(), true); + } + + for method in methods { + if let Statement::Function { name, params, body } = method { + let declaration = if name.lexeme == "init" { + FunctionType::Initializer + } else { + FunctionType::Method + }; + self.resolve_function(params, body, declaration)?; + } + } + + self.end_scope(); + + self.current_class = enclosing_class; + + Ok(()) + } + + fn resolve_return_stmt( + &mut self, + keyword: &Token, + value: &Option, + ) -> Result<(), ResolverError> { + if self.current_fun == FunctionType::None { + return Err(ResolverError::TopLevelReturn(keyword.line)); + } + if let Some(value) = value { + if self.current_fun == FunctionType::Initializer { + return Err(ResolverError::ReturnFromInitializer(keyword.line)); + } + self.resolve_expression(value) + } else { + Ok(()) + } + } + fn resolve_var( &mut self, name: &Token, @@ -205,6 +277,17 @@ impl<'a> Resolver<'a> { Ok(()) } + fn resolve_set_expression( + &mut self, + object: &Expression, + value: &Expression, + ) -> Result<(), ResolverError> { + self.resolve_expression(value)?; + self.resolve_expression(object)?; + + Ok(()) + } + fn resolve_if_stmt( &mut self, condition: &Expression, diff --git a/rust/rox/src/statement.rs b/rust/rox/src/statement.rs index c75b3c4..0ed5af8 100644 --- a/rust/rox/src/statement.rs +++ b/rust/rox/src/statement.rs @@ -28,4 +28,8 @@ pub enum Statement { keyword: Token, value: Option, }, + Class { + name: Token, + methods: Vec, + }, } diff --git a/rust/rox/src/value.rs b/rust/rox/src/value.rs index d344c4b..5cb5970 100644 --- a/rust/rox/src/value.rs +++ b/rust/rox/src/value.rs @@ -1,8 +1,8 @@ -use std::{fmt::Display, rc::Rc}; +use std::{cell::RefCell, fmt::Display, rc::Rc}; use ordered_float::OrderedFloat; -use crate::{callable::Callable, token::Literal}; +use crate::{callable::Callable, instance, token::Literal}; /// Concrete value in the interpreter. #[derive(Clone, Debug, PartialEq)] @@ -12,6 +12,7 @@ pub enum Value { String(String), Number(OrderedFloat), Boolean(bool), + Instance(Rc>), Nil, } @@ -23,6 +24,7 @@ impl Display for Value { Value::String(x) => write!(f, "{x}"), Value::Number(x) => write!(f, "{x}"), Value::Boolean(x) => write!(f, "{x}"), + Value::Instance(x) => write!(f, "{}", x.borrow()), Value::Nil => write!(f, "nil"), } }