Skip to content

Commit

Permalink
Implemented handling of calls to superclass methods.
Browse files Browse the repository at this point in the history
  • Loading branch information
jens-siebert committed Feb 9, 2024
1 parent 47dbbcf commit fdb599b
Show file tree
Hide file tree
Showing 7 changed files with 147 additions and 10 deletions.
18 changes: 18 additions & 0 deletions rlox-lib/src/base/expr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,11 @@ pub enum Expr {
name: Box<Token>,
value: Box<Expr>,
},
Super {
uuid: Uuid,
keyword: Box<Token>,
method: Box<Token>,
},
This {
uuid: Uuid,
keyword: Box<Token>,
Expand Down Expand Up @@ -141,6 +146,14 @@ impl Expr {
}
}

pub fn super_expr(keyword: Token, method: Token) -> Self {
Expr::Super {
uuid: Uuid::new_v4(),
keyword: Box::new(keyword),
method: Box::new(method),
}
}

pub fn this(keyword: Token) -> Self {
Expr::This {
uuid: Uuid::new_v4(),
Expand Down Expand Up @@ -213,6 +226,11 @@ impl ExprUuid for Expr {
name: _name,
value: _value,
} => uuid,
Expr::Super {
uuid,
keyword: _keyword,
method: _method,
} => uuid,
Expr::This {
uuid,
keyword: _keyword,
Expand Down
6 changes: 4 additions & 2 deletions rlox-lib/src/base/expr_result.rs
Original file line number Diff line number Diff line change
Expand Up @@ -186,10 +186,12 @@ impl LoxClass {
}

pub fn find_method(&self, name: &str) -> Option<&LoxFunction> {
if let Some(sc) = self.superclass.as_ref() {
if self.methods.contains_key(name) {
self.methods.get(&name.to_string())
} else if let Some(sc) = self.superclass.as_ref() {
sc.find_method(name)
} else {
self.methods.get(&name.to_string())
None
}
}
}
Expand Down
22 changes: 22 additions & 0 deletions rlox-lib/src/base/parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,10 @@ pub enum ParserError {
MissingPropertyName { line: usize },
#[error("{line:?}: Expect superclass name.")]
MissingSuperclassName { line: usize },
#[error("{line:?}: Expect '.' after 'super'.")]
MissingDotAfterSuper { line: usize },
#[error("{line:?}: Expect superclass method name.")]
MissingSuperclassMethodName { line: usize },
#[error("{line:?}: Invalid assignment target.")]
InvalidAssignmentTarget { line: usize },
}
Expand Down Expand Up @@ -568,6 +572,24 @@ impl Parser {
_ => {}
}

if self.match_token_types(&[TokenType::Super])? {
let keyword = self.previous()?;
self.consume(
TokenType::Dot,
ParserError::MissingDotAfterSuper {
line: self.peek().unwrap().line,
},
)?;
let method = self.consume(
TokenType::Identifier,
ParserError::MissingSuperclassMethodName {
line: self.peek().unwrap().line,
},
)?;

return Ok(Expr::super_expr(keyword, method));
}

if self.match_token_types(&[TokenType::This])? {
return Ok(Expr::this(self.previous()?));
}
Expand Down
51 changes: 43 additions & 8 deletions rlox-lib/src/interpreter/interpreter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -280,6 +280,26 @@ impl Visitor<Expr, ExprResult, RuntimeError> for Interpreter<'_> {
Err(RuntimeError::InvalidFieldAccess { line: name.line })
}
}
Expr::Super {
uuid,
keyword,
method,
} => {
if let Some(distance) = self.locals.borrow().get(uuid) {
let superclass = self.environment.borrow().get_at(*distance, "super");
let object = self.environment.borrow().get_at(*distance - 1, "this");

if let Some(ExprResult::Class(sc)) = superclass {
if let Some(ExprResult::Instance(obj)) = object {
if let Some(method) = sc.find_method(&method.lexeme) {
return Ok(method.bind(&obj));
}
}
}
}

Err(RuntimeError::UndefinedProperty { line: keyword.line })
}
Expr::This { uuid, keyword } => self.lookup_variable(keyword, uuid),
Expr::Unary {
uuid: _uuid,
Expand Down Expand Up @@ -319,12 +339,8 @@ impl Visitor<Stmt, (), RuntimeError> for Interpreter<'_> {
superclass,
methods,
} => {
let class_superclass = if let Some(sc) = superclass.as_ref() {
if let ExprResult::Class(c) = self.evaluate(sc)? {
Some(c)
} else {
None
}
let sc_result = if let Some(sc) = superclass.as_ref() {
Some(self.evaluate(sc)?)
} else {
None
};
Expand All @@ -333,6 +349,15 @@ impl Visitor<Stmt, (), RuntimeError> for Interpreter<'_> {
.borrow_mut()
.define(&name.lexeme, ExprResult::none());

let enclosing_environment = if let Some(sc) = sc_result.to_owned() {
let env = Environment::new_enclosing(Rc::clone(&self.environment));
env.borrow_mut().define("super", sc);

env
} else {
Rc::clone(&self.environment)
};

let functions = methods
.iter()
.filter_map(|method| {
Expand All @@ -341,7 +366,7 @@ impl Visitor<Stmt, (), RuntimeError> for Interpreter<'_> {
*name.to_owned(),
params.to_owned(),
body.to_owned(),
Rc::clone(&self.environment),
Rc::clone(&enclosing_environment),
name.lexeme.eq("this"),
);

Expand All @@ -352,7 +377,17 @@ impl Visitor<Stmt, (), RuntimeError> for Interpreter<'_> {
})
.collect();

let class = LoxClass::new(*name.to_owned(), class_superclass, functions);
let lox_superclass = if let Some(sc) = sc_result.to_owned() {
if let ExprResult::Class(c) = sc {
Some(c)
} else {
return Err(RuntimeError::SuperclassInvalidType { line: name.line });
}
} else {
None
};

let class = LoxClass::new(*name.to_owned(), lox_superclass, functions);

self.environment
.borrow_mut()
Expand Down
25 changes: 25 additions & 0 deletions rlox-lib/src/interpreter/resolver.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ enum FunctionType {
enum ClassType {
None,
Class,
Subclass,
}

pub struct Resolver<'a> {
Expand Down Expand Up @@ -147,6 +148,8 @@ impl Visitor<Stmt, (), RuntimeError> for Resolver<'_> {
self.define(name);

if let Some(sc) = superclass.as_ref() {
self.current_class_type.replace(ClassType::Subclass);

if let Expr::Variable {
uuid: _uuid,
name: sc_name,
Expand All @@ -160,6 +163,11 @@ impl Visitor<Stmt, (), RuntimeError> for Resolver<'_> {
}

self.resolve_expr(sc)?;

self.begin_scope();
if let Some(scope) = self.scopes.borrow_mut().last_mut() {
scope.insert(String::from("super"), true);
}
}

self.begin_scope();
Expand All @@ -184,6 +192,10 @@ impl Visitor<Stmt, (), RuntimeError> for Resolver<'_> {
}
}

if superclass.is_some() {
self.end_scope();
}

self.end_scope();
self.current_class_type.replace(enclosing_class);
}
Expand Down Expand Up @@ -306,6 +318,19 @@ impl Visitor<Expr, (), RuntimeError> for Resolver<'_> {
self.resolve_expr(value)?;
self.resolve_expr(object)?;
}
Expr::Super {
uuid: _uuid,
keyword,
method: _method,
} => {
if *self.current_class_type.borrow() == ClassType::None {
return Err(RuntimeError::SuperOutsideClass { line: keyword.line });
} else if *self.current_class_type.borrow() != ClassType::Subclass {
return Err(RuntimeError::SuperWithoutSuperclass { line: keyword.line });
}

self.resolve_local(input, keyword)?;
}
Expr::This {
uuid: _uuid,
keyword,
Expand Down
4 changes: 4 additions & 0 deletions rlox-lib/src/interpreter/runtime_error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,10 @@ pub enum RuntimeError {
SuperclassSelfInheritance { line: usize },
#[error("{line:?}: Superclass must be a class!")]
SuperclassInvalidType { line: usize },
#[error("{line:?}: Can't use 'super' outside of a class!")]
SuperOutsideClass { line: usize },
#[error("{line:?}: Can't use 'super' in a class with no superclass!")]
SuperWithoutSuperclass { line: usize },
#[error(transparent)]
Return { ret_val: ExprResult },
}
31 changes: 31 additions & 0 deletions rlox-lib/tests/class_inheritance_super.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
mod common;

const INPUT: &str = r###"
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();
"###;

const RESULT: &str = r###"
Fry until golden brown.
Pipe full of custard and coat with chocolate.
"###;

#[test]
fn test_class_inheritance_super() {
assert_eq!(
common::interpret(INPUT).unwrap(),
RESULT.strip_prefix('\n').unwrap()
)
}

0 comments on commit fdb599b

Please sign in to comment.