From 719a0149772619b0ed1b236a9c99309bd7953283 Mon Sep 17 00:00:00 2001 From: Moritz Gmeiner Date: Sat, 28 Jan 2023 14:19:12 +0100 Subject: [PATCH] Lox Interpreter done (Chapter 13) --- GRAMMAR | 11 ++- frontend/src/parser/error.rs | 6 +- frontend/src/parser/expr.rs | 76 +++++++++++++-- frontend/src/parser/parse.rs | 146 ++++++++++++++++++++-------- frontend/src/parser/stmt.rs | 11 --- interpreter/src/class.rs | 130 ++++++++++++++++++++++++- interpreter/src/environment.rs | 13 +-- interpreter/src/error.rs | 12 ++- interpreter/src/function.rs | 66 +++++++++---- interpreter/src/interpret.rs | 121 +++++++++++++++++++++-- interpreter/src/lib.rs | 2 + interpreter/src/lox_std.rs | 105 ++++++++------------ interpreter/src/object.rs | 104 ++++++++++++++++++++ interpreter/src/resolver/error.rs | 6 +- interpreter/src/resolver/resolve.rs | 66 +++++++++++-- interpreter/src/value.rs | 28 ++---- 16 files changed, 707 insertions(+), 196 deletions(-) create mode 100644 interpreter/src/object.rs diff --git a/GRAMMAR b/GRAMMAR index 7bb225c..3d6e8b9 100644 --- a/GRAMMAR +++ b/GRAMMAR @@ -23,25 +23,26 @@ declaration -> var_decl | fun_decl | class_decl ; var_decl -> "var" IDENTIFIER ( "=" expression )? ";" fun_decl -> "fun" IDENTIFIER "(" parameters ")" block ; -class_decl -> "class" IDENTIFIER "{" method* "}" ; +class_decl -> "class" IDENTIFIER ( "<" IDENTIFIER ) "{" method* "}" ; method -> IDENTIFIER "(" parameters ")" block ; expression -> assignment -assignment -> IDENTIFIER "=" assignment | logic_or ; +assignment -> ( call_or_get "." )? IDENTIFIER "=" assignment | logic_or ; logic_or -> logic_and ( "or" logic_and )* ; logic_and -> equality ( "and" equality )* ; equality -> comparison ( ( "==" | "!=" ) comparison )* ; comparison -> term ( ">" | ">=" | "<" | "<=" term )* ; term -> factor ( ( "+" | "-" ) factor )* factor -> unary ( ( "*" | "/" ) unary )* ; -unary -> ( "!" | "-" ) unary | call ; +unary -> ( "!" | "-" ) unary | call_or_get ; -call -> primary ( "(" arguments? ")" )* ; +call_or_get -> primary ( "(" arguments? ")" | "." IDENTIFIER )* ; arguments -> expression ( "," expression )* ; -primary -> "(" expression ")" | IDENTIFIER | lambda | NUMBER | STRING | "true" | "false" | "nil" ; +primary -> "(" expression ")" | IDENTIFIER | lambda | NUMBER | STRING | "true" | "false" | "nil" + | "this" | "super" "." IDENTIFIER ; lambda -> "fun" "(" parameters ")" block ; diff --git a/frontend/src/parser/error.rs b/frontend/src/parser/error.rs index a28c982..47216f1 100644 --- a/frontend/src/parser/error.rs +++ b/frontend/src/parser/error.rs @@ -37,9 +37,13 @@ pub enum ParserError { #[error("Function declaration at {code_pos} has too many parameters")] TooManyParams { code_pos: CodePos }, #[error("Return statement outside of function definition")] - InvalidReturn { code_pos: CodePos }, + ReturnOutsideFunction { code_pos: CodePos }, #[error("Break statement outside of loop")] InvalidBreak { code_pos: CodePos }, #[error("Missing class body at {code_pos}")] MissingClassBody { code_pos: CodePos }, + #[error("Return statement in init")] + ReturnInInit { code_pos: CodePos }, + #[error("Missing method name after super")] + MissingMethodAfterSuper { code_pos: CodePos }, } diff --git a/frontend/src/parser/expr.rs b/frontend/src/parser/expr.rs index 3159b32..d0d0f22 100644 --- a/frontend/src/parser/expr.rs +++ b/frontend/src/parser/expr.rs @@ -45,12 +45,32 @@ pub enum Expr { callee: Box, args: Vec, }, - Function { + Get { + target: Box, name: String, - param_names: Vec, - closure_vars: Vec<(String, usize)>, + }, + Set { + target: Box, + name: String, + value: Box, + }, + Function { + name: Box, + param_names: Box>, + closure_vars: Box>, body: Box, }, + Class { + superclass: Option>, + name: String, + methods: Box>, + }, + This, + Super { + super_var: Box, + this_var: Box, + method: String, + }, } impl Expr { @@ -110,15 +130,36 @@ impl Expr { Expr::Call { callee, args } } + pub fn get(target: Expr, name: impl Into) -> Self { + let target = Box::new(target); + let name = name.into(); + Expr::Get { target, name } + } + pub fn function(name: String, param_names: Vec, body: Stmt) -> Self { + let name = Box::new(name); + let param_names = Box::new(param_names); + #[allow(clippy::box_default)] + let closure_vars = Box::new(Vec::new()); let body = Box::new(body); Self::Function { name, param_names, - closure_vars: Vec::new(), + closure_vars, body, } } + + pub fn class(name: impl Into, methods: Vec, superclass: Option) -> Self { + let superclass = superclass.map(Box::new); + let name = name.into(); + let methods = Box::new(methods); + Expr::Class { + superclass, + name, + methods, + } + } } impl Display for Expr { @@ -136,6 +177,8 @@ impl Display for Expr { Expr::GlobalVariable { name } => write!(f, "(var {name} global)"), Expr::Assignment { target, value } => write!(f, "{target} = {value}"), Expr::Call { callee, args } => write!(f, "({callee} {})", args.iter().map(|arg| arg.to_string()).join(" ")), + Expr::Get { target, name } => write!(f, "(get {name} {target})"), + Expr::Set { target, name, value } => write!(f, "(set {name} {target} {value})"), Expr::Function { name, param_names, @@ -145,11 +188,32 @@ impl Display for Expr { if !closure_vars.is_empty() { let closure_fmt = closure_vars.iter().map(|(name, _level)| name).join(", "); - write!(f, "fun [{closure_fmt}] {name}({}) {body}", param_names.join(", ")) + write!(f, "fun [{closure_fmt}] {name}({}) => {body}", param_names.join(", ")) } else { - write!(f, "fun {name}({}) {body}", param_names.join(", ")) + write!(f, "fun {name}({}) => {body}", param_names.join(", ")) } } + Expr::Class { + superclass, + name, + methods, + } => { + if let Some(superclass) = superclass { + writeln!(f, "class {name} < {superclass} {{")?; + } else { + writeln!(f, "class {name} {{")?; + } + for method in methods.iter() { + writeln!(f, "{method}")?; + } + write!(f, "}}") + } + Expr::This => write!(f, "this"), + Expr::Super { + super_var: _, + this_var: _, + method, + } => write!(f, "super.{method}"), } } } diff --git a/frontend/src/parser/parse.rs b/frontend/src/parser/parse.rs index 2f0861d..f814f7f 100644 --- a/frontend/src/parser/parse.rs +++ b/frontend/src/parser/parse.rs @@ -62,8 +62,10 @@ struct Parser { parse_errors: Vec, - is_in_function: bool, is_in_loop: bool, + is_in_class: bool, + is_in_function: bool, + is_in_init: bool, } impl Parser { @@ -71,8 +73,10 @@ impl Parser { Parser { token_iter: TokenIter::new(tokens), parse_errors: Vec::new(), - is_in_function: false, is_in_loop: false, + is_in_class: false, + is_in_function: false, + is_in_init: false, } } @@ -112,6 +116,12 @@ impl Parser { return; } + // when synchronising: assume all false + self.is_in_loop = false; + self.is_in_class = false; + self.is_in_function = false; + self.is_in_init = false; + let peek_token = self.peek_token(); // if we match a synchronisation point: return @@ -158,23 +168,43 @@ impl Parser { } Ok(Stmt::Break) } - TokenType::Return => { - let code_pos = self.peek_token().code_pos; - assert_eq!(self.next_token().token_type, TokenType::Return); - let expr = match self.peek_token().token_type { - TokenType::Semicolon => Expr::nil(), - _ => self.expression()?, - }; - self.semicolon()?; - if !self.is_in_function { - return Err(ParserError::InvalidReturn { code_pos }); - } - Ok(Stmt::return_stmt(expr)) - } + TokenType::Return => self.return_statement(), _ => self.expression_statement(), } } + fn return_statement(&mut self) -> ParserResult { + let code_pos = self.peek_token().code_pos; + assert_eq!(self.next_token().token_type, TokenType::Return); + + let expr = match self.peek_token().token_type { + TokenType::Semicolon => { + if !self.is_in_init { + Expr::nil() + } else { + Expr::Variable { + name: "this".to_owned(), + } + } + } + _ => { + if !self.is_in_init { + self.expression()? + } else { + return Err(ParserError::ReturnInInit { code_pos }); + } + } + }; + + self.semicolon()?; + + if !self.is_in_function { + return Err(ParserError::ReturnOutsideFunction { code_pos }); + } + + Ok(Stmt::return_stmt(expr)) + } + fn if_statement(&mut self) -> ParserResult { assert_eq!(self.next_token().token_type, TokenType::If); @@ -217,13 +247,7 @@ impl Parser { let is_in_loop = std::mem::replace(&mut self.is_in_loop, true); - let body = match self.statement() { - Ok(body) => body, - Err(err) => { - self.is_in_loop = is_in_loop; - return Err(err); - } - }; + let body = self.statement()?; self.is_in_loop = is_in_loop; @@ -264,13 +288,7 @@ impl Parser { let is_in_loop = std::mem::replace(&mut self.is_in_loop, true); - let mut body = match self.statement() { - Ok(body) => body, - Err(err) => { - self.is_in_loop = is_in_loop; - return Err(err); - } - }; + let mut body = self.statement()?; self.is_in_loop = is_in_loop; @@ -333,10 +351,24 @@ impl Parser { let name = self.identifier("Missing class name")?; + let superclass = if self.peek_token().token_type == TokenType::Less { + assert_eq!(self.next_token().token_type, TokenType::Less); + + let superclass_name = self.identifier("Expected superclass")?; + + Some(Expr::Variable { name: superclass_name }) + } else { + None + }; + self.consume_token(TokenType::LeftBrace, |token| ParserError::MissingClassBody { code_pos: token.code_pos, })?; + let is_in_loop = std::mem::replace(&mut self.is_in_loop, false); + let is_in_class = std::mem::replace(&mut self.is_in_class, true); + let is_in_function = std::mem::replace(&mut self.is_in_function, false); + let mut methods = Vec::new(); while self.peek_token().token_type != TokenType::RightBrace { @@ -350,14 +382,27 @@ impl Parser { } })?; + let is_in_init = self.is_in_init; + if method_name == "init" { + self.is_in_init = true; + } + let method = self.fun_params_and_body(method_name)?; + self.is_in_init = is_in_init; + methods.push(method); } assert_eq!(self.next_token().token_type, TokenType::RightBrace); - Ok(Stmt::Class { name, methods }) + self.is_in_loop = is_in_loop; + self.is_in_class = is_in_class; + self.is_in_function = is_in_function; + + let class = Expr::class(&name, methods, superclass); + + Ok(Stmt::var_decl(name, class)) } fn fun_declaration(&mut self) -> ParserResult { @@ -399,14 +444,7 @@ impl Parser { let is_in_function = std::mem::replace(&mut self.is_in_function, true); let is_in_loop = std::mem::replace(&mut self.is_in_loop, false); - let body = match self.block() { - Ok(body) => body, - Err(err) => { - self.is_in_function = is_in_function; - self.is_in_loop = is_in_loop; - return Err(err); - } - }; + let body = self.block()?; self.is_in_function = is_in_function; self.is_in_loop = is_in_loop; @@ -494,6 +532,10 @@ impl Parser { match expr { Expr::Variable { name } => Ok(Expr::assignment(Expr::Variable { name }, value)), + Expr::Get { target, name } => { + let value = Box::new(value); + Ok(Expr::Set { target, name, value }) + } _ => Err(ParserError::InvalidAssignment { expr, code_pos }), } } @@ -625,11 +667,11 @@ impl Parser { let _ = self.next_token(); Ok(Expr::unary(UnaryOp::Negate, self.unary()?)) } - _ => self.call(), + _ => self.call_or_get(), } } - fn call(&mut self) -> ParserResult { + fn call_or_get(&mut self) -> ParserResult { let mut expr = self.primary()?; loop { @@ -647,7 +689,13 @@ impl Parser { expr = Expr::call(expr, args); } - TokenType::Dot => todo!(), + TokenType::Dot => { + assert_eq!(self.next_token().token_type, TokenType::Dot); + + let name = self.identifier("Expected property name after dot")?; + + expr = Expr::get(expr, name); + } _ => break, } } @@ -695,6 +743,22 @@ impl Parser { TokenType::False => Ok(Expr::bool(false)), TokenType::True => Ok(Expr::bool(true)), TokenType::Nil => Ok(Expr::nil()), + TokenType::This => Ok(Expr::This), + TokenType::Super => { + self.consume_token(TokenType::Dot, |token| ParserError::MissingMethodAfterSuper { + code_pos: token.code_pos, + })?; + let method = self.identifier("Expected method name after super")?; + let super_var = Box::new(Expr::Variable { + name: "super".to_owned(), + }); + let this_var = Box::new(Expr::This); + Ok(Expr::Super { + super_var, + this_var, + method, + }) + } TokenType::LeftParen => { let expr = self.expression()?; diff --git a/frontend/src/parser/stmt.rs b/frontend/src/parser/stmt.rs index bd90e79..dc7299b 100644 --- a/frontend/src/parser/stmt.rs +++ b/frontend/src/parser/stmt.rs @@ -24,10 +24,6 @@ pub enum Stmt { Block { statements: Vec, }, - Class { - name: String, - methods: Vec, - }, ExprStmt { expr: Box, }, @@ -122,13 +118,6 @@ impl Display for Stmt { } write!(f, "}}") } - Stmt::Class { name, methods } => { - writeln!(f, "class {name} {{")?; - for method in methods { - writeln!(f, "{method}")?; - } - write!(f, "}}") - } Stmt::ExprStmt { expr } => write!(f, "{expr};"), Stmt::Break => write!(f, "break;"), Stmt::Return { expr } => { diff --git a/interpreter/src/class.rs b/interpreter/src/class.rs index 9efb3ee..304ecce 100644 --- a/interpreter/src/class.rs +++ b/interpreter/src/class.rs @@ -1,19 +1,141 @@ +use std::collections::HashMap; use std::fmt::Display; +use std::rc::Rc; -#[derive(Debug)] +use rlox2_frontend::parser::{Expr, Stmt}; + +use crate::{LoxFunction, LoxReference, Value}; + +#[derive(Debug, Clone)] pub struct LoxClass { + superclass: Option>, + name: String, + + methods: HashMap, } +/// Representation of a class in Lox. Always behind an Rc to ensure uniqueness. Should never be handled raw. impl LoxClass { - pub fn new(name: impl Into) -> Self { + pub fn new(name: impl Into, methods: HashMap, superclass: Option>) -> Rc { let name = name.into(); - LoxClass { name } + let mut methods = methods; + + // if class has an init method: insert an implicit "return this;" at its end + if let Some(Value::Function(init)) = methods.get("init") { + let name = init.name().to_owned(); + let closure = init.closure().clone(); + let param_names = init.param_names().to_vec(); + let mut body = init.body().clone(); + + if let Stmt::Block { ref mut statements } = body { + statements.push(Stmt::return_stmt(Expr::LocalVariable { + name: "this".to_owned(), + level: 1, + })); + } else { + panic!("Body of init method of class {name} wasn't a block"); + } + + let new_init = Value::function(LoxFunction::new(name, closure, param_names, body)); + methods.insert("init".to_owned(), new_init); + } + + if let Some(ref superclass) = superclass { + let mut new_methods: HashMap = HashMap::new(); + + // Rc is immutable, so we need to drain, change, and replace + for (name, value) in methods { + if let Value::Function(method_ref) = value { + let superclass = superclass.clone(); + let mut method = method_ref.as_ref().clone(); + + method.inject_closed_var("super", Value::Class(superclass)); + + new_methods.insert(name, Value::function(method)); + } + } + + methods = new_methods; + } + + Rc::new(LoxClass { + superclass, + name, + methods, + }) } + + pub fn superclass(&self) -> Option> { + self.superclass.clone() + } + + pub fn name(&self) -> &str { + &self.name + } + + pub fn arity(&self) -> usize { + if let Some(Value::Function(method)) = self.methods.get("init") { + return method.arity(); + } + + /* if let Some(superclass) = self.superclass { + return superclass.arity(); + } */ + + 0 + } + + pub fn get_method(&self, name: &str, this: LoxReference) -> Option { + /* self.methods + .get(name) + .cloned() + .or_else(|| self.superclass.as_ref().and_then(|cls| cls.get_method(name))) */ + + if let Some(method) = self.methods.get(name) { + match method { + Value::Function(method) => { + let mut method = method.as_ref().clone(); + method.inject_closed_var("this", Value::Object(this)); + return Some(Value::function(method)); + } + method => panic!("method {name} on class {self} is {method}"), + } + } + + if let Some(ref superclass) = self.superclass { + return superclass.get_method(name, this); + } + + None + } + + /* pub fn init(&self, args: Vec, env: &mut Environment) -> EvalResult<()> { + if let Some(ref superclass) = self.superclass { + let args = args.clone(); + superclass.init(args, env)?; + } + + if let Some(Value::Function(method)) = self.get_method("init") { + method.call(args, env)?; + } + + Ok(()) + } */ } impl Display for LoxClass { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "", self.name) + write!(f, "", self.name, (self as *const LoxClass) as usize) + } +} + +// slightly hacky: two classes are equal if they are stored at the same location +// since a LoxClass is always (supposed to be) behind an Rc anyways, we might as well compare their pointers +impl PartialEq for LoxClass { + fn eq(&self, other: &Self) -> bool { + let self_ptr = self as *const LoxClass; + let other_ptr = other as *const LoxClass; + self_ptr == other_ptr } } diff --git a/interpreter/src/environment.rs b/interpreter/src/environment.rs index cb3c48d..f118e5f 100644 --- a/interpreter/src/environment.rs +++ b/interpreter/src/environment.rs @@ -56,10 +56,6 @@ impl<'a> Environment<'a> { } } - // DEBUG - println!("Name {name} not defined at level {level}"); - println!("{self}"); - Err(RuntimeError::NameNotDefined { name: name.to_owned() }) // self.runtime.get_global(name) } @@ -101,14 +97,19 @@ impl<'a> Environment<'a> { let mut closure_scope = Scope::new(); for (name, level) in closure_vars { + // special injected variables + if name == "this" || name == "super" { + continue; + } + let heap_value = self .local_scopes .iter() .rev() .nth(*level) - .unwrap() + .expect("Closure variable got resolved to level that doesn't exist") .get(name) - .unwrap() + .expect("Closure variable was resolved, but could not be found") .clone(); let name = name.to_owned(); diff --git a/interpreter/src/error.rs b/interpreter/src/error.rs index e62f536..6efdf5b 100644 --- a/interpreter/src/error.rs +++ b/interpreter/src/error.rs @@ -1,10 +1,12 @@ +use std::rc::Rc; + use itertools::Itertools; use rlox2_frontend::lexer::LexerError; use rlox2_frontend::parser::{BinaryOp, ParserError, UnaryOp}; use thiserror::Error; -use crate::ResolverError; use crate::Value; +use crate::{LoxClass, ResolverError}; #[derive(Error, Debug)] pub enum RuntimeError { @@ -30,6 +32,14 @@ pub enum RuntimeError { Return { value: Value }, #[error("Exit with exit code {exit_code}")] Exit { exit_code: i32 }, + #[error("Only objects have attributes")] + InvalidGetTarget, + #[error("Only objects have attributes")] + InvalidSetTarget, + #[error("Class {0} has no property {name}", class.name())] + UndefinedAttribute { class: Rc, name: String }, + #[error("{value} is not a valid superclass")] + InvalidSuperclass { value: Value }, } #[derive(Error, Debug)] diff --git a/interpreter/src/function.rs b/interpreter/src/function.rs index 722c447..6aebe57 100644 --- a/interpreter/src/function.rs +++ b/interpreter/src/function.rs @@ -1,7 +1,10 @@ use std::fmt::{Debug, Display}; +use std::rc::Rc; use rlox2_frontend::parser::Stmt; +use crate::value::HeapedValue; + use super::environment::{Environment, Scope}; use super::interpret::EvalResult; use super::{Runtime, Value}; @@ -15,19 +18,16 @@ pub struct LoxFunction { } impl LoxFunction { - pub fn new(name: impl Into, closure: Scope, param_names: Vec, body: Stmt) -> Self { + pub fn new(name: impl Into, closure: Scope, param_names: Vec, body: Stmt) -> Rc { let name = name.into(); let body = Box::new(body); - LoxFunction { + let fun = LoxFunction { name, closure, param_names, body, - } - } - - pub fn arity(&self) -> usize { - self.param_names.len() + }; + Rc::new(fun) } pub fn name(&self) -> &str { @@ -38,13 +38,23 @@ impl LoxFunction { &self.closure } - pub fn param_names(&self) -> impl Iterator { - self.param_names.iter() + pub fn param_names(&self) -> &[String] { + &self.param_names } pub fn body(&self) -> &Stmt { &self.body } + + pub fn arity(&self) -> usize { + self.param_names().len() + } + + pub fn inject_closed_var(&mut self, name: impl Into, value: Value) { + let name = name.into(); + let heaped_value = HeapedValue::new(value); + self.closure.insert(name, heaped_value); + } } impl Display for LoxFunction { @@ -53,6 +63,16 @@ impl Display for LoxFunction { } } +// slightly hacky: two functions are equal if they are stored at the same location +// since a LoxFunction is always (supposed to be) behind an Rc anyways, we might as well compare their pointers +impl PartialEq for LoxFunction { + fn eq(&self, other: &Self) -> bool { + let self_ptr = self as *const LoxFunction; + let other_ptr = other as *const LoxFunction; + self_ptr == other_ptr + } +} + /*====================================================================================================================*/ pub type ExternFunClosure = fn(Vec, &mut Environment) -> EvalResult; @@ -60,15 +80,27 @@ pub type ExternFunClosure = fn(Vec, &mut Environment) -> EvalResult, closure: ExternFunClosure) -> Self { + pub fn new(name: impl Into, arity: usize, closure: ExternFunClosure) -> Self { let name = name.into(); - LoxExternFunction { name, closure } + LoxExternFunction { name, arity, closure } + } + + pub fn name(&self) -> &str { + &self.name + } + + pub fn arity(&self) -> usize { + self.arity + } + + pub fn closure(&self) -> ExternFunClosure { + self.closure } pub fn register(self, env: &mut Runtime) { @@ -76,10 +108,6 @@ impl LoxExternFunction { let fun = Value::extern_function(self); env.define_global(name, fun); } - - pub fn call(&self, args: Vec, env: &mut Environment) -> EvalResult { - (self.closure)(args, env) - } } impl Display for LoxExternFunction { @@ -97,8 +125,12 @@ impl Debug for LoxExternFunction { } } +// slightly hacky: two extern functions are equal if they are stored at the same location +// since a LoxExternFunction is always (supposed to be) behind an Rc anyways, we might as well compare their pointers impl PartialEq for LoxExternFunction { fn eq(&self, other: &Self) -> bool { - self.name == other.name + let self_ptr = self as *const LoxExternFunction; + let other_ptr = other as *const LoxExternFunction; + self_ptr == other_ptr } } diff --git a/interpreter/src/interpret.rs b/interpreter/src/interpret.rs index 3a2780b..fcf995e 100644 --- a/interpreter/src/interpret.rs +++ b/interpreter/src/interpret.rs @@ -1,9 +1,11 @@ +use std::collections::HashMap; use std::rc::Rc; use rlox2_frontend::parser::{BinaryOp, Expr, Literal, LogicalOp, Stmt, UnaryOp}; use crate::error::RuntimeError; -use crate::LoxClass; +use crate::function::LoxExternFunction; +use crate::{LoxClass, LoxReference}; use super::environment::Environment; use super::{LoxFunction, Runtime, Value}; @@ -133,20 +135,89 @@ impl Eval for Expr { match callee { Value::Function(fun) => fun.call(args, env), Value::ExternFunction(ext_fun) => ext_fun.call(args, env), + Value::Class(class) => LoxClass::call(class, args, env), _ => Err(RuntimeError::NotCallable { callee }), } } + Expr::Get { target, name } => { + let target = target.eval(env)?; + + if let Value::Object(object) = target { + object.get(name).ok_or_else(|| { + let class = object.class(); + let name = name.to_owned(); + RuntimeError::UndefinedAttribute { class, name } + }) + } else { + Err(RuntimeError::InvalidGetTarget) + } + } + Expr::Set { target, name, value } => { + let target = target.eval(env)?; + + if let Value::Object(mut object) = target { + let value = value.eval(env)?; + object.set(name, value); + Ok(Value::Nil) + } else { + Err(RuntimeError::InvalidSetTarget) + } + } Expr::Function { name, param_names, closure_vars, body, } => Ok(Value::function(LoxFunction::new( - name, + name.as_ref(), env.collect_closure(closure_vars), - param_names.clone(), + param_names.as_ref().clone(), body.as_ref().clone(), ))), + Expr::Class { + superclass, + name, + methods: method_exprs, + } => { + let superclass = match superclass.as_ref().map(|expr| expr.eval(env)) { + Some(Ok(Value::Class(superclass))) => Some(superclass), + Some(Ok(value)) => return Err(RuntimeError::InvalidSuperclass { value }), + Some(Err(err)) => return Err(err), + None => None, + }; + + let mut methods: HashMap = HashMap::new(); + + // this is the scope "this" will get injected in + env.enter_scope(); + + for method_expr in method_exprs.iter() { + let method = method_expr.eval(env)?; + if let Value::Function(ref fun) = method { + let name = fun.name().to_owned(); + methods.insert(name, method); + } + } + + env.exit_scope(); + + Ok(Value::class(LoxClass::new(name, methods, superclass))) + } + Expr::This => panic!("Unresolved this"), + Expr::Super { + super_var, + this_var, + method, + } => match (super_var.eval(env)?, this_var.eval(env)?) { + (Value::Class(superclass), Value::Object(this)) => { + if let Some(method) = superclass.get_method(method, this) { + Ok(method) + } else { + Err(RuntimeError::InvalidGetTarget) + } + } + (super_val, this_val) => panic!("super evaluated to {super_val} and this evaluated to {this_val}"), + }, } } } @@ -196,13 +267,6 @@ impl Eval for Stmt { } env.exit_scope(); } - Stmt::Class { name, methods: _ } => { - env.define(name, Value::Nil); - - let class = Value::class(LoxClass::new(name)); - - env.assign(name, class, 0)?; - } Stmt::ExprStmt { expr } => { // expr.eval(env)?; // Ok(Value::Nil) @@ -248,3 +312,40 @@ impl LoxFunction { ret_val } } + +impl LoxExternFunction { + pub fn call(&self, args: Vec, env: &mut Environment) -> EvalResult { + if args.len() != self.arity() { + return Err(RuntimeError::WrongArity { + name: self.name().to_owned(), + arity: self.arity(), + given: args.len(), + }); + } + + (self.closure())(args, env) + } +} + +impl LoxClass { + // has to take class as an argument instead of as self to leave it behind its Rc + pub fn call(class: Rc, args: Vec, env: &mut Environment) -> EvalResult { + if args.len() != class.arity() { + return Err(RuntimeError::WrongArity { + name: class.name().to_owned(), + arity: class.arity(), + given: args.len(), + }); + } + + let object = LoxReference::new(class); + + // object.init(args, env)?; + + if let Some(Value::Function(method)) = object.get("init") { + method.call(args, env)?; + } + + Ok(Value::Object(object)) + } +} diff --git a/interpreter/src/lib.rs b/interpreter/src/lib.rs index 8ae862b..e41dd4d 100644 --- a/interpreter/src/lib.rs +++ b/interpreter/src/lib.rs @@ -4,6 +4,7 @@ mod error; mod function; mod interpret; mod lox_std; +mod object; mod resolver; mod run; mod runtime; @@ -12,6 +13,7 @@ mod value; pub use class::LoxClass; pub use error::{LoxError, RuntimeError}; pub use function::LoxFunction; +pub use object::LoxReference; pub use resolver::{resolve, ResolverError}; pub use run::{run_file, run_repl}; pub use runtime::Runtime; diff --git a/interpreter/src/lox_std.rs b/interpreter/src/lox_std.rs index e51e96c..eed8f47 100644 --- a/interpreter/src/lox_std.rs +++ b/interpreter/src/lox_std.rs @@ -12,102 +12,79 @@ pub fn init_std(env: &mut Runtime) { } fn input() -> LoxExternFunction { - let closure: ExternFunClosure = |args, _env| match *args { - [] => { - let mut buf = String::new(); - std::io::stdin() - .read_line(&mut buf) - .map_err(|err| RuntimeError::ExtFunCallFailed { - name: "input".to_owned(), - msg: format!("input() failed to read from stdin: {err}"), - })?; + let closure: ExternFunClosure = |args, _env| { + assert_eq!(args.len(), 0); + let mut buf = String::new(); + std::io::stdin() + .read_line(&mut buf) + .map_err(|err| RuntimeError::ExtFunCallFailed { + name: "input".to_owned(), + msg: format!("input() failed to read from stdin: {err}"), + })?; - assert_eq!(buf.pop(), Some('\n')); + assert_eq!(buf.pop(), Some('\n')); - Ok(Value::string(buf)) - } - _ => Err(RuntimeError::ExtFunCallFailed { - name: "input".to_owned(), - msg: "input() takes no arguments".to_owned(), - }), + Ok(Value::string(buf)) }; - LoxExternFunction::new("input", closure) + LoxExternFunction::new("input", 0, closure) } fn clock() -> LoxExternFunction { - let closure: ExternFunClosure = |args, _env| match *args { - [] => { - let time = std::time::SystemTime::now() - .duration_since(std::time::UNIX_EPOCH) - .unwrap() - .as_secs_f64(); + let closure: ExternFunClosure = |args, _env| { + assert_eq!(args.len(), 0); + let time = std::time::SystemTime::now() + .duration_since(std::time::UNIX_EPOCH) + .unwrap() + .as_secs_f64(); - Ok(Value::Number(time)) - } - _ => Err(RuntimeError::ExtFunCallFailed { - name: "clock".to_owned(), - msg: "clock() takes no arguments".to_owned(), - }), + Ok(Value::Number(time)) }; - LoxExternFunction::new("clock", closure) + LoxExternFunction::new("clock", 0, closure) } fn print_globals() -> LoxExternFunction { - let closure: ExternFunClosure = |args, env| match *args { - [] => { - let mut globals: Vec<(&String, &Value)> = env.globals().iter().collect(); + let closure: ExternFunClosure = |args, env| { + assert_eq!(args.len(), 0); + let mut globals: Vec<(&String, &Value)> = env.globals().iter().collect(); - globals.sort_by_key(|&(name, _value)| name); + globals.sort_by_key(|&(name, _value)| name); - for (name, value) in globals { - println!("{name}: {value}"); - } - - Ok(Value::Nil) + for (name, value) in globals { + println!("{name}: {value}"); } - _ => Err(RuntimeError::ExtFunCallFailed { - name: "print_globals".to_owned(), - msg: "print_globals() takes no arguments".to_owned(), - }), + + Ok(Value::Nil) }; - LoxExternFunction::new("print_globals", closure) + LoxExternFunction::new("print_globals", 0, closure) } fn print_env() -> LoxExternFunction { - let closure: ExternFunClosure = |args, env| match *args { - [] => { - println!("{env}"); - Ok(Value::Nil) - } - _ => Err(RuntimeError::ExtFunCallFailed { - name: "print_env".to_owned(), - msg: "print_env() takes no arguments".to_owned(), - }), + let closure: ExternFunClosure = |args, env| { + assert_eq!(args.len(), 0); + println!("{env}"); + Ok(Value::Nil) }; - LoxExternFunction::new("print_env", closure) + LoxExternFunction::new("print_env", 0, closure) } fn exit() -> LoxExternFunction { let closure: ExternFunClosure = |args, _env| { - match &*args { - [] => return Err(RuntimeError::Exit { exit_code: 0 }), - [Value::Number(exit_code)] => { - if exit_code.fract() == 0.0 { - let exit_code = *exit_code as i32; - return Err(RuntimeError::Exit { exit_code }); - } + assert_eq!(args.len(), 1); + if let Value::Number(exit_code) = args[0] { + if exit_code.fract() == 0.0 { + let exit_code = exit_code as i32; + return Err(RuntimeError::Exit { exit_code }); } - _ => {} } Err(RuntimeError::ExtFunCallFailed { name: "exit".to_owned(), - msg: "Arguments to exit() must an integer or nothing for exit code 0".to_owned(), + msg: "Arguments to exit() must an integer".to_owned(), }) }; - LoxExternFunction::new("exit", closure) + LoxExternFunction::new("exit", 1, closure) } diff --git a/interpreter/src/object.rs b/interpreter/src/object.rs new file mode 100644 index 0000000..5d45be2 --- /dev/null +++ b/interpreter/src/object.rs @@ -0,0 +1,104 @@ +use std::cell::UnsafeCell; +use std::collections::HashMap; +use std::fmt::Display; +use std::rc::Rc; + +use crate::{LoxClass, Value}; + +/// This struct is private, since *nothing* is supposed to be handling an object directly, +/// but *only* through a LoxReference, which is basically a pointer to an object +#[derive(Debug, Clone)] +struct LoxObject { + class: Rc, + + attrs: HashMap, +} + +impl LoxObject { + fn new(class: Rc) -> Self { + LoxObject { + class, + attrs: HashMap::new(), + } + } + + fn class(&self) -> Rc { + Rc::clone(&self.class) + } + + fn get(&self, name: &str, this: LoxReference) -> Option { + if let Some(value) = self.attrs.get(name).cloned() { + return Some(value); + } + + self.class.get_method(name, this) + } + + fn set(&mut self, name: impl Into, value: Value) { + let name = name.into(); + self.attrs.insert(name, value); + } +} + +/*====================================================================================================================*/ + +/// Pointer-like to LoxObject. Has interior mutability, which means it can't give out references to its interior. +/// Also includes the possibility of weird, self-referential constructions that lead to unsafe beheaviour, +/// so make sure not to do any (too) weird shit here. +#[derive(Debug, Clone)] +pub struct LoxReference { + inner: Rc>, +} + +impl LoxReference { + pub fn new(class: Rc) -> Self { + let object = LoxObject::new(class); + let inner = Rc::new(UnsafeCell::new(object)); + LoxReference { inner } + } + + pub fn class(&self) -> Rc { + unsafe { + let ptr = self.inner.get(); + let object = &*ptr; + object.class() + } + } + + pub fn get(&self, name: &str) -> Option { + unsafe { + let ptr = self.inner.get(); + let object = &*ptr; + let this = self.clone(); + object.get(name, this) + } + } + + pub fn set(&mut self, name: impl Into, value: Value) { + unsafe { + let ptr = self.inner.get(); + let object = &mut *ptr; + object.set(name, value) + } + } + + /* pub fn init(&self, args: Vec, env: &mut Environment) -> EvalResult<()> { + self.class().init(self.clone(), args, env) + } */ +} + +impl PartialEq for LoxReference { + fn eq(&self, other: &Self) -> bool { + Rc::ptr_eq(&self.inner, &other.inner) + } +} + +impl Display for LoxReference { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + unsafe { + let ptr = self.inner.get(); + let object = &*ptr; + write!(f, "<{} object at {:#X}>", object.class().name(), ptr as usize) + } + } +} diff --git a/interpreter/src/resolver/error.rs b/interpreter/src/resolver/error.rs index fbfee8c..7fdd17a 100644 --- a/interpreter/src/resolver/error.rs +++ b/interpreter/src/resolver/error.rs @@ -4,8 +4,12 @@ use thiserror::Error; pub enum ResolverError { #[error("Can't read variable {name} in its own initializer.")] VarInOwnInitializer { name: String }, - #[error("Could not resolve variable {name}")] + #[error("Variable {name} not defined")] UnresolvableVariable { name: String }, #[error("Return outside of function definition")] ReturnOutsideFunction, + #[error("this outside of method")] + ThisOutsideMethod, + #[error("super outside of subclass method")] + SuperOutsideMethod, } diff --git a/interpreter/src/resolver/resolve.rs b/interpreter/src/resolver/resolve.rs index da96f36..7ae89cf 100644 --- a/interpreter/src/resolver/resolve.rs +++ b/interpreter/src/resolver/resolve.rs @@ -115,7 +115,6 @@ impl Resolver { if scope.contains_key(&name) { return Ok(Expr::LocalVariable { name, level }); } - // DEBUG level += 1; } @@ -142,7 +141,13 @@ impl Resolver { return Ok(Expr::GlobalVariable { name }); } - Err(ResolverError::UnresolvableVariable { name }) + if name == "this" { + Err(ResolverError::ThisOutsideMethod) + } else if name == "super" { + Err(ResolverError::SuperOutsideMethod) + } else { + Err(ResolverError::UnresolvableVariable { name }) + } } fn resolve_stmt(&mut self, stmt: &mut Stmt) -> Result<(), ResolverError> { @@ -179,11 +184,6 @@ impl Resolver { self.exit_scope(); Ok(()) } - Stmt::Class { name, methods: _ } => { - self.declare(name); - self.define(name); - Ok(()) - } Stmt::ExprStmt { expr } => self.resolve_expr(expr), Stmt::Break => Ok(()), Stmt::Return { expr } => self.resolve_expr(expr), @@ -241,6 +241,15 @@ impl Resolver { } Ok(()) } + Expr::Get { target, name: _ } => { + self.resolve_expr(target)?; + Ok(()) + } + Expr::Set { target, name: _, value } => { + self.resolve_expr(target)?; + self.resolve_expr(value)?; + Ok(()) + } Expr::Function { name, param_names, @@ -254,7 +263,7 @@ impl Resolver { self.declare(name); self.define(name); - for param_name in param_names { + for param_name in param_names.iter() { self.declare(param_name); self.define(param_name); } @@ -271,6 +280,47 @@ impl Resolver { Ok(()) } + Expr::Class { + superclass, + name, + methods, + } => { + self.declare(name); + + if let Some(superclass) = superclass { + self.resolve_expr(superclass)?; + } + + self.define(name); + + // this is the scope "this" is defined in + self.enter_scope(); + self.declare("this"); + + if superclass.is_some() { + self.declare("super"); + } + + for method in methods.iter_mut() { + self.resolve_expr(method)?; + } + + self.exit_scope(); + + Ok(()) + } + Expr::This => { + *expr = self.resolve_var("this")?; + Ok(()) + } + Expr::Super { + super_var, + this_var, + method: _, + } => { + self.resolve_expr(super_var)?; + self.resolve_expr(this_var) + } } } } diff --git a/interpreter/src/value.rs b/interpreter/src/value.rs index 163162a..0e75b25 100644 --- a/interpreter/src/value.rs +++ b/interpreter/src/value.rs @@ -2,13 +2,14 @@ use std::cell::UnsafeCell; use std::fmt::{Debug, Display}; use std::rc::Rc; -use crate::LoxClass; +use crate::{LoxClass, LoxReference}; use super::function::LoxExternFunction; use super::LoxFunction; -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialEq)] pub enum Value { + Object(LoxReference), Class(Rc), Function(Rc), ExternFunction(Rc), @@ -17,15 +18,13 @@ pub enum Value { Bool(bool), Nil, } - impl Value { - pub fn class(class: LoxClass) -> Self { - let class = Rc::new(class); + pub fn class(class: Rc) -> Self { Value::Class(class) } - pub fn function(fun: LoxFunction) -> Self { - let fun = Rc::new(fun); + pub fn function(fun: impl Into>) -> Self { + let fun = fun.into(); Value::Function(fun) } @@ -47,6 +46,7 @@ impl Value { impl Display for Value { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { + Value::Object(object) => write!(f, "{object}"), Value::Class(class) => write!(f, "{class}"), Value::Function(fun) => write!(f, "{fun}"), Value::ExternFunction(ext_fun) => write!(f, "{ext_fun}"), @@ -58,20 +58,6 @@ impl Display for Value { } } -impl PartialEq for Value { - fn eq(&self, other: &Value) -> bool { - match (self, other) { - (Value::Function(l0), Value::Function(r0)) => Rc::ptr_eq(l0, r0), - (Value::ExternFunction(l0), Value::ExternFunction(r0)) => Rc::ptr_eq(l0, r0), - (Value::String(l0), Value::String(r0)) => l0 == r0, - (Value::Number(l0), Value::Number(r0)) => l0 == r0, - (Value::Bool(l0), Value::Bool(r0)) => l0 == r0, - (Value::Nil, Value::Nil) => true, - _ => false, - } - } -} - /*====================================================================================================================*/ #[derive(Clone)]