Skip to content

Commit

Permalink
Merge pull request #55 from froth/super
Browse files Browse the repository at this point in the history
Implementation of `super` keyword
  • Loading branch information
froth committed Apr 18, 2024
2 parents c2ba23c + d3e5e4c commit b11f956
Show file tree
Hide file tree
Showing 16 changed files with 544 additions and 203 deletions.
2 changes: 2 additions & 0 deletions src/ast/expr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,7 @@ impl Display for Expr {
Get(object, name) => write!(f, "(Get {}.{})", object, name.name),
Set(object, name, value) => write!(f, "(Set {}.{} = {})", object, name.name, value),
This => write!(f, "this"),
Super(method) => write!(f, "(super.{})", method.name),
}
}
}
Expand All @@ -132,6 +133,7 @@ pub enum ExprType {
Get(Box<Expr>, NameExpr),
Set(Box<Expr>, NameExpr, Box<Expr>),
This,
Super(NameExpr),
}

impl ExprType {
Expand Down
12 changes: 12 additions & 0 deletions src/ast/name.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,14 @@ impl NameExpr {
src: src.clone(),
}
}

pub fn super_name(location: SourceSpan, src: Arc<NamedSource<String>>) -> Self {
NameExpr {
name: Name::super_name(),
location,
src: src.clone(),
}
}
}

#[derive(Debug, Clone, PartialEq, Eq, Hash)]
Expand All @@ -33,6 +41,10 @@ impl Name {
Name(TokenType::This.to_string())
}

pub fn super_name() -> Self {
Name(TokenType::Super.to_string())
}

pub fn init() -> Self {
Name(String::from(INIT))
}
Expand Down
57 changes: 52 additions & 5 deletions src/interpreter/expression.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,17 @@
use miette::SourceSpan;

use crate::ast::{
expr::{Expr, ExprType},
name::NameExpr,
token::{Token, TokenType},
use crate::{
ast::{
expr::{Expr, ExprType},
name::{Name, NameExpr},
token::{Token, TokenType},
},
interpreter::runtime_error::RuntimeError,
};

use super::{
literal::LiteralInterpreter, runtime_error::RuntimeError::*, types::Type, value::Value,
callable::Callable, literal::LiteralInterpreter, runtime_error::RuntimeError::*, types::Type,
value::Value,
};
use super::{Interpreter, Result};

Expand All @@ -27,6 +31,7 @@ impl Interpreter {
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())),
Super(method) => self.interpret_super(method, location),
}
}

Expand Down Expand Up @@ -127,6 +132,48 @@ impl Interpreter {
}
}

fn interpret_super(&mut self, method_expr: &NameExpr, location: SourceSpan) -> Result<Value> {
let distance = self
.locals
.get(&NameExpr {
name: Name::super_name(),
location,
src: method_expr.src.clone(),
})
.expect("super local was undefined bug in resolver");
let superclass = self
.environment
.borrow()
.get_at(*distance, &Name::super_name())
.expect("super value not in environment: bug in interpreter");
let superclass = if let Value::Callable(Callable::Class(class)) = superclass {
class
} else {
panic!("super value not a class: bug in interpreter");
};
let object = self
.environment
.borrow()
.get_at(distance - 1, &Name::this())
.expect("object in super call was not in environment: bug in interpreter");
let object = if let Value::Instance(instance) = object {
instance
} else {
panic!("object value not an instance: bug in interpreter");
};
let method = superclass.find_method(&method_expr.name);
let method_name = method_expr.name.clone();
if let Some(method) = method {
Ok(Value::Callable(Callable::Function(method.bind(&object))))
} else {
Err(RuntimeError::UndefinedProperty {
name: method_name,
src: method_expr.src.clone(),
location: method_expr.location,
})
}
}

fn handle_numbers(
&mut self,
left: &Expr,
Expand Down
31 changes: 27 additions & 4 deletions src/interpreter/statement.rs
Original file line number Diff line number Diff line change
Expand Up @@ -96,8 +96,17 @@ impl Interpreter {
})
.transpose()?;

let mut env = self.environment.borrow_mut();
env.define(name, Value::Nil);
self.environment.borrow_mut().define(name, Value::Nil);

if let Some(superclass) = &superclass {
let mut local_env = Environment::from_parent(self.environment.clone());
local_env.define(
&Name::super_name(),
Value::Callable(Callable::Class(superclass.clone())),
);
self.environment = Rc::new(RefCell::new(local_env))
}

let methods = methods
.iter()
.map(|m| {
Expand All @@ -113,8 +122,22 @@ impl Interpreter {
)
})
.collect();
let class = Callable::Class(Class::new(name.clone(), superclass, methods));
env.assign(name, &Value::Callable(class));
let class = Callable::Class(Class::new(name.clone(), superclass.clone(), methods));

if superclass.is_some() {
let parent = self
.environment
.borrow()
.parent
.as_ref()
.expect("created above")
.clone();
self.environment = parent;
}

self.environment
.borrow_mut()
.assign(name, &Value::Callable(class));
Ok(())
}

