use std::collections::HashMap; use std::fmt::Display; use std::rc::Rc; 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, methods: HashMap, superclass: Option>) -> Rc { let name = name.into(); 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, (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 } }