implement chapter 13 in rust!

This commit is contained in:
Sebastian Hugentobler 2025-05-26 10:19:18 +02:00
parent 621c97102a
commit 32e5e8a41a
Signed by: shu
SSH key fingerprint: SHA256:ppcx6MlixdNZd5EUM1nkHOKoyQYoJwzuQKXM6J/t66M
23 changed files with 318 additions and 19 deletions

6
lox/class_fields.lox Normal file
View file

@ -0,0 +1,6 @@
class Dog{}
var dog = Dog();
dog.name = "Leika";
print dog.name;

3
lox/class_instance.lox Normal file
View file

@ -0,0 +1,3 @@
class Bagel {}
var bagel = Bagel();
print bagel;

7
lox/class_methods.lox Normal file
View file

@ -0,0 +1,7 @@
class Bacon {
eat() {
print "Crunch crunch crunch!";
}
}
Bacon().eat();

7
lox/class_name.lox Normal file
View file

@ -0,0 +1,7 @@
class DevonshireCream {
serveOn() {
return "Scones";
}
}
print DevonshireCream;

10
lox/class_this.lox Normal file
View file

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

9
lox/fibonacci_timed.lox Normal file
View file

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

9
lox/init.lox Normal file
View file

@ -0,0 +1,9 @@
class Foo {
init() {
this.name = "test";
print this;
}
}
var foo = Foo();
print foo.name;

View file

@ -0,0 +1,8 @@
class Foo {
init() {
return;
print this;
}
}
var foo = Foo();

5
lox/init_return.lox Normal file
View file

@ -0,0 +1,5 @@
class Foo {
init() {
return "something else";
}
}

1
lox/self_inheritance.lox Normal file
View file

@ -0,0 +1 @@
class Oops < Oops {}

14
lox/super_call.lox Normal file
View file

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

View file

@ -0,0 +1,6 @@
class Eclair {
cook() {
super.cook();
print "Pipe full of crème pâtissière.";
}
}

View file

@ -0,0 +1,9 @@
class Doughnut {
cook() {
print "Fry until golden brown.";
}
}
class BostonCream < Doughnut {}
BostonCream().cook();

View file

@ -0,0 +1,3 @@
var NotAClass = "I am totally not a class";
class Subclass < NotAClass {}

View file

@ -10,6 +10,7 @@ use crate::{
#[derive(Debug, Clone, PartialEq)]
pub struct Class {
pub name: String,
pub superclass: Option<Box<Class>>,
pub methods: HashMap<String, Function>,
}
@ -20,12 +21,28 @@ impl Display for Class {
}
impl Class {
pub fn new(name: String, methods: HashMap<String, Function>) -> Self {
Self { name, methods }
pub fn new(
name: String,
superclass: Option<Class>,
methods: HashMap<String, Function>,
) -> 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
}
})
}
}

View file

@ -47,4 +47,8 @@ pub enum Expression {
Variable {
name: Token,
},
Super {
keyword: Token,
method: Token,
},
}

View file

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

View file

@ -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<Expression>,
methods: &[Statement],
) -> Result<Option<Value>, 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<String, Function> = 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<Value, InterpreterError> {
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)
}

View file

@ -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)),
)]
}

View file

@ -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<Statement, ParserError> {
@ -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(),

View file

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

View file

@ -30,6 +30,7 @@ pub enum Statement {
},
Class {
name: Token,
superclass: Option<Expression>,
methods: Vec<Statement>,
},
}

View file

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