scheme4r 0.2.3

Scheme interpreter for rust
Documentation
use std::collections::BTreeMap;

use super::*;
use crate::runtime::DictKey;

pub(super) fn install(env: &mut Environment) {
    define_builtin(env, "dict?", is_dict);
    define_builtin(env, "make-dict", make_dict);
    define_builtin(env, "dict", dict_from_pairs);
    define_builtin(env, "dict-size", dict_size);
    define_builtin(env, "dict-has-key?", dict_has_key);
    define_builtin(env, "dict-ref", dict_ref);
    define_builtin(env, "dict-set!", dict_set);
    define_builtin(env, "dict-delete!", dict_delete);
    define_builtin(env, "dict-clear!", dict_clear);
    define_builtin(env, "dict-keys", dict_keys);
}

fn is_dict(_: &Engine, args: &[Value]) -> Result<Value, SchemeError> {
    expect_arity("dict?", args, 1)?;
    Ok(Value::Boolean(matches!(args[0], Value::Dict(_))))
}

fn make_dict(_: &Engine, args: &[Value]) -> Result<Value, SchemeError> {
    expect_arity("make-dict", args, 0)?;
    Ok(Value::dict(BTreeMap::new()))
}

fn dict_from_pairs(_: &Engine, args: &[Value]) -> Result<Value, SchemeError> {
    if args.len() % 2 != 0 {
        return Err(SchemeError::arity(
            "'dict' expects an even number of key/value arguments",
        ));
    }

    let mut map = BTreeMap::new();
    let mut index = 0;
    while index < args.len() {
        let key = expect_dict_key("dict", &args[index])?;
        let value = args[index + 1].clone();
        map.insert(key, value);
        index += 2;
    }

    Ok(Value::dict(map))
}

fn dict_size(_: &Engine, args: &[Value]) -> Result<Value, SchemeError> {
    expect_arity("dict-size", args, 1)?;
    let dict = expect_dict("dict-size", &args[0])?;
    let len = dict.borrow().len() as i64;
    Ok(Value::Number(len))
}

fn dict_has_key(_: &Engine, args: &[Value]) -> Result<Value, SchemeError> {
    expect_arity("dict-has-key?", args, 2)?;
    let dict = expect_dict("dict-has-key?", &args[0])?;
    let key = expect_dict_key("dict-has-key?", &args[1])?;
    let has_key = dict.borrow().contains_key(&key);
    Ok(Value::Boolean(has_key))
}

fn dict_ref(_: &Engine, args: &[Value]) -> Result<Value, SchemeError> {
    if args.len() != 2 && args.len() != 3 {
        return Err(SchemeError::arity("'dict-ref' expects 2 or 3 arguments"));
    }

    let dict = expect_dict("dict-ref", &args[0])?;
    let key = expect_dict_key("dict-ref", &args[1])?;
    let maybe_value = dict.borrow().get(&key).cloned();
    match maybe_value {
        Some(value) => Ok(value),
        None => match args.get(2) {
            Some(default) => Ok(default.clone()),
            None => Err(SchemeError::runtime("'dict-ref' key does not exist")),
        },
    }
}

fn dict_set(_: &Engine, args: &[Value]) -> Result<Value, SchemeError> {
    expect_arity("dict-set!", args, 3)?;
    let dict = expect_dict("dict-set!", &args[0])?;
    let key = expect_dict_key("dict-set!", &args[1])?;
    dict.borrow_mut().insert(key, args[2].clone());
    Ok(Value::Unspecified)
}

fn dict_delete(_: &Engine, args: &[Value]) -> Result<Value, SchemeError> {
    expect_arity("dict-delete!", args, 2)?;
    let dict = expect_dict("dict-delete!", &args[0])?;
    let key = expect_dict_key("dict-delete!", &args[1])?;
    let removed = dict.borrow_mut().remove(&key).is_some();
    Ok(Value::Boolean(removed))
}

fn dict_clear(_: &Engine, args: &[Value]) -> Result<Value, SchemeError> {
    expect_arity("dict-clear!", args, 1)?;
    let dict = expect_dict("dict-clear!", &args[0])?;
    dict.borrow_mut().clear();
    Ok(Value::Unspecified)
}

fn dict_keys(_: &Engine, args: &[Value]) -> Result<Value, SchemeError> {
    expect_arity("dict-keys", args, 1)?;
    let dict = expect_dict("dict-keys", &args[0])?;
    let keys = dict
        .borrow()
        .keys()
        .map(DictKey::to_value)
        .collect::<Vec<_>>();
    Ok(Value::list(keys))
}

fn expect_dict_key(name: &str, value: &Value) -> Result<DictKey, SchemeError> {
    DictKey::try_from_value(value).ok_or_else(|| {
        SchemeError::type_error(format!(
            "'{name}' key must be boolean/number/character/string/symbol/()"
        ))
    })
}