2023-01-28 01:11:55 +01:00
|
|
|
use std::fmt::Display;
|
2023-01-28 14:19:12 +01:00
|
|
|
use std::rc::Rc;
|
2023-01-28 01:11:55 +01:00
|
|
|
|
2023-01-28 14:19:12 +01:00
|
|
|
use rlox2_frontend::parser::{Expr, Stmt};
|
2024-09-02 05:19:30 +02:00
|
|
|
use smol_str::SmolStr;
|
2023-01-28 14:19:12 +01:00
|
|
|
|
2024-09-03 16:53:46 +02:00
|
|
|
use crate::value::Attrs;
|
2023-01-28 14:19:12 +01:00
|
|
|
use crate::{LoxFunction, LoxReference, Value};
|
|
|
|
|
|
|
|
|
|
#[derive(Debug, Clone)]
|
2023-01-28 01:11:55 +01:00
|
|
|
pub struct LoxClass {
|
2023-01-28 14:19:12 +01:00
|
|
|
superclass: Option<Rc<LoxClass>>,
|
|
|
|
|
|
2024-09-02 05:19:30 +02:00
|
|
|
name: SmolStr,
|
2023-01-28 14:19:12 +01:00
|
|
|
|
2024-09-03 16:53:46 +02:00
|
|
|
methods: Attrs,
|
2023-01-28 01:11:55 +01:00
|
|
|
}
|
|
|
|
|
|
2024-09-03 16:48:00 +02:00
|
|
|
/// Representation of a class in Lox. Always behind an Rc to ensure uniqueness. Should never be
|
|
|
|
|
/// handled raw.
|
2023-01-28 01:11:55 +01:00
|
|
|
impl LoxClass {
|
2023-01-30 17:41:48 +01:00
|
|
|
pub fn new(
|
2024-09-02 05:19:30 +02:00
|
|
|
name: impl Into<SmolStr>,
|
2024-09-03 16:53:46 +02:00
|
|
|
methods: Attrs,
|
2023-01-30 17:41:48 +01:00
|
|
|
superclass: Option<Rc<LoxClass>>,
|
|
|
|
|
) -> Rc<Self> {
|
2023-01-28 01:11:55 +01:00
|
|
|
let name = name.into();
|
2023-01-28 14:19:12 +01:00
|
|
|
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 {
|
2024-09-01 19:16:30 +02:00
|
|
|
statements.push(Stmt::return_stmt(Expr::local_variable("this", 1)));
|
2023-01-28 14:19:12 +01:00
|
|
|
} 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));
|
2024-09-03 16:53:46 +02:00
|
|
|
|
|
|
|
|
methods.insert("init".into(), new_init);
|
2023-01-28 14:19:12 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if let Some(ref superclass) = superclass {
|
2024-09-03 16:53:46 +02:00
|
|
|
let mut new_methods = Attrs::default();
|
2023-01-28 14:19:12 +01:00
|
|
|
|
|
|
|
|
// Rc<LoxFunction> 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<Rc<LoxClass>> {
|
|
|
|
|
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();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
0
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn get_method(&self, name: &str, this: LoxReference) -> Option<Value> {
|
|
|
|
|
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
|
2023-01-28 01:11:55 +01:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl Display for LoxClass {
|
|
|
|
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
2024-09-01 19:16:30 +02:00
|
|
|
write!(
|
|
|
|
|
f,
|
|
|
|
|
"<class {} at {:#X}>",
|
|
|
|
|
self.name,
|
|
|
|
|
(self as *const LoxClass) as usize
|
|
|
|
|
)
|
2023-01-28 14:19:12 +01:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// slightly hacky: two classes are equal if they are stored at the same location
|
2024-09-03 16:48:00 +02:00
|
|
|
// since a LoxClass is always (supposed to be) behind an Rc anyways, we might as well compare their
|
|
|
|
|
// pointers
|
2023-01-28 14:19:12 +01:00
|
|
|
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
|
2023-01-28 01:11:55 +01:00
|
|
|
}
|
|
|
|
|
}
|