Skip to content

Commit

Permalink
Merge pull request #52 from froth/initializers
Browse files Browse the repository at this point in the history
Implement initializers
  • Loading branch information
froth committed Apr 16, 2024
2 parents cf13542 + 99580a4 commit 9307588
Show file tree
Hide file tree
Showing 13 changed files with 184 additions and 36 deletions.
6 changes: 5 additions & 1 deletion src/ast/name.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ impl NameExpr {

#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct Name(String);

const INIT: &str = "init";
impl Name {
pub fn new(name: String) -> Self {
Name(name)
Expand All @@ -32,6 +32,10 @@ impl Name {
pub fn this() -> Self {
Name(TokenType::This.to_string())
}

pub fn init() -> Self {
Name(String::from(INIT))
}
}

impl From<&str> for Name {
Expand Down
23 changes: 7 additions & 16 deletions src/interpreter/callable.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,46 +2,37 @@ use core::fmt::Display;

use self::Callable::*;
use super::{
class::{Class, Instance},
function::Function,
value::Value,
Interpreter, Result,
class::Class, function::Function, native_functions::Native, value::Value, Interpreter, Result,
};
#[derive(Debug, Clone, PartialEq)]
pub enum Callable {
Native {
function: fn(interpreter: &mut Interpreter, arguments: Vec<Value>) -> Result<Value>,
arity: usize,
name: String,
},
Native(Native),
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),
Native(native) => native.call(interpreter, arguments),
Function(function) => function.call(interpreter, arguments),
Class(class) => Ok(Value::Instance(Instance::new(class.clone()))),
Class(class) => class.call(interpreter, arguments),
}
}

pub fn arity(&self) -> usize {
match self {
Native { arity, .. } => *arity,
Native(native) => native.arity(),
Function(function) => function.arity(),
Class(_) => 0,
Class(class) => class.arity(),
}
}
}

impl Display for Callable {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Native { name, arity, .. } => {
write!(f, "<native fun {name} ({arity} arguments)>",)
}
Native(native) => native.fmt(f),
Function(function) => function.fmt(f),
Class(class) => class.fmt(f),
}
Expand Down
16 changes: 15 additions & 1 deletion src/interpreter/class.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use std::{cell::RefCell, collections::HashMap, fmt::Display, rc::Rc};

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

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

#[derive(Debug, Clone, PartialEq)]
pub struct Instance {
Expand Down Expand Up @@ -50,6 +50,20 @@ impl Class {
pub fn find_method(&self, name: &Name) -> Option<Function> {
self.methods.get(name).cloned()
}

pub fn call(&self, interpreter: &mut Interpreter, arguments: Vec<Value>) -> Result<Value> {
let instance = Instance::new(self.clone());
self.find_method(&Name::init())
.map(|i| i.bind(&instance).call(interpreter, arguments))
.transpose()?;
Ok(Value::Instance(instance))
}

pub fn arity(&self) -> usize {
self.find_method(&Name::init())
.map(|m| m.arity())
.unwrap_or(0)
}
}

impl Display for Class {
Expand Down
4 changes: 2 additions & 2 deletions src/interpreter/environment.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use std::{cell::RefCell, collections::HashMap, rc::Rc};

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

use super::{native_functions::native_functions, value::Value};
use super::{callable::Callable, native_functions::native_functions, value::Value};

#[derive(Debug, PartialEq)]
pub struct Environment {
Expand All @@ -29,7 +29,7 @@ impl Environment {
let mut env = Self::new();
native_functions()
.into_iter()
.for_each(|(k, v)| env.define(&k, Value::Callable(v)));
.for_each(|(k, v)| env.define(&k, Value::Callable(Callable::Native(v))));
env
}

Expand Down
14 changes: 14 additions & 0 deletions src/interpreter/function.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ pub struct Function {
parameters: Vec<Name>,
body: Vec<Stmt>,
closure: Rc<RefCell<Environment>>,
is_initializer: bool,
}

impl Function {
Expand All @@ -21,12 +22,14 @@ impl Function {
parameters: Vec<Name>,
body: Vec<Stmt>,
closure: Rc<RefCell<Environment>>,
is_initializer: bool,
) -> Self {
Self {
name,
parameters,
body,
closure,
is_initializer,
}
}

Expand All @@ -38,7 +41,17 @@ impl Function {
.for_each(|(p, a)| env.define(p, a.clone()));
let result = interpreter.execute_block(&self.body, env);
match result {
Ok(_) if self.is_initializer => Ok(self
.closure
.borrow()
.get_at(0, &Name::this())
.unwrap_or(Value::Nil)),
Ok(_) => Ok(Value::Nil),
Err(RuntimeErrorOrReturn::Return(_)) if self.is_initializer => Ok(self
.closure
.borrow()
.get_at(0, &Name::this())
.unwrap_or(Value::Nil)),
Err(RuntimeErrorOrReturn::Return(value)) => Ok(value),
Err(RuntimeErrorOrReturn::RuntimeError(err)) => Err(err),
}
Expand All @@ -57,6 +70,7 @@ impl Function {
parameters: self.parameters,
body: self.body,
closure: Rc::new(RefCell::new(env)),
is_initializer: self.is_initializer,
}
}
}
Expand Down
31 changes: 27 additions & 4 deletions src/interpreter/native_functions.rs
Original file line number Diff line number Diff line change
@@ -1,14 +1,37 @@
use std::{collections::HashMap, time::SystemTime};
use std::{collections::HashMap, fmt::Display, time::SystemTime};

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

use super::{callable::Callable, value::Value, Interpreter, Result};
use super::{value::Value, Interpreter, Result};

pub fn native_functions() -> HashMap<Name, Callable> {
#[derive(Debug, Clone, PartialEq)]
pub struct Native {
pub function: fn(interpreter: &mut Interpreter, arguments: Vec<Value>) -> Result<Value>,
arity: usize,
name: String,
}

impl Native {
pub fn arity(&self) -> usize {
self.arity
}

pub fn call(&self, interpreter: &mut Interpreter, arguments: Vec<Value>) -> Result<Value> {
(self.function)(interpreter, arguments)
}
}

impl Display for Native {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "<native fun {} ({} arguments)>", self.name, self.arity)
}
}

pub fn native_functions() -> HashMap<Name, Native> {
let mut builtins = HashMap::new();
builtins.insert(
"clock".into(),
Callable::Native {
Native {
function: clock,
arity: 0,
name: "clock".to_string(),
Expand Down
2 changes: 2 additions & 0 deletions src/interpreter/statement.rs
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ impl Interpreter {
parameters.to_vec(),
body.to_vec(),
self.environment.clone(),
false,
);
self.environment
.borrow_mut()
Expand All @@ -77,6 +78,7 @@ impl Interpreter {
m.parameters.clone(),
m.body.clone(),
self.environment.clone(),
m.name == Name::init(),
),
)
})
Expand Down
49 changes: 37 additions & 12 deletions src/resolver/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,10 @@ pub struct Resolver {
current_class: Option<ClassType>,
}

#[derive(Debug)]
#[derive(Debug, PartialEq)]
enum FunctionType {
Function,
Initializer,
Method,
}

Expand Down Expand Up @@ -70,16 +71,7 @@ impl Resolver {
self.define(&function.name);
self.resolve_function(&function.parameters, &function.body, FunctionType::Function)
}
Return(expr) => {
if self.current_function.is_none() {
Err(ResolutionError::InvalidReturn {
src: statement.src.clone(),
location: statement.location,
})
} else {
expr.iter().try_for_each(|e| self.resolve_expr(e))
}
}
Return(expr) => self.resolve_return(expr, statement.location, &statement.src),
Block(statements) => self.resolve_block(statements),
If {
condition,
Expand Down Expand Up @@ -138,7 +130,12 @@ impl Resolver {

self.define(&Name::this());
methods.iter().try_for_each(|m| {
self.resolve_function(&m.parameters, &m.body, FunctionType::Method)
let function_type = if m.name == Name::init() {
FunctionType::Initializer
} else {
FunctionType::Method
};
self.resolve_function(&m.parameters, &m.body, function_type)
})?;
self.end_scope();
self.current_class = enclosing_class;
Expand Down Expand Up @@ -203,6 +200,34 @@ impl Resolver {
}
}

fn resolve_return(
&mut self,
expr: &Option<Expr>,
location: SourceSpan,
src: &Arc<NamedSource<String>>,
) -> Result<()> {
if self.current_function.is_none() {
Err(ResolutionError::InvalidReturn {
src: src.clone(),
location,
})
} else {
expr.iter().try_for_each(|e| {
if self
.current_function
.as_ref()
.is_some_and(|c| *c == FunctionType::Initializer)
{
Err(ResolutionError::ReturnInInitializer {
src: src.clone(),
location,
})
} else {
self.resolve_expr(e)
}
})
}
}
fn declare(&mut self, name: &Name) {
if let Some(scope) = self.scopes.last_mut() {
scope.insert(name.clone(), false);
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 @@ -21,6 +21,13 @@ pub enum ResolutionError {
#[label("here")]
location: SourceSpan,
},
#[error("Can't return a value from initializer")]
ReturnInInitializer {
#[source_code]
src: Arc<NamedSource<String>>,
#[label("here")]
location: SourceSpan,
},
#[error("Can't use 'this' outside of a class.")]
InvalidThis {
#[source_code]
Expand Down
19 changes: 19 additions & 0 deletions tests/initializers.lox
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
interpret
class A {
init(a,b) {
this.c = a;
this.d = b;
}

print_me(s) {
print(s);
print(this.c);
print(this.d);
}
}
var x = A(1, false);
x.print_me("s");
----
s
1
false
10 changes: 10 additions & 0 deletions tests/invoke_init_directly.lox
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
interpret
class A {
init(a) {
this.a = a;
}
}
var a = A(1);
print(a.init(5).a);
----
5
26 changes: 26 additions & 0 deletions tests/resolver_errors/return_value_from_initializer.lox
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
error
class A {
init() {
return 0;
}
}
----
----
{
"causes": [],
"filename": "tests/resolver_errors/return_value_from_initializer.lox",
"labels": [
{
"label": "here",
"span": {
"length": 9,
"offset": 31
}
}
],
"message": "Can't return a value from initializer",
"related": [],
"severity": "error"
}
----
---- (no newline)
13 changes: 13 additions & 0 deletions tests/return_from_initialzer.lox
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
interpret
class A {
init(a,b) {
if(a) {
return;
}
this.z = b;
}
}
var x = A(false, 1);
print(x.init(true, 2).z);
----
1

0 comments on commit 9307588

Please sign in to comment.