1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
use std::cell::{Ref, RefCell, RefMut};
use std::collections::HashMap;
use std::fmt;
use std::hash::{Hash, Hasher};
use std::rc::Rc;

use crate::eval::Env;
use crate::objects::{Datum, Eval, Object, Vtable};
use crate::unwind::Unwind;

pub struct Record {
    data: RefCell<HashMap<String, Object>>,
}

impl Record {
    pub fn borrow(&self) -> Ref<HashMap<String, Object>> {
        self.data.borrow()
    }
    pub fn borrow_mut(&self) -> RefMut<HashMap<String, Object>> {
        self.data.borrow_mut()
    }
}

impl PartialEq for Record {
    fn eq(&self, other: &Self) -> bool {
        std::ptr::eq(self, other)
    }
}

impl Eq for Record {}

impl Hash for Record {
    fn hash<H: Hasher>(&self, state: &mut H) {
        std::ptr::hash(self, state);
    }
}

impl fmt::Debug for Record {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "{{")?;
        let mut first = true;
        for (k, v) in self.borrow().iter() {
            if first {
                first = false;
            } else {
                write!(f, ", ")?;
            }
            write!(f, "{}: {:?}", k, v)?;
        }
        write!(f, "}}")
    }
}

pub fn class_vtable() -> Vtable {
    let vt = Vtable::new("class Record");
    vt.add_primitive_method_or_panic("perform:with:", class_record_perform_with);
    vt
}

pub fn instance_vtable() -> Vtable {
    let vt = Vtable::new("Record");
    vt.add_primitive_method_or_panic("perform:with:", record_perform_with);
    vt.add_primitive_method_or_panic("displayOn:", record_display_on);
    vt
}

pub fn as_record<'a>(obj: &'a Object, ctx: &str) -> Result<&'a Record, Unwind> {
    match &obj.datum {
        Datum::Record(ref record) => Ok(record),
        _ => Unwind::error(&format!("{:?} is not a Record in {}", obj, ctx)),
    }
}

fn class_record_perform_with(_receiver: &Object, args: &[Object], env: &Env) -> Eval {
    let mut data = HashMap::new();
    let selector = args[0].string_as_str();
    let values = args[1].as_array("in Record##perform:with:")?.borrow();
    for (k, v) in selector.split(':').zip(&*values) {
        data.insert(k.to_string(), v.clone());
    }
    Ok(Object {
        vtable: env.foo.record_vtable.clone(),
        datum: Datum::Record(Rc::new(Record {
            data: RefCell::new(data),
        })),
    })
}

fn record_perform_with(receiver: &Object, args: &[Object], _env: &Env) -> Eval {
    let r: &Record = receiver.as_record("in Record#perform:with:")?;
    let data: Ref<HashMap<_, _>> = r.borrow();
    let selector = args[0].string_as_str();
    match data.get(selector) {
        Some(obj) => Ok(obj.clone()),
        None => Unwind::message_error(receiver, selector, &args[1..2]),
    }
}

fn record_display_on(receiver: &Object, args: &[Object], env: &Env) -> Eval {
    args[0].send("print:", &[receiver.send("toString", &[], env)?], env)
}