monkeyinterpreter 0.2.0

An interpreter written in Rust for the monkey programming language described in the book 'Writing An Interpreter In Go' (https://interpreterbook.com/).
Documentation
use std::{cell::RefCell, collections::HashMap, fmt::Display, rc::Rc};

use crate::{
    ast::{BlockStatement, Identifier},
    evaluator::EvalError,
};

pub type RObject = Rc<Object>;
pub type MutableEnvironment = Rc<RefCell<Environment>>;

pub enum Object {
    Integer(i64),
    String(String),
    Boolean(bool),
    Null,
    Return(RObject),
    Function(Function),
    Builtin(Builtin),
    Array(Array),
    Hash(Hash),
}

impl Object {
    pub fn inspect(&self) -> String {
        match &self {
            Object::Integer(x) => x.to_string(),
            Object::Boolean(x) => x.to_string(),
            Object::String(x) => x.to_string(),
            Object::Null => "null".to_string(),
            Object::Return(x) => x.inspect(),
            Object::Function(f) => f.inspect(),
            Object::Builtin(f) => f.name.clone(),
            Object::Array(a) => {
                format!(
                    "[{}]",
                    a.elements
                        .iter()
                        .map(|e| e.inspect())
                        .collect::<Vec<_>>()
                        .join(", ")
                )
            }
            Object::Hash(h) => {
                format!(
                    "{{{}}}",
                    h.elements
                        .iter()
                        .map(|(key, value)| format!("{}: {}", key.to_string(), value.inspect()))
                        .collect::<Vec<_>>()
                        .join(", ")
                )
            }
        }
    }

    pub fn type_name(&self) -> String {
        match &self {
            Object::Integer(_) => "INTEGER",
            Object::String(_) => "STRING",
            Object::Boolean(_) => "BOOLEAN",
            Object::Null => "NULL",
            Object::Return(_) => "RETURN",
            Object::Function(_) => "FUNCTION",
            Object::Builtin(_) => "BUILTIN",
            Object::Array(_) => "ARRAY",
            Object::Hash(_) => "HASH",
        }
        .to_string()
    }

    pub fn is_return(&self) -> bool {
        match self {
            Object::Return(_) => true,
            _ => false,
        }
    }

    pub fn get_return(&self) -> Option<RObject> {
        match self {
            Object::Return(x) => Some(Rc::clone(x)),
            _ => None,
        }
    }

    pub fn as_string(&self) -> Option<&String> {
        if let Self::String(v) = self {
            Some(v)
        } else {
            None
        }
    }
}

pub struct Function {
    pub environment: MutableEnvironment,
    pub parameters: Rc<Vec<Identifier>>,
    pub body: Rc<BlockStatement>,
}

type BuiltinFunction = Box<dyn Fn(&Vec<RObject>) -> Result<RObject, EvalError>>;

pub struct Builtin {
    pub(crate) func: BuiltinFunction,
    pub(crate) name: String,
}

impl Function {
    fn inspect(&self) -> String {
        let params = self
            .parameters
            .iter()
            .map(|i| i.name.clone())
            .collect::<Vec<String>>()
            .join(", ");
        format!("fn ({}) {{\n{}\n}}", params, self.body.to_string())
    }
}

pub struct Array {
    pub elements: Vec<RObject>,
}

pub struct Hash {
    elements: HashMap<HashKey, RObject>,
}

impl Hash {
    pub fn new(pairs: Vec<(RObject, RObject)>) -> Result<Hash, EvalError> {
        let elements: Result<HashMap<_, _>, _> = pairs
            .iter()
            .map(|(k, v)| {
                let k = Self::get_key(Rc::clone(k))?;
                Ok((k, Rc::clone(v)))
            })
            .collect();
        Ok(Hash {
            elements: elements?,
        })
    }

    pub fn get(&self, key: RObject) -> Result<RObject, EvalError> {
        let key = Self::get_key(key)?;
        let res = match self.elements.get(&key) {
            Some(o) => Rc::clone(o),
            None => Rc::new(Object::Null),
        };
        Ok(res)
    }

    fn get_key(object: RObject) -> Result<HashKey, EvalError> {
        let ret = match *Rc::clone(&object) {
            Object::Boolean(b) => HashKey::Boolean(b),
            Object::Integer(i) => HashKey::Integer(i),
            Object::String(_) => HashKey::String(object.as_string().unwrap().clone()),
            _ => {
                return Err(EvalError(format!(
                    "Un-hashable type {} used to index hash map.",
                    object.inspect()
                )))
            }
        };
        Ok(ret)
    }
}

#[derive(Hash, PartialEq, Eq)]
pub enum HashKey {
    Integer(i64),
    Boolean(bool),
    String(String),
}

impl Display for HashKey {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(
            f,
            "{}",
            match self {
                HashKey::Boolean(b) => Object::Boolean(*b).inspect(),
                HashKey::Integer(i) => Object::Integer(*i).inspect(),
                HashKey::String(s) => Object::String(s.clone()).inspect(),
            }
        )
    }
}

pub struct Environment {
    parent: Option<MutableEnvironment>,
    store: HashMap<String, RObject>,
}

impl Environment {
    pub fn new() -> MutableEnvironment {
        Rc::new(RefCell::new(Environment {
            parent: None,
            store: HashMap::new(),
        }))
    }

    pub fn new_enclosed(parent: Option<&MutableEnvironment>) -> MutableEnvironment {
        Rc::new(RefCell::new(Environment {
            parent: parent.and_then(|p| Some(Rc::clone(p))),
            store: HashMap::new(),
        }))
    }

    pub fn get(&self, name: &str) -> Option<Rc<Object>> {
        let ret = self.store.get(name);
        if ret.is_some() {
            return Some(ret.unwrap().clone());
        }

        if self.parent.is_none() {
            return None;
        }

        match self.parent.as_ref().unwrap().borrow_mut().get(name) {
            Some(x) => Some(x.clone()),
            None => None,
        }
    }

    pub fn set(&mut self, name: &str, value: &Rc<Object>) {
        self.store.insert(name.to_owned(), Rc::clone(value));
    }
}