Skip to content

Commit

Permalink
Implement methods on class instances
Browse files Browse the repository at this point in the history
  • Loading branch information
froth committed Apr 3, 2024
1 parent 31c5109 commit 45b488a
Show file tree
Hide file tree
Showing 13 changed files with 217 additions and 71 deletions.
50 changes: 7 additions & 43 deletions src/interpreter/callable.rs
Original file line number Diff line number Diff line change
@@ -1,14 +1,12 @@
use core::fmt::Display;
use std::{cell::RefCell, rc::Rc};

use crate::ast::{name::Name, stmt::Stmt};

use self::Callable::*;
use super::{
class::{Class, Instance},
environment::Environment,
function::Function,
value::Value,
Interpreter, Result, RuntimeErrorOrReturn,
Interpreter, Result,
};
#[derive(Debug, Clone, PartialEq)]
pub enum Callable {
Expand All @@ -17,37 +15,15 @@ pub enum Callable {
arity: usize,
name: String,
},
Function {
name: Name,
parameters: Vec<Name>,
body: Vec<Stmt>,
closure: Rc<RefCell<Environment>>,
},
Function(Function),
Class(Class),
}

impl Callable {
pub fn call(&self, interpreter: &mut Interpreter, arguments: Vec<Value>) -> Result<Value> {
match self {
Native { function, .. } => function(interpreter, arguments),
Function {
parameters,
body,
closure,
..
} => {
let mut env = Environment::from_parent(closure.clone());
parameters
.iter()
.zip(arguments.iter())
.for_each(|(p, a)| env.define(p, a.clone()));
let result = interpreter.execute_block(body, env);
match result {
Ok(_) => Ok(Value::Nil),
Err(RuntimeErrorOrReturn::Return(value)) => Ok(value),
Err(RuntimeErrorOrReturn::RuntimeError(err)) => Err(err),
}
}
Function(function) => function.call(interpreter, arguments),
Class(class) => Ok(Value::Instance(Rc::new(RefCell::new(Instance::new(
class.clone(),
))))),
Expand All @@ -57,10 +33,7 @@ impl Callable {
pub fn arity(&self) -> usize {
match self {
Native { arity, .. } => *arity,
Function {
parameters: arguments,
..
} => arguments.len(),
Function(function) => function.arity(),
Class(_) => 0,
}
}
Expand All @@ -72,17 +45,8 @@ impl Display for Callable {
Native { name, arity, .. } => {
write!(f, "<native fun {name} ({arity} arguments)>",)
}
Function {
name,
parameters: arguments,
..
} => {
let arity = arguments.len();
write!(f, "<fun {name} ({arity} arguments)>")
}
Class(class) => {
write!(f, "<class {}>", class.name)
}
Function(function) => function.fmt(f),
Class(class) => class.fmt(f),
}
}
}
26 changes: 23 additions & 3 deletions src/interpreter/class.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use std::{collections::HashMap, fmt::Display};

use crate::ast::name::Name;

use super::value::Value;
use super::{function::Function, value::Value};

#[derive(Debug, Clone, PartialEq)]
pub struct Instance {
Expand All @@ -19,7 +19,10 @@ impl Instance {
}

pub fn get(&self, name: &Name) -> Option<Value> {
self.fields.get(name).cloned()
self.fields
.get(name)
.cloned()
.or(self.class.find_method(name))
}

pub fn set(&mut self, name: &Name, value: Value) {
Expand All @@ -35,5 +38,22 @@ impl Display for Instance {

#[derive(Debug, Clone, PartialEq)]
pub struct Class {
pub name: Name,
name: Name,
methods: HashMap<Name, Function>,
}

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

pub fn find_method(&self, name: &Name) -> Option<Value> {
self.methods.get(name).cloned().map(|m| m.into())
}
}

impl Display for Class {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "<class {}>", self.name)
}
}
58 changes: 58 additions & 0 deletions src/interpreter/function.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
use std::{cell::RefCell, fmt::Display, rc::Rc};

use crate::ast::{name::Name, stmt::Stmt};

use super::{
environment::Environment, runtime_error::RuntimeErrorOrReturn, value::Value, Interpreter,
Result,
};

#[derive(Debug, Clone, PartialEq)]
pub struct Function {
name: Name,
parameters: Vec<Name>,
body: Vec<Stmt>,
closure: Rc<RefCell<Environment>>,
}

impl Function {
pub fn new(
name: Name,
parameters: Vec<Name>,
body: Vec<Stmt>,
closure: Rc<RefCell<Environment>>,
) -> Self {
Self {
name,
parameters,
body,
closure,
}
}

pub fn call(&self, interpreter: &mut Interpreter, arguments: Vec<Value>) -> Result<Value> {
let mut env = Environment::from_parent(self.closure.clone());
self.parameters
.iter()
.zip(arguments.iter())
.for_each(|(p, a)| env.define(p, a.clone()));
let result = interpreter.execute_block(&self.body, env);
match result {
Ok(_) => Ok(Value::Nil),
Err(RuntimeErrorOrReturn::Return(value)) => Ok(value),
Err(RuntimeErrorOrReturn::RuntimeError(err)) => Err(err),
}
}

pub fn arity(&self) -> usize {
self.parameters.len()
}
}

impl Display for Function {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let arity = self.parameters.len();
let name = &self.name;
write!(f, "<fun {name} ({arity} parameters)>")
}
}
1 change: 1 addition & 0 deletions src/interpreter/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ mod callable;
mod class;
mod environment;
mod expression;
mod function;
mod literal;
mod native_functions;
pub mod printer;
Expand Down
36 changes: 25 additions & 11 deletions src/interpreter/statement.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,11 @@ use std::{cell::RefCell, rc::Rc};
use crate::ast::{
expr::Expr,
name::Name,
stmt::{Function, Stmt, StmtType},
stmt::{self, Stmt, StmtType},
};

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

Expand Down Expand Up @@ -51,23 +51,37 @@ impl Interpreter {
}

fn define_function(&mut self, name: &Name, parameters: &[Name], body: &[Stmt]) -> Result<()> {
let function = Callable::Function {
name: name.clone(),
parameters: parameters.to_vec(),
body: body.to_vec(),
closure: self.environment.clone(),
};
let function = Function::new(
name.clone(),
parameters.to_vec(),
body.to_vec(),
self.environment.clone(),
);
self.environment
.borrow_mut()
.define(name, Value::Callable(function));
.define(name, Value::Callable(Callable::Function(function)));
Ok(())
}

fn define_class(&mut self, name: &Name, methods: &[Function]) -> Result<()> {
fn define_class(&mut self, name: &Name, methods: &[stmt::Function]) -> Result<()> {
let mut env = self.environment.borrow_mut();

env.define(name, Value::Nil);
let class = Callable::Class(Class { name: name.clone() });
let methods = methods
.iter()
.map(|m| {
(
m.name.clone(),
Function::new(
m.name.clone(),
m.parameters.clone(),
m.body.clone(),
self.environment.clone(),
),
)
})
.collect();
let class = Callable::Class(Class::new(name.clone(), methods));
env.assign(name, &Value::Callable(class));
Ok(())
}
Expand Down
8 changes: 7 additions & 1 deletion src/interpreter/value.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use std::{cell::RefCell, fmt::Display, rc::Rc};

use super::{callable::Callable, class::Instance, types::Type};
use super::{callable::Callable, class::Instance, function::Function, types::Type};

#[derive(Clone, Debug, PartialEq)]
pub enum Value {
Expand Down Expand Up @@ -75,3 +75,9 @@ impl From<&str> for Value {
Value::String(value.to_string())
}
}

impl From<Function> for Value {
fn from(value: Function) -> Self {
Value::Callable(Callable::Function(value))
}
}
11 changes: 5 additions & 6 deletions src/parser/declaration.rs
Original file line number Diff line number Diff line change
Expand Up @@ -49,17 +49,17 @@ impl Parser {
}

fn fun_declaration(&mut self) -> Result<Stmt> {
let (function, location) = self.function()?;
let fun_location = self.advance().location;
let (function, location) = self.function(fun_location)?;
Ok(Stmt {
stmt_type: StmtType::Function(function),
location,
src: self.src.clone(),
})
}

fn function(&mut self) -> Result<(Function, SourceSpan)> {
fn function(&mut self, start_location: SourceSpan) -> Result<(Function, SourceSpan)> {
use TokenType::*;
let fun_location = self.advance().location;
let identifier = self.peek();
if let Identifier(name) = &identifier.token_type {
let name = name.clone();
Expand All @@ -83,7 +83,7 @@ impl Parser {
parameters: parameters.into_iter().map(|arg| arg.into()).collect(),
body: body.stmts,
},
fun_location.until(body.location),
start_location.until(body.location),
))
} else {
Err(ExpectedIdentifier {
Expand All @@ -110,7 +110,7 @@ impl Parser {
let mut methods = vec![];

while !check!(self, RightBrace) && !self.is_at_end() {
methods.push(self.function()?.0)
methods.push(self.function(self.peek().location)?.0)
}

let right_brace = consume!(self, RightBrace, |t: &Token| {
Expand Down Expand Up @@ -256,7 +256,6 @@ mod test {
token(TokenType::Class),
token(TokenType::Identifier(class_name)),
token(TokenType::LeftBrace),
token(TokenType::Fun),
token(TokenType::Identifier(method_name)),
token(TokenType::LeftParen),
token(TokenType::RightParen),
Expand Down
18 changes: 12 additions & 6 deletions src/resolver/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ pub mod resolution_error;
use crate::ast::{
expr::{Expr, ExprType::*},
name::{Name, NameExpr},
stmt::{Stmt, StmtType::*},
stmt::{Function, Stmt, StmtType::*},
};

use self::resolution_error::ResolutionError;
Expand All @@ -19,6 +19,7 @@ pub struct Resolver {
#[derive(Debug)]
enum FunctionType {
Function,
Method,
}

type Result<T> = std::result::Result<T, ResolutionError>;
Expand Down Expand Up @@ -73,11 +74,7 @@ impl Resolver {
self.resolve_expr(condition)?;
self.resolve_statement(body)
}
Class { name, methods: _ } => {
self.declare(name);
self.define(name);
Ok(())
}
Class { name, methods } => self.resolve_class(name, methods),
}
}

Expand Down Expand Up @@ -113,6 +110,15 @@ impl Resolver {
Ok(())
}

fn resolve_class(&mut self, name: &Name, methods: &[Function]) -> Result<()> {
self.declare(name);
self.define(name);
methods.iter().try_for_each(|m| {
self.resolve_function(&m.parameters, &m.body, FunctionType::Method)
})?;
Ok(())
}

fn resolve_expr(&mut self, expression: &Expr) -> Result<()> {
match &expression.expr_type {
Assign(name_expr, expr) => {
Expand Down
2 changes: 1 addition & 1 deletion tests/classes.lox → tests/classes_get_set.lox
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,4 @@ print leo2.name;
<class Dog>
<Dog instance>
Leo
Leo2
Leo2
10 changes: 10 additions & 0 deletions tests/classes_simple_method.lox
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
interpret
class Bacon {
eat() {
print "Crunch crunch crunch!";
}
}

Bacon().eat();
----
Crunch crunch crunch!
Loading

0 comments on commit 45b488a

Please sign in to comment.