Skip to content

Commit

Permalink
Implemented initial support for inheritance.
Browse files Browse the repository at this point in the history
  • Loading branch information
jens-siebert committed Feb 8, 2024
1 parent 7001400 commit 47dbbcf
Show file tree
Hide file tree
Showing 7 changed files with 101 additions and 8 deletions.
19 changes: 16 additions & 3 deletions rlox-lib/src/base/expr_result.rs
Original file line number Diff line number Diff line change
Expand Up @@ -168,16 +168,29 @@ impl Callable for LoxFunction {
#[derive(Clone, Debug, PartialEq)]
pub struct LoxClass {
name: Token,
superclass: Box<Option<LoxClass>>,
methods: HashMap<String, LoxFunction>,
}

impl LoxClass {
pub fn new(name: Token, methods: HashMap<String, LoxFunction>) -> Self {
Self { name, methods }
pub fn new(
name: Token,
superclass: Option<LoxClass>,
methods: HashMap<String, LoxFunction>,
) -> Self {
Self {
name,
superclass: Box::new(superclass),
methods,
}
}

pub fn find_method(&self, name: &str) -> Option<&LoxFunction> {
self.methods.get(&name.to_string())
if let Some(sc) = self.superclass.as_ref() {
sc.find_method(name)
} else {
self.methods.get(&name.to_string())
}
}
}

Expand Down
17 changes: 16 additions & 1 deletion rlox-lib/src/base/parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,8 @@ pub enum ParserError {
MissingParameterName { line: usize },
#[error("{line:?}: Expect property name after '.'.")]
MissingPropertyName { line: usize },
#[error("{line:?}: Expect superclass name.")]
MissingSuperclassName { line: usize },
#[error("{line:?}: Invalid assignment target.")]
InvalidAssignmentTarget { line: usize },
}
Expand Down Expand Up @@ -100,6 +102,19 @@ impl Parser {
},
)?;

let superclass = if self.match_token_types(&[TokenType::Less])? {
self.consume(
TokenType::Identifier,
ParserError::MissingSuperclassName {
line: self.peek().unwrap().line,
},
)?;

Some(Expr::variable(self.previous()?))
} else {
None
};

self.consume(
TokenType::LeftBrace,
ParserError::MissingLeftBraceBeforeClassBody {
Expand All @@ -119,7 +134,7 @@ impl Parser {
},
)?;

Ok(Stmt::class(name, methods))
Ok(Stmt::class(name, superclass, methods))
}

fn function(&self) -> Result<Stmt, ParserError> {
Expand Down
4 changes: 3 additions & 1 deletion rlox-lib/src/base/stmt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ pub enum Stmt {
},
Class {
name: Box<Token>,
superclass: Box<Option<Expr>>,
methods: Vec<Stmt>,
},
Expression {
Expand Down Expand Up @@ -46,9 +47,10 @@ impl Stmt {
Stmt::Block { statements }
}

pub fn class(name: Token, methods: Vec<Stmt>) -> Self {
pub fn class(name: Token, superclass: Option<Expr>, methods: Vec<Stmt>) -> Self {
Stmt::Class {
name: Box::new(name),
superclass: Box::new(superclass),
methods,
}
}
Expand Down
18 changes: 16 additions & 2 deletions rlox-lib/src/interpreter/interpreter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -314,7 +314,21 @@ impl Visitor<Stmt, (), RuntimeError> for Interpreter<'_> {
self.fork(Environment::new_enclosing(Rc::clone(&self.environment)));
scoped_interpreter.execute_block(statements)?;
}
Stmt::Class { name, methods } => {
Stmt::Class {
name,
superclass,
methods,
} => {
let class_superclass = if let Some(sc) = superclass.as_ref() {
if let ExprResult::Class(c) = self.evaluate(sc)? {
Some(c)
} else {
None
}
} else {
None
};

self.environment
.borrow_mut()
.define(&name.lexeme, ExprResult::none());
Expand All @@ -338,7 +352,7 @@ impl Visitor<Stmt, (), RuntimeError> for Interpreter<'_> {
})
.collect();

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

self.environment
.borrow_mut()
Expand Down
22 changes: 21 additions & 1 deletion rlox-lib/src/interpreter/resolver.rs
Original file line number Diff line number Diff line change
Expand Up @@ -136,12 +136,32 @@ impl Visitor<Stmt, (), RuntimeError> for Resolver<'_> {
self.resolve_stmts(statements)?;
self.end_scope()
}
Stmt::Class { name, methods } => {
Stmt::Class {
name,
superclass,
methods,
} => {
let enclosing_class = self.current_class_type.replace(ClassType::Class);

self.declare(name)?;
self.define(name);

if let Some(sc) = superclass.as_ref() {
if let Expr::Variable {
uuid: _uuid,
name: sc_name,
} = sc
{
if name.lexeme == sc_name.lexeme {
return Err(RuntimeError::SuperclassSelfInheritance {
line: name.line,
});
}
}

self.resolve_expr(sc)?;
}

self.begin_scope();
if let Some(scope) = self.scopes.borrow_mut().last_mut() {
scope.insert(String::from("this"), true);
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 @@ -37,6 +37,10 @@ pub enum RuntimeError {
ThisOutsideClass { line: usize },
#[error("{line:?}: Can't return a value from an initializer!")]
ReturnValueFromInitializer { line: usize },
#[error("{line:?}: A class can't inherit from itself!")]
SuperclassSelfInheritance { line: usize },
#[error("{line:?}: Superclass must be a class!")]
SuperclassInvalidType { line: usize },
#[error(transparent)]
Return { ret_val: ExprResult },
}
25 changes: 25 additions & 0 deletions rlox-lib/tests/class_inheritance.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
mod common;

const INPUT: &str = r###"
class Doughnut {
cook() {
print "Fry until golden brown.";
}
}
class BostonCream < Doughnut {}
BostonCream().cook();
"###;

const RESULT: &str = r###"
Fry until golden brown.
"###;

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

0 comments on commit 47dbbcf

Please sign in to comment.