From 0ebe2e9f2244c8fe9854f51f39c6ba1185825508 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Emmanuel=20Beno=C3=AEt?= <tseeker@nocternity.net>
Date: Thu, 12 Jan 2023 07:39:12 +0100
Subject: [PATCH] Interpreter - Class properties

---
 src/interpreter/classes.rs       | 35 ++++++++++++++++++++++++++++++--
 src/interpreter/interpretable.rs |  4 ++--
 src/interpreter/value.rs         | 21 ++++++++++++++++++-
 3 files changed, 55 insertions(+), 5 deletions(-)

diff --git a/src/interpreter/classes.rs b/src/interpreter/classes.rs
index d521f17..4a23f90 100644
--- a/src/interpreter/classes.rs
+++ b/src/interpreter/classes.rs
@@ -19,6 +19,7 @@ pub trait PropertyCarrier {
 pub struct Class {
     name: String,
     methods: HashMap<String, Function>,
+    fields: RefCell<HashMap<String, Value>>,
 }
 
 /// Classes are mostly used through references.
@@ -47,7 +48,11 @@ fn bind_method(method: &Function, this_value: Value) -> Function {
 impl Class {
     /// Create a new class, specifying its name.
     pub fn new(name: String, methods: HashMap<String, Function>) -> Self {
-        Self { name, methods }
+        Self {
+            name,
+            methods,
+            fields: RefCell::new(HashMap::default()),
+        }
     }
 }
 
@@ -72,7 +77,7 @@ impl Callable for ClassRef {
         let inst_value = Value::from(Instance::new(self.clone()));
         if let Some(init) = self.borrow().methods.get("init") {
             inst_value.with_instance(
-                |instance| {
+                |_| {
                     let bound_init = bind_method(init, inst_value.clone());
                     bound_init.call(itpr_state, arguments)
                 },
@@ -84,6 +89,32 @@ impl Callable for ClassRef {
     }
 }
 
+impl PropertyCarrier for ClassRef {
+    fn get(&self, name: &Token) -> SloxResult<Value> {
+        let class = self.borrow();
+        if let Some(value) = class.fields.borrow().get(&name.lexeme) {
+            return Ok(value.clone());
+        }
+        /*
+        if let Some(method) = class.methods.get(&name.lexeme) {
+            let bound_method = bind_method(method, Value::from(self.clone()));
+            return Ok(Value::from(bound_method));
+        }
+        */
+
+        Err(SloxError::with_token(
+            ErrorKind::Runtime,
+            name,
+            "undefined property".to_owned(),
+        ))
+    }
+
+    fn set(&self, name: &Token, value: Value) {
+        let class = self.borrow();
+        class.fields.borrow_mut().insert(name.lexeme.clone(), value);
+    }
+}
+
 /* ----------------------- *
  * Instance implementation *
  * ----------------------- */
diff --git a/src/interpreter/interpretable.rs b/src/interpreter/interpretable.rs
index 5ec6ff1..a6016b3 100644
--- a/src/interpreter/interpretable.rs
+++ b/src/interpreter/interpretable.rs
@@ -540,7 +540,7 @@ impl ExprNode {
         get_expr: &GetExpr,
     ) -> InterpreterResult {
         let instance = get_expr.instance.interpret(itpr_state)?.result();
-        instance.with_instance(
+        instance.with_property_carrier(
             |inst| inst.get(&get_expr.name).map(|v| v.into()),
             || error(&get_expr.name, "only instances have properties"),
         )
@@ -553,7 +553,7 @@ impl ExprNode {
         set_expr: &SetExpr,
     ) -> InterpreterResult {
         let instance = set_expr.instance.interpret(itpr_state)?.result();
-        instance.with_instance(
+        instance.with_property_carrier(
             |instance| {
                 let value = set_expr.value.interpret(itpr_state)?.result();
                 instance.set(&set_expr.name, value.clone());
diff --git a/src/interpreter/value.rs b/src/interpreter/value.rs
index 2d97812..a76336e 100644
--- a/src/interpreter/value.rs
+++ b/src/interpreter/value.rs
@@ -1,7 +1,7 @@
 use std::{cell::RefCell, fmt::Display, rc::Rc};
 
 use super::{
-    classes::{Class, ClassRef, Instance, InstanceRef},
+    classes::{Class, ClassRef, Instance, InstanceRef, PropertyCarrier},
     functions::Function,
     native_fn::NativeFunction,
     Callable,
@@ -75,6 +75,25 @@ impl Value {
             _ => ferr(),
         }
     }
+
+    /// Run some code against a property carrier value (either an instance
+    /// or a class). If the value does not contain such an object, an error
+    /// function will be called instead.
+    pub fn with_property_carrier<Fok, Ferr, Rt>(&self, fok: Fok, ferr: Ferr) -> Rt
+    where
+        Fok: FnOnce(&dyn PropertyCarrier) -> Rt,
+        Ferr: FnOnce() -> Rt,
+    {
+        let obj = match self {
+            Value::Object(obj_ref) => obj_ref.borrow(),
+            _ => return ferr(),
+        };
+        match &*obj {
+            Object::Class(cls) => fok(cls),
+            Object::Instance(inst) => fok(inst),
+            _ => ferr(),
+        }
+    }
 }
 
 impl PartialEq for Value {