From 32e5e8a41a913d442c4e043b60a03f4366827197 Mon Sep 17 00:00:00 2001 From: Sebastian Hugentobler Date: Mon, 26 May 2025 10:19:18 +0200 Subject: [PATCH] implement chapter 13 in rust! --- lox/class_fields.lox | 6 ++ lox/class_instance.lox | 3 + lox/class_methods.lox | 7 +++ lox/class_name.lox | 7 +++ lox/class_this.lox | 10 ++++ lox/fibonacci_timed.lox | 9 +++ lox/init.lox | 9 +++ lox/init_early_return.lox | 8 +++ lox/init_return.lox | 5 ++ lox/self_inheritance.lox | 1 + lox/super_call.lox | 14 +++++ lox/super_without_superclass.lox | 6 ++ lox/superclass_method.lox | 9 +++ lox/superclass_not_class.lox | 3 + rust/rox/src/class.rs | 23 ++++++- rust/rox/src/expression.rs | 4 ++ rust/rox/src/instance.rs | 11 +++- rust/rox/src/interpreter.rs | 100 ++++++++++++++++++++++++++++--- rust/rox/src/native_functions.rs | 7 ++- rust/rox/src/parser.rs | 30 +++++++++- rust/rox/src/resolver.rs | 54 ++++++++++++++++- rust/rox/src/statement.rs | 1 + rust/rox/src/value.rs | 10 +++- 23 files changed, 318 insertions(+), 19 deletions(-) create mode 100644 lox/class_fields.lox create mode 100644 lox/class_instance.lox create mode 100644 lox/class_methods.lox create mode 100644 lox/class_name.lox create mode 100644 lox/class_this.lox create mode 100644 lox/fibonacci_timed.lox create mode 100644 lox/init.lox create mode 100644 lox/init_early_return.lox create mode 100644 lox/init_return.lox create mode 100644 lox/self_inheritance.lox create mode 100644 lox/super_call.lox create mode 100644 lox/super_without_superclass.lox create mode 100644 lox/superclass_method.lox create mode 100644 lox/superclass_not_class.lox diff --git a/lox/class_fields.lox b/lox/class_fields.lox new file mode 100644 index 0000000..17de71d --- /dev/null +++ b/lox/class_fields.lox @@ -0,0 +1,6 @@ +class Dog{} + +var dog = Dog(); +dog.name = "Leika"; + +print dog.name; diff --git a/lox/class_instance.lox b/lox/class_instance.lox new file mode 100644 index 0000000..fd47330 --- /dev/null +++ b/lox/class_instance.lox @@ -0,0 +1,3 @@ +class Bagel {} +var bagel = Bagel(); +print bagel; diff --git a/lox/class_methods.lox b/lox/class_methods.lox new file mode 100644 index 0000000..35268be --- /dev/null +++ b/lox/class_methods.lox @@ -0,0 +1,7 @@ +class Bacon { + eat() { + print "Crunch crunch crunch!"; + } +} + +Bacon().eat(); diff --git a/lox/class_name.lox b/lox/class_name.lox new file mode 100644 index 0000000..747db5c --- /dev/null +++ b/lox/class_name.lox @@ -0,0 +1,7 @@ +class DevonshireCream { + serveOn() { + return "Scones"; + } +} + +print DevonshireCream; diff --git a/lox/class_this.lox b/lox/class_this.lox new file mode 100644 index 0000000..10564ee --- /dev/null +++ b/lox/class_this.lox @@ -0,0 +1,10 @@ +class Cake { + taste() { + var adjective = "delicious"; + print "The " + this.flavor + " cake is " + adjective + "!"; + } +} + +var cake = Cake(); +cake.flavor = "German chocolate"; +cake.taste(); diff --git a/lox/fibonacci_timed.lox b/lox/fibonacci_timed.lox new file mode 100644 index 0000000..62a48cf --- /dev/null +++ b/lox/fibonacci_timed.lox @@ -0,0 +1,9 @@ +fun fib(n) { + if (n < 2) return n; + return fib(n - 1) + fib(n - 2); +} + +var before = clock(); +print fib(40); +var after = clock(); +print after - before; diff --git a/lox/init.lox b/lox/init.lox new file mode 100644 index 0000000..20ea22a --- /dev/null +++ b/lox/init.lox @@ -0,0 +1,9 @@ +class Foo { + init() { + this.name = "test"; + print this; + } +} + +var foo = Foo(); +print foo.name; diff --git a/lox/init_early_return.lox b/lox/init_early_return.lox new file mode 100644 index 0000000..554913e --- /dev/null +++ b/lox/init_early_return.lox @@ -0,0 +1,8 @@ +class Foo { + init() { + return; + print this; + } +} + +var foo = Foo(); diff --git a/lox/init_return.lox b/lox/init_return.lox new file mode 100644 index 0000000..124da9e --- /dev/null +++ b/lox/init_return.lox @@ -0,0 +1,5 @@ +class Foo { + init() { + return "something else"; + } +} diff --git a/lox/self_inheritance.lox b/lox/self_inheritance.lox new file mode 100644 index 0000000..d144a83 --- /dev/null +++ b/lox/self_inheritance.lox @@ -0,0 +1 @@ +class Oops < Oops {} diff --git a/lox/super_call.lox b/lox/super_call.lox new file mode 100644 index 0000000..79ad64b --- /dev/null +++ b/lox/super_call.lox @@ -0,0 +1,14 @@ +class Doughnut { + cook() { + print "Fry until golden brown."; + } +} + +class BostonCream < Doughnut { + cook() { + super.cook(); + print "Pipe full of custard and coat with chocolate."; + } +} + +BostonCream().cook(); diff --git a/lox/super_without_superclass.lox b/lox/super_without_superclass.lox new file mode 100644 index 0000000..59dcc5e --- /dev/null +++ b/lox/super_without_superclass.lox @@ -0,0 +1,6 @@ +class Eclair { + cook() { + super.cook(); + print "Pipe full of crème pâtissière."; + } +} diff --git a/lox/superclass_method.lox b/lox/superclass_method.lox new file mode 100644 index 0000000..7d01973 --- /dev/null +++ b/lox/superclass_method.lox @@ -0,0 +1,9 @@ +class Doughnut { + cook() { + print "Fry until golden brown."; + } +} + +class BostonCream < Doughnut {} + +BostonCream().cook(); diff --git a/lox/superclass_not_class.lox b/lox/superclass_not_class.lox new file mode 100644 index 0000000..44d215d --- /dev/null +++ b/lox/superclass_not_class.lox @@ -0,0 +1,3 @@ +var NotAClass = "I am totally not a class"; + +class Subclass < NotAClass {} diff --git a/rust/rox/src/class.rs b/rust/rox/src/class.rs index 8f82b55..0137772 100644 --- a/rust/rox/src/class.rs +++ b/rust/rox/src/class.rs @@ -10,6 +10,7 @@ use crate::{ #[derive(Debug, Clone, PartialEq)] pub struct Class { pub name: String, + pub superclass: Option>, pub methods: HashMap, } @@ -20,12 +21,28 @@ impl Display for Class { } impl Class { - pub fn new(name: String, methods: HashMap) -> Self { - Self { name, methods } + pub fn new( + name: String, + superclass: Option, + methods: HashMap, + ) -> Self { + let superclass = superclass.map(|x| Box::new(x)); + + Self { + name, + superclass, + methods, + } } pub fn find_method(&self, name: &str) -> Option<&Function> { - self.methods.get(name) + self.methods.get(name).or_else(|| { + if let Some(superclass) = &self.superclass { + superclass.find_method(name) + } else { + None + } + }) } } diff --git a/rust/rox/src/expression.rs b/rust/rox/src/expression.rs index 42474b9..844e710 100644 --- a/rust/rox/src/expression.rs +++ b/rust/rox/src/expression.rs @@ -47,4 +47,8 @@ pub enum Expression { Variable { name: Token, }, + Super { + keyword: Token, + method: Token, + }, } diff --git a/rust/rox/src/instance.rs b/rust/rox/src/instance.rs index 4139866..e9edd19 100644 --- a/rust/rox/src/instance.rs +++ b/rust/rox/src/instance.rs @@ -2,7 +2,11 @@ use std::{cell::RefCell, collections::HashMap, fmt::Display, rc::Rc}; use thiserror::Error; -use crate::{class::Class, token::Token, value::Value}; +use crate::{ + class::Class, + token::Token, + value::{CallableType, Value}, +}; #[derive(Error, Debug)] pub enum InstanceError { @@ -34,7 +38,10 @@ impl Instance { 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)))) + Ok(Value::Callable(( + Rc::new(method.bind(instance)), + CallableType::Function, + ))) } else { Err(InstanceError::NoSuchProperty( name.line, diff --git a/rust/rox/src/interpreter.rs b/rust/rox/src/interpreter.rs index 0901fca..f0b730f 100644 --- a/rust/rox/src/interpreter.rs +++ b/rust/rox/src/interpreter.rs @@ -13,7 +13,7 @@ use crate::{ native_functions, statement::Statement, token::{Literal, Token, TokenType}, - value::Value, + value::{CallableType, Value}, }; #[derive(Error, Debug)] @@ -36,8 +36,14 @@ pub enum InterpreterError { FailedToCall(usize, CallingError), #[error("line {0}: only instances have fields.")] OnlyInstancesHaveFields(usize), + #[error("line {0}: superclass must be a class.")] + SuperclassNotClass(usize), + #[error("line {0}: Undefined property '{1}'.")] + UndefinedProperty(usize, String), #[error("{0}")] InstanceError(#[from] crate::instance::InstanceError), + #[error("{0}")] + EnvironmentError(#[from] crate::environment::EnvironmentError), } /// Interpreter for the Lox language. @@ -111,7 +117,11 @@ impl Interpreter { self.function_statement(name, params, body) } Statement::Return { keyword: _, value } => self.return_statement(value), - Statement::Class { name, methods } => self.class(name, methods), + Statement::Class { + name, + superclass, + methods, + } => self.class(name, superclass, methods), } } @@ -147,12 +157,38 @@ impl Interpreter { fn class( &mut self, name: &Token, + superclass: &Option, methods: &[Statement], ) -> Result, InterpreterError> { + let superclass = if let Some(superclass) = superclass { + let superclass = self.evaluate(superclass)?; + if let Value::Callable((_, CallableType::Class(class))) = superclass { + Some(class) + } else { + return Err(InterpreterError::SuperclassNotClass(name.line)); + } + } else { + None + }; + self.environment .borrow_mut() .define(name.lexeme.clone(), Value::Nil); + let old_env = self.environment.clone(); + if let Some(ref superclass) = superclass { + self.environment = Rc::new(RefCell::new(Environment::with_enclosing( + self.environment.clone(), + ))); + self.environment.borrow_mut().define( + "super".to_string(), + Value::Callable(( + Rc::new(superclass.clone()), + CallableType::Class(superclass.clone()), + )), + ); + } + let mut class_methods: HashMap = HashMap::new(); for method in methods { @@ -168,10 +204,17 @@ impl Interpreter { } } - let class = Class::new(name.lexeme.clone(), class_methods); + let class = Class::new(name.lexeme.clone(), superclass, class_methods); + + if class.superclass.is_some() { + self.environment = old_env; + } self.environment .borrow_mut() - .assign(name, Value::Callable(Rc::new(class))) + .assign( + name, + Value::Callable((Rc::new(class.clone()), CallableType::Class(class))), + ) .map_err(InterpreterError::UndefinedVariable)?; Ok(None) @@ -210,6 +253,7 @@ impl Interpreter { value, } => self.set(object, name, value), Expression::This { keyword } => self.lookup_var(keyword, expression), + Expression::Super { keyword, method } => self.super_expr(expression, keyword, method), } } @@ -228,7 +272,7 @@ impl Interpreter { args.push(self.evaluate(arg)?); } - if let Value::Callable(f) = callee { + if let Value::Callable((f, _)) = callee { f.call(self, args) .map_err(|e| InterpreterError::FailedToCall(paren.line, e)) } else { @@ -242,6 +286,7 @@ impl Interpreter { _ => Err(InterpreterError::OnlyInstancesHaveFields(name.line)), } } + fn set( &mut self, object: &Expression, @@ -259,6 +304,44 @@ impl Interpreter { } } + fn super_expr( + &mut self, + expression: &Expression, + keyword: &Token, + method: &Token, + ) -> Result { + if let Some(distance) = self.locals.get(expression) { + let superclass = self.environment.borrow().get_at(*distance, keyword)?; + let object = self.environment.borrow().get_at( + *distance - 1, + &Token { + token_type: TokenType::This, + lexeme: "this".to_string(), + literal: Some(Literal::String("this".to_string())), + line: 0, + }, + )?; + let Value::Instance(instance) = object else { + return Err(InterpreterError::SuperclassNotClass(keyword.line)); + }; + + let Value::Callable((_, CallableType::Class(class))) = superclass else { + return Err(InterpreterError::SuperclassNotClass(keyword.line)); + }; + if let Some(method) = class.find_method(&method.lexeme) { + let method = method.bind(instance); + Ok(Value::Callable((Rc::new(method), CallableType::Function))) + } else { + Err(InterpreterError::UndefinedProperty( + keyword.line, + method.lexeme.clone(), + )) + } + } else { + Err(InterpreterError::SuperclassNotClass(keyword.line)) + } + } + /// Define a new function. fn function_statement( &mut self, @@ -275,9 +358,10 @@ impl Interpreter { closure: self.environment.clone(), }; - self.environment - .borrow_mut() - .define(fn_name, Value::Callable(Rc::new(fun))); + self.environment.borrow_mut().define( + fn_name, + Value::Callable((Rc::new(fun), CallableType::Function)), + ); Ok(None) } diff --git a/rust/rox/src/native_functions.rs b/rust/rox/src/native_functions.rs index 0e6cd08..aaeb6cb 100644 --- a/rust/rox/src/native_functions.rs +++ b/rust/rox/src/native_functions.rs @@ -8,7 +8,7 @@ use ordered_float::OrderedFloat; use crate::{ callable::{Callable, CallingError}, interpreter::Interpreter, - value::Value, + value::{CallableType, Value}, }; struct Clock; @@ -37,5 +37,8 @@ impl Callable for Clock { } pub fn all() -> Vec<(String, Value)> { - vec![("clock".into(), Value::Callable(Rc::new(Clock {})))] + vec![( + "clock".into(), + Value::Callable((Rc::new(Clock {}), CallableType::Function)), + )] } diff --git a/rust/rox/src/parser.rs b/rust/rox/src/parser.rs index d199ec9..76e8d5a 100644 --- a/rust/rox/src/parser.rs +++ b/rust/rox/src/parser.rs @@ -65,6 +65,10 @@ pub enum ParserError { RightBraceAfterClassExpected(usize), #[error("line {0}: expected property name after '.'.")] PropertyNameAfterDotExpected(usize), + #[error("line {0}: expected '.' after 'super'.")] + DotAfterSuper(usize), + #[error("line {0}: expected superclass method name.")] + SuperclassMethodNameExpected(usize), } /// Parse the Lox language. @@ -314,6 +318,16 @@ impl Parser { .ok_or(ParserError::ClassNameExpected(line))? .clone(); + let superclass = if self.matches(&[Less]) { + self.consume(&Identifier) + .ok_or(ParserError::ClassNameExpected(line))?; + let name = self.previous()?.clone(); + + Some(Expression::Variable { name }) + } else { + None + }; + self.consume(&LeftBrace) .ok_or(ParserError::LeftBraceBeforeClassExpected(line))?; @@ -326,7 +340,11 @@ impl Parser { self.consume(&RightBrace) .ok_or(ParserError::RightBraceAfterClassExpected(line))?; - Ok(Statement::Class { name, methods }) + Ok(Statement::Class { + name, + superclass, + methods, + }) } fn var_declaration(&mut self) -> Result { @@ -585,6 +603,16 @@ impl Parser { Ok(Expression::Literal { value: token::Literal::Nil, }) + } else if self.matches(&[Super]) { + let line = self.current_token.line; + let keyword = self.previous()?.clone(); + self.consume(&Dot).ok_or(ParserError::DotAfterSuper(line))?; + let method = self + .consume(&Identifier) + .ok_or(ParserError::SuperclassMethodNameExpected(line))? + .clone(); + + Ok(Expression::Super { keyword, method }) } else if self.matches(&[This]) { Ok(Expression::This { keyword: self.previous()?.clone(), diff --git a/rust/rox/src/resolver.rs b/rust/rox/src/resolver.rs index 3be87da..7e2ac1d 100644 --- a/rust/rox/src/resolver.rs +++ b/rust/rox/src/resolver.rs @@ -14,6 +14,12 @@ pub enum ResolverError { ThisOutsideClass(usize), #[error("line {0}: can not return a value from an initializer.")] ReturnFromInitializer(usize), + #[error("line {0}: a class can not inherit from itself.")] + SelfInheritance(usize), + #[error("line {0}: can not use 'super' outside of a class.")] + SuperOutsideClass(usize), + #[error("line {0}: can not use 'super' in a class with no superclass.")] + SuperWithoutSuperclass(usize), } #[derive(Debug, Clone, Copy, PartialEq, Eq)] @@ -28,6 +34,7 @@ enum FunctionType { enum ClassType { None, Class, + Subclass, } #[derive(Debug)] @@ -75,7 +82,11 @@ impl<'a> Resolver<'a> { self.resolve_function_stmt(name, params, body) } Statement::Return { keyword, value } => self.resolve_return_stmt(keyword, value), - Statement::Class { name, methods } => self.resolve_class_stmt(name, methods), + Statement::Class { + name, + superclass, + methods, + } => self.resolve_class_stmt(name, superclass, methods), } } @@ -114,6 +125,9 @@ impl<'a> Resolver<'a> { self.resolve_local(expr, keyword) } + Expression::Super { keyword, method: _ } => { + self.resolve_super_expression(keyword, expr) + } } } @@ -154,6 +168,7 @@ impl<'a> Resolver<'a> { fn resolve_class_stmt( &mut self, name: &Token, + superclass: &Option, methods: &[Statement], ) -> Result<(), ResolverError> { let enclosing_class = self.current_class; @@ -162,6 +177,25 @@ impl<'a> Resolver<'a> { self.declare(name)?; self.define(name); + if let Some( + superclass @ Expression::Variable { + name: superclass_name, + }, + ) = superclass + { + if superclass_name.lexeme == name.lexeme { + return Err(ResolverError::SelfInheritance(superclass_name.line)); + } + + self.current_class = ClassType::Subclass; + self.resolve_expression(superclass)?; + + self.begin_scope(); + if let Some(scope) = self.scopes.last_mut() { + scope.insert("super".to_string(), true); + } + } + self.begin_scope(); if let Some(scope) = self.scopes.last_mut() { @@ -181,6 +215,10 @@ impl<'a> Resolver<'a> { self.end_scope(); + if superclass.is_some() { + self.end_scope(); + } + self.current_class = enclosing_class; Ok(()) @@ -288,6 +326,20 @@ impl<'a> Resolver<'a> { Ok(()) } + fn resolve_super_expression( + &mut self, + keyword: &Token, + expression: &Expression, + ) -> Result<(), ResolverError> { + if self.current_class == ClassType::None { + return Err(ResolverError::SuperOutsideClass(keyword.line)); + } else if self.current_class != ClassType::Subclass { + return Err(ResolverError::SuperWithoutSuperclass(keyword.line)); + } + + self.resolve_local(expression, keyword) + } + fn resolve_if_stmt( &mut self, condition: &Expression, diff --git a/rust/rox/src/statement.rs b/rust/rox/src/statement.rs index 0ed5af8..fc892af 100644 --- a/rust/rox/src/statement.rs +++ b/rust/rox/src/statement.rs @@ -30,6 +30,7 @@ pub enum Statement { }, Class { name: Token, + superclass: Option, methods: Vec, }, } diff --git a/rust/rox/src/value.rs b/rust/rox/src/value.rs index 5cb5970..e96c7fd 100644 --- a/rust/rox/src/value.rs +++ b/rust/rox/src/value.rs @@ -4,11 +4,17 @@ use ordered_float::OrderedFloat; use crate::{callable::Callable, instance, token::Literal}; +#[derive(Clone, Debug, PartialEq)] +pub enum CallableType { + Function, + Class(crate::class::Class), +} + /// Concrete value in the interpreter. #[derive(Clone, Debug, PartialEq)] pub enum Value { Return(Box), - Callable(Rc), + Callable((Rc, CallableType)), String(String), Number(OrderedFloat), Boolean(bool), @@ -20,7 +26,7 @@ impl Display for Value { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { Value::Return(x) => write!(f, "{x}"), - Value::Callable(x) => write!(f, "{x}"), + Value::Callable(x) => write!(f, "{}", x.0), Value::String(x) => write!(f, "{x}"), Value::Number(x) => write!(f, "{x}"), Value::Boolean(x) => write!(f, "{x}"),