Expand Down
47 changes: 47 additions & 0 deletions src/parser/expression.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
use miette::SourceSpan;

use crate::ast::expr::Expr;
use crate::ast::literal::Literal;
use crate::ast::name::{Name, NameExpr};
Expand Down Expand Up @@ -208,6 +210,7 @@ impl Parser {
location: token.location,
src: self.src.clone(),
},
Super => self.parse_super(token.location)?,
Eof => Err(UnexpectedEof {
src: token.src.clone(),
location: (
Expand All @@ -223,6 +226,37 @@ impl Parser {
};
Ok(expr)
}

fn parse_super(&mut self, super_location: SourceSpan) -> Result<Expr> {
consume!(self, TokenType::Dot, |t: &Token| {
ExpectedDot {
src: t.src.clone(),
location: self.previous_if_eof(t.location),
}
});

let method = if let TokenType::Identifier(name) = &self.peek().token_type {
let location = self.peek().location;
let name = name.clone().into();
self.advance();
NameExpr {
name,
location,
src: self.src.clone(),
}
} else {
return Err(ExpectedIdentifier {
src: self.src.clone(),
location: self.peek().location,
});
};
let location = super_location.until(method.location);
Ok(Expr {
expr_type: ExprType::Super(method),
location,
src: self.src.clone(),
})
}
}

#[cfg(test)]
Expand Down Expand Up @@ -449,4 +483,17 @@ mod test {
"(Set (variable object).name = (true))"
)
}

#[test]
fn parse_super() {
let name: String = "name".into();
let tokens = vec![
token(TokenType::Super),
token(TokenType::Dot),
token(TokenType::Identifier(name.clone())),
token(TokenType::Eof),
];
let expr = parse_expr(tokens).unwrap();
assert_eq!(expr.to_string().trim_end(), "(super.name)")
}
}
8 changes: 8 additions & 0 deletions src/parser/parser_error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,14 @@ pub enum ParserError {
location: SourceSpan,
},

#[error("Expected '.'")]
ExpectedDot {
#[source_code]
src: Arc<NamedSource<String>>,
#[label("here")]
location: SourceSpan,
},

#[error("Invalid assignment target")]
InvalidAssignmentTarget {
#[source_code]
Expand Down
93 changes: 93 additions & 0 deletions src/resolver/expr.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
use std::sync::Arc;

use miette::{NamedSource, SourceSpan};

use super::{resolution_error::ResolutionError, ClassType, Resolver, Result};
use crate::ast::{
expr::{Expr, ExprType::*},
name::NameExpr,
};

impl Resolver {
pub(super) fn resolve_expr(&mut self, expression: &Expr) -> Result<()> {
match &expression.expr_type {
Assign(name_expr, expr) => {
self.resolve_expr(expr)?;
self.resolve_local(name_expr);
Ok(())
}
Binary(lhs, _, rhs) => {
self.resolve_expr(lhs)?;
self.resolve_expr(rhs)
}
Logical(lhs, _, rhs) => {
self.resolve_expr(lhs)?;
self.resolve_expr(rhs)
}
Grouping(expr) => self.resolve_expr(expr),
Literal(_) => Ok(()),
Unary(_, expr) => self.resolve_expr(expr),
Variable(name_expr) => self.resolve_var_expr(name_expr),
Call(name, arguments) => {
self.resolve_expr(name)?;
arguments.iter().try_for_each(|e| self.resolve_expr(e))
}
Get(expr, _) => self.resolve_expr(expr),
Set(expr, _, object) => {
self.resolve_expr(expr)?;
self.resolve_expr(object)
}
This => self.resolve_this(expression.location, &expression.src),
Super(_) => self.resolve_super(expression.location, &expression.src),
}
}

fn resolve_var_expr(&mut self, name_expr: &NameExpr) -> Result<()> {
if let Some(false) = self.scopes.last().and_then(|s| s.get(&name_expr.name)) {
Err(ResolutionError::InitializedWithSelf {
name: name_expr.name.clone(),
src: name_expr.src.clone(),
location: name_expr.location,
})
} else {
self.resolve_local(name_expr);
Ok(())
}
}

fn resolve_this(&mut self, location: SourceSpan, src: &Arc<NamedSource<String>>) -> Result<()> {
if self.current_class.is_none() {
Err(ResolutionError::InvalidThis {
src: src.clone(),
location,
})
} else {
let name_expr = NameExpr::this(location, src.clone());
self.resolve_local(&name_expr);
Ok(())
}
}

fn resolve_super(
&mut self,
location: SourceSpan,
src: &Arc<NamedSource<String>>,
) -> Result<()> {
use ResolutionError::*;
match self.current_class {
None => Err(SuperOutsideClass {
src: src.clone(),
location,
}),
Some(ClassType::Class) => Err(SuperWithoutSuperclass {
src: src.clone(),
location,
}),
Some(ClassType::Subclass) => {
let name_expr = NameExpr::super_name(location, src.clone());
self.resolve_local(&name_expr);
Ok(())
}
}
}
}
Loading

0 comments on commit b11f956

Please sign in to comment.