diff --git a/Cargo.lock b/Cargo.lock index aa84f2d..eeeeda0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -155,7 +155,6 @@ dependencies = [ "rikulox-lex", "rikulox-parse", "rikulox-resolve", - "rikulox-runtime", "rikulox-treewalk", ] diff --git a/program/method.rlx b/program/method.rlx new file mode 100644 index 0000000..ca31c9b --- /dev/null +++ b/program/method.rlx @@ -0,0 +1,7 @@ +class A { + m() { + print "method"; + } +} + +A().m(); diff --git a/rikulox-ast/src/expr.rs b/rikulox-ast/src/expr.rs index 348ef1f..73cf8ec 100644 --- a/rikulox-ast/src/expr.rs +++ b/rikulox-ast/src/expr.rs @@ -38,6 +38,16 @@ pub enum ExprKind<'src> { callee: Box>, args: Vec>, }, + Get { + left: Box>, + name: Identifier<'src>, + }, + Set { + left: Box>, + name: Identifier<'src>, + value: Box>, + }, + This, } #[derive(Debug, Clone, PartialEq, Eq, Hash)] diff --git a/rikulox-ast/src/stmt.rs b/rikulox-ast/src/stmt.rs index 111f44a..0b281dd 100644 --- a/rikulox-ast/src/stmt.rs +++ b/rikulox-ast/src/stmt.rs @@ -31,6 +31,7 @@ pub enum StmtKind<'src> { }, Function(FunctionDecl<'src>), Return(Option>), + Class(ClassDecl<'src>), } #[derive(Debug, Clone, PartialEq)] @@ -39,3 +40,9 @@ pub struct FunctionDecl<'src> { pub params: Vec>, pub body: Vec>, } + +#[derive(Debug, Clone, PartialEq)] +pub struct ClassDecl<'src> { + pub name: Identifier<'src>, + pub methods: Vec>, +} diff --git a/rikulox-cli/Cargo.toml b/rikulox-cli/Cargo.toml index d49566c..bffe86d 100644 --- a/rikulox-cli/Cargo.toml +++ b/rikulox-cli/Cargo.toml @@ -1,8 +1,9 @@ [package] name = "rikulox-cli" +version = "0.1.0" authors.workspace = true edition.workspace = true -version.workspace = true +# version.workspace = true readme.workspace = true license.workspace = true repository.workspace = true @@ -18,5 +19,4 @@ clap = { version = "4.5.41", features = ["derive"] } rikulox-parse = { version = "0.1.0", path = "../rikulox-parse" } rikulox-lex = { version = "0.1.0", path = "../rikulox-lex" } rikulox-treewalk = { version = "0.1.0", path = "../rikulox-treewalk" } -rikulox-runtime = { version = "0.1.0", path = "../rikulox-runtime" } rikulox-resolve = { version = "0.1.0", path = "../rikulox-resolve" } diff --git a/rikulox-gc/src/gc.rs b/rikulox-gc/src/gc.rs index 979cd2c..9d0cbba 100644 --- a/rikulox-gc/src/gc.rs +++ b/rikulox-gc/src/gc.rs @@ -5,6 +5,45 @@ use crate::{ trace::{Trace, Tracer}, }; +pub struct Gc +where + F: Fn() -> I, + I: Iterator>, +{ + heap: Heap, + alloc_count: u32, + gc_threshold: u32, + get_roots: F, +} + +impl Gc +where + F: Fn() -> I, + I: Iterator>, +{ + pub fn new(gc_threshold: u32, get_roots: F) -> Self { + Self { + heap: Heap::new(), + alloc_count: 0, + gc_threshold, + get_roots, + } + } + + pub fn alloc(&mut self, value: T) -> EntryRef { + self.alloc_count += 1; + + if self.alloc_count == self.gc_threshold { + self.alloc_count = 0; + let roots = (self.get_roots)(); + self.heap.mark(roots); + self.heap.sweep(); + } + + self.heap.alloc(value) + } +} + #[derive(Debug, Clone)] pub struct Heap { list: FreeList, diff --git a/rikulox-parse/src/parse.rs b/rikulox-parse/src/parse.rs index 2dfae70..b3b629f 100644 --- a/rikulox-parse/src/parse.rs +++ b/rikulox-parse/src/parse.rs @@ -4,7 +4,7 @@ use rikulox_ast::{ expr::{BinOp, Expr, ExprKind, Identifier, Literal, LogicalOp, UnaryOp}, id::IdGen, span::Span, - stmt::{FunctionDecl, Stmt, StmtKind}, + stmt::{ClassDecl, FunctionDecl, Stmt, StmtKind}, token::{Keyword, Token, TokenKind}, }; @@ -50,7 +50,16 @@ where } TokenKind::Keyword(Keyword::Fun) => { let fun_span = self.advance().unwrap().span; - self.function_decl(Some(fun_span)) + let (fun, end_span) = self.function_decl(Some(fun_span))?; + Ok(Stmt { + kind: StmtKind::Function(fun), + span: fun_span.with_end_from(end_span), + id: self.id_gen.next_id(), + }) + } + TokenKind::Keyword(Keyword::Class) => { + let class_span = self.advance().unwrap().span; + self.class_decl(class_span) } _ => self.statement(), } @@ -132,7 +141,7 @@ where fn function_decl( &mut self, fun_span: Option, - ) -> Result, ParseError<'src>> { + ) -> Result<(FunctionDecl<'src>, Span), ParseError<'src>> { let Token { kind: TokenKind::Identifier(ident), span: ident_span, @@ -171,13 +180,58 @@ where self.consume(&TokenKind::LBrace)?; let (body, end_span) = self.block()?; - Ok(Stmt { - kind: StmtKind::Function(FunctionDecl { + Ok(( + FunctionDecl { name: Identifier { symbol: ident }, params, body, + }, + fun_span.unwrap_or(ident_span).with_end_from(end_span), + )) + } + + fn class_decl( + &mut self, + class_span: Span, + ) -> Result, ParseError<'src>> { + let name = match self.peek_or_err(ExpectedItem::Ident)? { + &Token { + kind: TokenKind::Identifier(name), + .. + } => { + self.advance().unwrap(); + name + } + unexpected => { + return Err(ParseError { + kind: ParseErrorKind::UnexpectedToken { + expected: ExpectedItem::Ident, + found: unexpected.kind, + }, + span: unexpected.span, + }); + } + }; + + self.consume(&TokenKind::LBrace)?; + + let mut methods = Vec::new(); + + while let Some(token) = self.peek() + && !matches!(token.kind, TokenKind::RBrace) + { + let (fun, _end_span) = self.function_decl(None)?; + methods.push(fun); + } + + let r_brace = self.consume(&TokenKind::RBrace)?; + + Ok(Stmt { + kind: StmtKind::Class(ClassDecl { + name: Identifier { symbol: name }, + methods, }), - span: fun_span.unwrap_or(ident_span).with_end_from(end_span), + span: class_span.with_end_from(r_brace.span), id: self.id_gen.next_id(), }) } @@ -461,6 +515,16 @@ where span: expr.span.with_end_from(token_eq.span), id: self.id_gen.next_id(), }) + } else if let ExprKind::Get { left: object, name } = expr.kind { + Ok(Expr { + kind: ExprKind::Set { + left: object, + name, + value: Box::new(value), + }, + span: expr.span.with_end_from(token_eq.span), + id: self.id_gen.next_id(), + }) } else { Err(ParseError { kind: ParseErrorKind::UnexpectedToken { @@ -668,19 +732,43 @@ where let mut expr = self.primary()?; let expr_span = expr.span; - while let Some(token) = self.peek() - && matches!(token.kind, TokenKind::LParen) - { - let l_paren = self.advance().unwrap(); - let (args, r_paren_span) = self.arguments(l_paren.span)?; + loop { + match self.peek().map(|token| token.kind) { + Some(TokenKind::LParen) => { + let l_paren = self.advance().unwrap(); + let (args, r_paren_span) = self.arguments(l_paren.span)?; - expr = Expr { - kind: ExprKind::Call { - callee: Box::new(expr), - args, - }, - span: expr_span.with_end_from(r_paren_span), - id: self.id_gen.next_id(), + expr = Expr { + kind: ExprKind::Call { + callee: Box::new(expr), + args, + }, + span: expr_span.with_end_from(r_paren_span), + id: self.id_gen.next_id(), + } + } + + Some(TokenKind::Dot) => { + let _dot = self.advance().unwrap(); + let Token { + kind: TokenKind::Identifier(ident), + span: ident_span, + } = self.consume(&TokenKind::Identifier(""))? + else { + unreachable!(); + }; + + expr = Expr { + kind: ExprKind::Get { + left: Box::new(expr), + name: Identifier { symbol: ident }, + }, + span: expr_span.with_end_from(ident_span), + id: self.id_gen.next_id(), + } + } + + _ => break, } } @@ -740,6 +828,11 @@ where span: token.span, id: self.id_gen.next_id(), }, + Keyword::This => Expr { + kind: ExprKind::This, + span: token.span, + id: self.id_gen.next_id(), + }, _ => { return Err(ParseError { kind: ParseErrorKind::UnexpectedToken { diff --git a/rikulox-resolve/src/error.rs b/rikulox-resolve/src/error.rs index 0c1e2f8..9f800a9 100644 --- a/rikulox-resolve/src/error.rs +++ b/rikulox-resolve/src/error.rs @@ -11,4 +11,6 @@ pub enum ResolveErrorKind { UninitializedVariable(String), VariableAlreadyDeclared(String), ReturnOutsideFunction, + ThisOutsideClass, + ReturnInInit, } diff --git a/rikulox-resolve/src/resolve.rs b/rikulox-resolve/src/resolve.rs index fc891ff..f2b101c 100644 --- a/rikulox-resolve/src/resolve.rs +++ b/rikulox-resolve/src/resolve.rs @@ -1,7 +1,7 @@ use std::{collections::HashMap, mem}; use rikulox_ast::{ - expr::Expr, + expr::{Expr, ExprKind}, id::NodeId, span::Span, stmt::{FunctionDecl, Stmt, StmtKind}, @@ -13,6 +13,7 @@ pub struct Resolver<'src> { scopes: Vec>, locals: HashMap, current_function: FunctionKind, + current_class: ClassKind, } impl<'src> Resolver<'src> { @@ -21,6 +22,7 @@ impl<'src> Resolver<'src> { scopes: vec![], locals: HashMap::new(), current_function: FunctionKind::None, + current_class: ClassKind::None, } } @@ -99,9 +101,41 @@ impl<'src> Resolver<'src> { } if let Some(expr) = expr { + if self.current_function == FunctionKind::Init { + return Err(ResolveError { + kind: ResolveErrorKind::ReturnInInit, + span: *span, + }); + } + self.expression(expr)?; } } + + StmtKind::Class(class_decl) => { + let enclosing_class = + mem::replace(&mut self.current_class, ClassKind::Class); + + self.declare(class_decl.name.symbol, *span)?; + self.define(class_decl.name.symbol); + + self.begin_scope(); + + self.scopes.last_mut().unwrap().insert("this", true); + + for method in &class_decl.methods { + let func_kind = if method.name.symbol == "init" { + FunctionKind::Init + } else { + FunctionKind::Method + }; + self.resolve_function(method, func_kind, *span)?; + } + + self.end_scope(); + + self.current_class = enclosing_class; + } } Ok(()) @@ -111,18 +145,14 @@ impl<'src> Resolver<'src> { let Expr { kind, span, id } = expr; match kind { - rikulox_ast::expr::ExprKind::Binary { left, op: _, right } => { + ExprKind::Binary { left, op: _, right } => { self.expression(left)?; self.expression(right)?; } - rikulox_ast::expr::ExprKind::Unary { op: _, right } => { - self.expression(right)? - } - rikulox_ast::expr::ExprKind::Grouping(expr) => { - self.expression(expr)? - } - rikulox_ast::expr::ExprKind::Literal(_) => (), - rikulox_ast::expr::ExprKind::Variable(identifier) => { + ExprKind::Unary { op: _, right } => self.expression(right)?, + ExprKind::Grouping(expr) => self.expression(expr)?, + ExprKind::Literal(_) => (), + ExprKind::Variable(identifier) => { if !self.scopes.is_empty() && self .scopes @@ -145,20 +175,43 @@ impl<'src> Resolver<'src> { } self.resolve_local(*id, identifier.symbol); } - rikulox_ast::expr::ExprKind::Assign { name, value } => { + ExprKind::Assign { name, value } => { self.expression(value)?; self.resolve_local(*id, name.symbol); } - rikulox_ast::expr::ExprKind::Logical { left, op: _, right } => { + ExprKind::Logical { left, op: _, right } => { self.expression(left)?; self.expression(right)?; } - rikulox_ast::expr::ExprKind::Call { callee, args } => { + ExprKind::Call { callee, args } => { self.expression(callee)?; for arg in args { self.expression(arg)?; } } + ExprKind::Get { + left: object, + name: _, + } => { + self.expression(object)?; + } + ExprKind::Set { + left: object, + name: _, + value, + } => { + self.expression(object)?; + self.expression(value)?; + } + ExprKind::This => { + if self.current_class == ClassKind::None { + return Err(ResolveError { + kind: ResolveErrorKind::ThisOutsideClass, + span: *span, + }); + } + self.resolve_local(*id, "this"); + } } Ok(()) @@ -249,4 +302,12 @@ impl<'src> Default for Resolver<'src> { enum FunctionKind { None, Function, + Init, + Method, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +enum ClassKind { + None, + Class, } diff --git a/rikulox-treewalk/src/call.rs b/rikulox-treewalk/src/call.rs index 3fd1893..b9796a5 100644 --- a/rikulox-treewalk/src/call.rs +++ b/rikulox-treewalk/src/call.rs @@ -1,71 +1,43 @@ -use std::{cell::RefCell, fmt::Display, rc::Rc}; +use std::{cell::RefCell, collections::HashMap, fmt::Display, rc::Rc}; -use rikulox_ast::{span::Span, stmt::FunctionDecl}; +use rikulox_ast::stmt::FunctionDecl; use crate::{ - env::Environment, - error::{RuntimeError, RuntimeErrorKind}, - interp::TreeWalkInterpreter, - obj::Object, - value::Value, + env::Environment, error::RuntimeError, interp::TreeWalkInterpreter, + obj::Object, value::Value, }; -pub trait Call<'src> { - fn arity(&self) -> usize; - fn call( - &self, - interp: &mut TreeWalkInterpreter<'src>, - args: &[Value<'src>], - call_span: Span, - ) -> Result, RuntimeError<'src>>; +#[derive(Debug, Clone, PartialEq)] +pub struct Class<'src> { + pub name: &'src str, + pub methods: HashMap<&'src str, Function<'src>>, +} + +impl<'src> Class<'src> { + pub fn find_method(&self, name: &str) -> Option<&Function<'src>> { + self.methods.get(name) + } +} + +impl<'src> Display for Class<'src> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self.name) + } } #[derive(Debug, Clone, PartialEq)] pub struct Function<'src> { pub declaration: FunctionDecl<'src>, pub closure: Rc>>, + pub is_init: bool, } -impl<'src> Call<'src> for Function<'src> { - fn arity(&self) -> usize { - self.declaration.params.len() - } - - fn call( - &self, - interp: &mut TreeWalkInterpreter<'src>, - args: &[Value<'src>], - call_span: Span, - ) -> Result, RuntimeError<'src>> { - if self.arity() != args.len() { - return Err(RuntimeError { - kind: RuntimeErrorKind::Arity { - expected: self.arity(), - actual: args.len(), - }, - span: call_span, - }); - } - +impl<'src> Function<'src> { + pub fn bind(&mut self, instance: Value<'src>) { let mut env = Environment::with_enclosing(Rc::clone(&self.closure)); + env.define("this", instance); - for (param, arg) in self.declaration.params.iter().zip(args) { - env.define(param.symbol, arg.clone()); - } - - let result = interp - .exec_block(&self.declaration.body, Rc::new(RefCell::new(env))); - - if let Err(RuntimeError { - kind: RuntimeErrorKind::Return(value), - span: _, - }) = result - { - Ok(value) - } else { - result?; - Ok(Value::Nil) - } + self.closure = Rc::new(RefCell::new(env)); } } @@ -100,30 +72,6 @@ impl NativeFunction { } } -impl<'src> Call<'src> for NativeFunction { - fn arity(&self) -> usize { - self.arity - } - - fn call( - &self, - interp: &mut TreeWalkInterpreter<'src>, - args: &[Value<'src>], - call_span: Span, - ) -> Result, RuntimeError<'src>> { - if self.arity() != args.len() { - return Err(RuntimeError { - kind: RuntimeErrorKind::Arity { - expected: self.arity, - actual: args.len(), - }, - span: call_span, - }); - } - (self.function)(interp, args) - } -} - impl Display for NativeFunction { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "") diff --git a/rikulox-treewalk/src/error.rs b/rikulox-treewalk/src/error.rs index 8e55301..c348004 100644 --- a/rikulox-treewalk/src/error.rs +++ b/rikulox-treewalk/src/error.rs @@ -14,4 +14,5 @@ pub enum RuntimeErrorKind<'src> { TypeError(Expr<'src>), UndefinedVariable(String), Arity { expected: usize, actual: usize }, + UndefinedProperty(String), } diff --git a/rikulox-treewalk/src/interp.rs b/rikulox-treewalk/src/interp.rs index ac68afc..0300a58 100644 --- a/rikulox-treewalk/src/interp.rs +++ b/rikulox-treewalk/src/interp.rs @@ -8,11 +8,11 @@ use rikulox_ast::{ }; use crate::{ - call::Function, + call::{Class, Function, NativeFunction}, env::Environment, error::{RuntimeError, RuntimeErrorKind}, native::CLOCK_FN, - obj::Object, + obj::{Instance, Object}, value::Value, }; @@ -107,6 +107,7 @@ impl<'src> TreeWalkInterpreter<'src> { Object::Function(Function { declaration: declaration.clone(), closure: Rc::clone(&self.env), + is_init: false, }), ))); @@ -123,6 +124,42 @@ impl<'src> TreeWalkInterpreter<'src> { span: stmt.span, }); } + StmtKind::Class(decl) => { + self.env.borrow_mut().define( + decl.name.symbol, + Value::Object(Rc::new(RefCell::new(Object::Class( + Class { + name: decl.name.symbol, + methods: HashMap::new(), + }, + )))), + ); + + let mut methods = HashMap::new(); + for method in &decl.methods { + let fun = Function { + declaration: method.clone(), + closure: Rc::clone(&self.env), + is_init: method.name.symbol == "init", + }; + + methods.insert(method.name.symbol, fun); + } + let class = Value::Object(Rc::new(RefCell::new( + Object::Class(Class { + name: decl.name.symbol, + methods, + }), + ))); + + self.env + .borrow_mut() + .assign(decl.name.symbol, class) + .map_err(|e| RuntimeError { + kind: e, + span: stmt.span, + })?; + } }; Ok(()) } @@ -150,7 +187,7 @@ impl<'src> TreeWalkInterpreter<'src> { &mut self, expr: &Expr<'src>, ) -> Result, RuntimeError<'src>> { - let object = match &expr.kind { + let value = match &expr.kind { ExprKind::Binary { left, op, right } => { self.binary_expr(left.as_ref(), op, right.as_ref(), expr)? } @@ -232,24 +269,186 @@ impl<'src> TreeWalkInterpreter<'src> { .map(|arg| self.eval(arg)) .collect::>, RuntimeError<'src>>>( )?; - let Value::Object(callee) = callee else { + + self.call(&callee, &args, expr)? + } + ExprKind::Get { left, name } => { + let value = self.eval(left.as_ref())?; + + let Value::Object(object) = &value else { return Err(RuntimeError { kind: RuntimeErrorKind::TypeError(expr.clone()), span: expr.span, }); }; - let callee = callee.borrow(); - let callee = callee.as_call().ok_or(RuntimeError { - kind: RuntimeErrorKind::TypeError(expr.clone()), - span: expr.span, - }); + let Object::Instance(instance) = &*object.borrow() else { + return Err(RuntimeError { + kind: RuntimeErrorKind::TypeError(expr.clone()), + span: expr.span, + }); + }; - callee?.call(self, &args, expr.span)? + instance.get(name.symbol, value.clone()).ok_or( + RuntimeError { + kind: RuntimeErrorKind::UndefinedProperty( + name.symbol.to_string(), + ), + span: expr.span, + }, + )? + } + ExprKind::Set { + left: object, + name, + value, + } => { + let value = self.eval(value.as_ref())?; + + let Value::Object(object) = self.eval(object.as_ref())? else { + return Err(RuntimeError { + kind: RuntimeErrorKind::TypeError(expr.clone()), + span: expr.span, + }); + }; + + let Object::Instance(instance) = &mut *object.borrow_mut() + else { + return Err(RuntimeError { + kind: RuntimeErrorKind::TypeError(expr.clone()), + span: expr.span, + }); + }; + + instance.set(name.symbol.to_string(), value.clone()); + + value } + + ExprKind::This => { + self.lookup_variable("this", expr.id, expr.span)?.clone() + } + }; + + Ok(value) + } + + fn call( + &mut self, + callee: &Value<'src>, + args: &[Value<'src>], + call_expr: &Expr<'src>, + ) -> Result, RuntimeError<'src>> { + let Value::Object(callee) = callee else { + return Err(RuntimeError { + kind: RuntimeErrorKind::TypeError(call_expr.clone()), + span: call_expr.span, + }); }; - Ok(object) + match &*callee.borrow() { + Object::Function(function) => { + self.call_function(function, args, call_expr.span) + } + Object::NativeFunction(native_function) => { + self.call_native_function(native_function, args, call_expr.span) + } + Object::Class(class) => { + self.call_class(class, callee, args, call_expr.span) + } + Object::String(_) | Object::Instance(_) => Err(RuntimeError { + kind: RuntimeErrorKind::TypeError(call_expr.clone()), + span: call_expr.span, + }), + } + } + + fn call_function( + &mut self, + function: &Function<'src>, + args: &[Value<'src>], + call_span: Span, + ) -> Result, RuntimeError<'src>> { + let arity = function.declaration.params.len(); + if arity != args.len() { + return Err(RuntimeError { + kind: RuntimeErrorKind::Arity { + expected: arity, + actual: args.len(), + }, + span: call_span, + }); + } + + let mut env = Environment::with_enclosing(Rc::clone(&function.closure)); + + for (param, arg) in function.declaration.params.iter().zip(args) { + env.define(param.symbol, arg.clone()); + } + + let result = self + .exec_block(&function.declaration.body, Rc::new(RefCell::new(env))); + + if let Err(RuntimeError { + kind: RuntimeErrorKind::Return(value), + span: _, + }) = result + { + Ok(value) + } else if function.is_init { + Ok(function.closure.borrow().get_at("this", 0).unwrap()) + } else { + result?; + + if function.is_init { + Ok(function.closure.borrow().get_at("this", 0).unwrap()) + } else { + Ok(Value::Nil) + } + } + } + + fn call_native_function( + &mut self, + native_function: &NativeFunction, + args: &[Value<'src>], + call_span: Span, + ) -> Result, RuntimeError<'src>> { + let arity = native_function.arity; + if arity != args.len() { + return Err(RuntimeError { + kind: RuntimeErrorKind::Arity { + expected: arity, + actual: args.len(), + }, + span: call_span, + }); + } + (native_function.function)(self, args) + } + + fn call_class( + &mut self, + class: &Class<'src>, + callee: &Rc>>, + args: &[Value<'src>], + call_span: Span, + ) -> Result, RuntimeError<'src>> { + assert!(args.is_empty()); + + let instance = + Value::Object(Rc::new(RefCell::new(Object::Instance(Instance { + class: Rc::clone(callee), + fields: HashMap::new(), + })))); + + let init = class.find_method("init").cloned(); + if let Some(mut init) = init { + init.bind(instance.clone()); + self.call_function(&init, args, call_span)?; + } + + Ok(instance) } fn binary_expr( diff --git a/rikulox-treewalk/src/obj.rs b/rikulox-treewalk/src/obj.rs index f5c6294..e6254b0 100644 --- a/rikulox-treewalk/src/obj.rs +++ b/rikulox-treewalk/src/obj.rs @@ -1,24 +1,17 @@ -use std::fmt::Display; +use std::{cell::RefCell, collections::HashMap, fmt::Display, rc::Rc}; -use crate::call::{Call, Function, NativeFunction}; +use crate::{ + call::{Class, Function, NativeFunction}, + value::Value, +}; #[derive(Debug, Clone, PartialEq)] pub enum Object<'src> { String(String), Function(Function<'src>), NativeFunction(NativeFunction), -} - -impl<'src> Object<'src> { - pub fn as_call(&self) -> Option<&dyn Call<'src>> { - let f: &dyn Call = match self { - Object::Function(f) => f, - Object::NativeFunction(f) => f, - _ => return None, - }; - - Some(f) - } + Class(Class<'src>), + Instance(Instance<'src>), } impl<'src> Display for Object<'src> { @@ -27,6 +20,45 @@ impl<'src> Display for Object<'src> { Object::String(s) => write!(f, "{s}"), Object::Function(fun) => write!(f, "{fun}"), Object::NativeFunction(fun) => write!(f, "{fun}"), + Object::Class(class) => write!(f, "{class}"), + Object::Instance(instance) => write!(f, "{instance}"), } } } + +#[derive(Debug, Clone, PartialEq)] +pub struct Instance<'src> { + pub class: Rc>>, + pub fields: HashMap>, +} + +impl<'src> Instance<'src> { + pub fn get( + &self, + name: &str, + self_ref: Value<'src>, + ) -> Option> { + let Object::Class(class) = &*self.class.borrow() else { + unreachable!() + }; + let result = self.fields.get(name); + if result.is_none() { + class.find_method(name).cloned().map(|mut m| { + m.bind(self_ref.clone()); + Value::Object(Rc::new(RefCell::new(Object::Function(m)))) + }) + } else { + result.cloned() + } + } + + pub fn set(&mut self, name: String, value: Value<'src>) { + self.fields.insert(name, value); + } +} + +impl<'src> Display for Instance<'src> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{} instance", self.class.borrow()) + } +}