Skip to content

Commit

Permalink
Merge pull request #54 from froth/inheritance
Browse files Browse the repository at this point in the history
Implement basic inheritance
  • Loading branch information
froth committed Apr 18, 2024
2 parents 9307588 + 344cbfa commit c2ba23c
Show file tree
Hide file tree
Showing 14 changed files with 228 additions and 24 deletions.
19 changes: 16 additions & 3 deletions src/ast/stmt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,10 @@ use std::{fmt::Display, sync::Arc};

use miette::{NamedSource, SourceSpan};

use super::{expr::Expr, name::Name};
use super::{
expr::Expr,
name::{Name, NameExpr},
};

#[derive(Debug, Clone, PartialEq)]
pub struct Stmt {
Expand Down Expand Up @@ -48,13 +51,15 @@ impl Stmt {
pub fn class(
name: String,
methods: Vec<Function>,
superclass: Option<NameExpr>,
location: SourceSpan,
src: Arc<NamedSource<String>>,
) -> Self {
Stmt {
stmt_type: StmtType::Class {
name: name.into(),
methods,
superclass,
},
src,
location,
Expand Down Expand Up @@ -115,6 +120,7 @@ pub enum StmtType {
Class {
name: Name,
methods: Vec<Function>,
superclass: Option<NameExpr>,
},
}

Expand Down Expand Up @@ -184,9 +190,16 @@ impl Display for Stmt {
Function(function) => write!(f, "{function}"),
Return(None) => writeln!(f, "return"),
Return(Some(expr)) => writeln!(f, "return {expr}"),
Class { name, methods } => {
Class {
name,
methods,
superclass,
} => {
write!(f, "class {}", name)?;
writeln!(f, "{{")?;
if let Some(superclass) = superclass {
write!(f, " < {}", superclass.name)?;
}
writeln!(f, " {{")?;
methods.iter().try_for_each(|s| write!(f, "{}", s))?;
writeln!(f, "}}")
}
Expand Down
14 changes: 11 additions & 3 deletions src/interpreter/class.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,16 +39,24 @@ impl Display for Instance {
#[derive(Debug, Clone, PartialEq)]
pub struct Class {
name: Name,
superclass: Option<Box<Class>>,
methods: HashMap<Name, Function>,
}

impl Class {
pub fn new(name: Name, methods: HashMap<Name, Function>) -> Self {
Self { name, methods }
pub fn new(name: Name, superclass: Option<Class>, methods: HashMap<Name, Function>) -> Self {
Self {
name,
superclass: superclass.map(Box::new),
methods,
}
}

pub fn find_method(&self, name: &Name) -> Option<Function> {
self.methods.get(name).cloned()
self.methods
.get(name)
.cloned()
.or(self.superclass.as_ref().and_then(|s| s.find_method(name)))
}

pub fn call(&self, interpreter: &mut Interpreter, arguments: Vec<Value>) -> Result<Value> {
Expand Down
10 changes: 5 additions & 5 deletions src/interpreter/expression.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,12 @@ impl Interpreter {
Grouping(expr) => self.interpret_expr(expr),
Literal(l) => l.interpret(),
Unary(token, expr) => self.interpret_unary(token, expr),
Variable(name) => self.read_variable(name, expr),
Variable(name) => self.read_variable(name),
Assign(name, expr) => self.assign_variable(name, expr),
Call(callee, arguments) => self.call(callee, arguments, expr.location),
Get(object, name) => self.get(object, name, location),
Set(object, name, value) => self.set(object, name, value, location),
This => self.read_variable(&NameExpr::this(location, expr.src.clone()), expr),
This => self.read_variable(&NameExpr::this(location, expr.src.clone())),
}
}

Expand Down Expand Up @@ -68,16 +68,16 @@ impl Interpreter {
}
}

fn read_variable(&self, name: &NameExpr, expr: &Expr) -> Result<Value> {
pub fn read_variable(&self, name: &NameExpr) -> Result<Value> {
let val = if let Some(distance) = self.locals.get(name) {
self.environment.borrow().get_at(*distance, &name.name)
} else {
self.global.borrow().get(&name.name)
};
val.ok_or(UndefinedVariable {
name: name.name.clone(),
src: expr.src.clone(),
location: expr.location,
src: name.src.clone(),
location: name.location,
})
}

Expand Down
9 changes: 9 additions & 0 deletions src/interpreter/runtime_error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,15 @@ pub enum RuntimeError {
#[label("here")]
location: SourceSpan,
},

#[error("Superclass was not a class but {actual}")]
InvalidSuperclass {
actual: Type,
#[source_code]
src: Arc<NamedSource<String>>,
#[label("here")]
location: SourceSpan,
},
}

#[derive(Debug)]
Expand Down
44 changes: 37 additions & 7 deletions src/interpreter/statement.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,18 @@ use std::{cell::RefCell, rc::Rc};

use crate::ast::{
expr::Expr,
name::Name,
name::{Name, NameExpr},
stmt::{self, Stmt, StmtType},
};

use super::{
callable::Callable, class::Class, environment::Environment, function::Function,
runtime_error::RuntimeErrorOrReturn, value::Value, Interpreter, OrReturnResult, Result,
callable::Callable,
class::Class,
environment::Environment,
function::Function,
runtime_error::{RuntimeError, RuntimeErrorOrReturn},
value::Value,
Interpreter, OrReturnResult, Result,
};

impl Interpreter {
Expand Down Expand Up @@ -37,7 +42,11 @@ impl Interpreter {
self.define_function(&function.name, &function.parameters, &function.body)?
}
Return(expr) => self.execute_return(expr)?,
Class { name, methods } => self.define_class(name, methods)?,
Class {
name,
methods,
superclass,
} => self.define_class(name, methods, superclass)?,
};
Ok(())
}
Expand All @@ -64,9 +73,30 @@ impl Interpreter {
Ok(())
}

fn define_class(&mut self, name: &Name, methods: &[stmt::Function]) -> Result<()> {
let mut env = self.environment.borrow_mut();
fn define_class(
&mut self,
name: &Name,
methods: &[stmt::Function],
superclass: &Option<NameExpr>,
) -> Result<()> {
let superclass = superclass
.as_ref()
.map(|s| {
self.read_variable(s).and_then(|value| {
if let Value::Callable(Callable::Class(class)) = value {
Ok(class)
} else {
Err(RuntimeError::InvalidSuperclass {
actual: value.get_type(),
src: s.src.clone(),
location: s.location,
})
}
})
})
.transpose()?;

let mut env = self.environment.borrow_mut();
env.define(name, Value::Nil);
let methods = methods
.iter()
Expand All @@ -83,7 +113,7 @@ impl Interpreter {
)
})
.collect();
let class = Callable::Class(Class::new(name.clone(), methods));
let class = Callable::Class(Class::new(name.clone(), superclass, methods));
env.assign(name, &Value::Callable(class));
Ok(())
}
Expand Down
4 changes: 3 additions & 1 deletion src/interpreter/types.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
#[derive(Debug, strum::Display)]
pub enum Type {
Callable,
Function,
NativeFunction,
Class,
Instance,
String,
Number,
Expand Down
4 changes: 3 additions & 1 deletion src/interpreter/value.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,9 @@ impl Value {

pub fn get_type(&self) -> Type {
match self {
Value::Callable(_) => Type::Callable,
Value::Callable(Callable::Function(_)) => Type::Function,
Value::Callable(Callable::Native(_)) => Type::NativeFunction,
Value::Callable(Callable::Class(_)) => Type::Class,
Value::Instance(_) => Type::Instance,
Value::String(_) => Type::String,
Value::Number(_) => Type::Number,
Expand Down
45 changes: 43 additions & 2 deletions src/parser/declaration.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ use std::vec;

use miette::SourceSpan;

use crate::ast::name::{Name, NameExpr};
use crate::ast::stmt::{Function, Stmt, StmtType};
use crate::ast::token::{Token, TokenType};
use crate::source_span_extensions::SourceSpanExtensions;
Expand Down Expand Up @@ -100,6 +101,25 @@ impl Parser {
if let Identifier(name) = &identifier.token_type {
let name = name.clone();
self.advance();
let superclass = if match_token!(self, Less).is_some() {
let superclass_location = self.peek().location;
if let Identifier(name) = &self.peek().token_type {
let name = Name::new(name.clone());
self.advance();
Some(NameExpr {
name,
location: superclass_location,
src: self.src.clone(),
})
} else {
Err(ExpectedSuperclass {
src: self.src.clone(),
location: superclass_location,
})?
}
} else {
None
};
consume!(self, LeftBrace, |t: &Token| {
ExpectedLeftBrace {
src: t.src.clone(),
Expand All @@ -120,7 +140,13 @@ impl Parser {
}
});
let location = class_location.until(right_brace.location);
Ok(Stmt::class(name, methods, location, self.src.clone()))
Ok(Stmt::class(
name,
methods,
superclass,
location,
self.src.clone(),
))
} else {
Err(ExpectedIdentifier {
src: self.src.clone(),
Expand Down Expand Up @@ -269,7 +295,22 @@ mod test {
let stmt = parse_declaration(tokens).unwrap();
assert_eq!(
stmt.to_string().trim_end(),
"class class_name{\nfun method_name() {\nExpr(nil)\n}\n}"
"class class_name {\nfun method_name() {\nExpr(nil)\n}\n}"
)
}

#[test]
fn parse_superclass() {
let tokens = vec![
token(TokenType::Class),
token(TokenType::Identifier("A".into())),
token(TokenType::Less),
token(TokenType::Identifier("B".into())),
token(TokenType::LeftBrace),
token(TokenType::RightBrace),
token(TokenType::Eof),
];
let stmt = parse_declaration(tokens).unwrap();
assert_eq!(stmt.to_string().trim_end(), "class A < B {\n}")
}
}
7 changes: 7 additions & 0 deletions src/parser/parser_error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,13 @@ pub enum ParserError {
#[label("this one is one too many")]
location: SourceSpan,
},
#[error("Expected superclass name")]
ExpectedSuperclass {
#[source_code]
src: Arc<NamedSource<String>>,
#[label("not an identifier")]
location: SourceSpan,
},
}

#[derive(thiserror::Error, Debug, Diagnostic)]
Expand Down
25 changes: 23 additions & 2 deletions src/resolver/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,11 @@ impl Resolver {
self.resolve_expr(condition)?;
self.resolve_statement(body)
}
Class { name, methods } => self.resolve_class(name, methods),
Class {
name,
methods,
superclass,
} => self.resolve_class(name, methods, superclass),
}
}

Expand Down Expand Up @@ -122,10 +126,27 @@ impl Resolver {
Ok(())
}

fn resolve_class(&mut self, name: &Name, methods: &[Function]) -> Result<()> {
fn resolve_class(
&mut self,
name: &Name,
methods: &[Function],
superclass: &Option<NameExpr>,
) -> Result<()> {
let enclosing_class = std::mem::replace(&mut self.current_class, Some(ClassType::Class));
self.declare(name);
self.define(name);

if let Some(superclass) = superclass {
if superclass.name == *name {
return Err(ResolutionError::SelfInheritance {
src: superclass.src.clone(),
location: superclass.location,
});
} else {
self.resolve_local(superclass)
}
}

self.begin_scope();

self.define(&Name::this());
Expand Down
7 changes: 7 additions & 0 deletions src/resolver/resolution_error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,4 +35,11 @@ pub enum ResolutionError {
#[label("here")]
location: SourceSpan,
},
#[error("A class can't inherit from itself")]
SelfInheritance {
#[source_code]
src: Arc<NamedSource<String>>,
#[label("here")]
location: SourceSpan,
},
}
12 changes: 12 additions & 0 deletions tests/inheritance.lox
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
interpret
class Doughnut {
cook() {
print "Fry until golden brown.";
}
}

class BostonCream < Doughnut {}

BostonCream().cook();
----
Fry until golden brown.
Loading

0 comments on commit c2ba23c

Please sign in to comment.