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
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
//! Dictionary Utilities
//!
//! Dictionaries are implemented on top of the indexmap::IndexMap type, a drop-in replacement
//! for std::collections::HashMap that preserves the order of keys.
//!
//! * Warning: when removing items from a dictionary, use `dict_remove`, defined here, as it
//!   preserves the order.  Using `IndexMap::remove` does not.

use crate::list::list_to_string;
use crate::molt_err;
use crate::molt_ok;
use crate::types::Exception;
use crate::types::MoltDict;
use crate::types::MoltList;
use crate::types::MoltResult;
use crate::value::Value;
use indexmap::IndexMap;

/// Create an empty dict.
pub fn dict_new() -> MoltDict {
    IndexMap::new()
}

/// Given a Value containing a dictionary, a list of keys, and a value,
/// inserts the value into the (possibly nested) dictionary, returning the new
/// dictionary value.
pub(crate) fn dict_path_insert(dict_val: &Value, keys: &[Value], value: &Value) -> MoltResult {
    assert!(!keys.is_empty());

    let dict = dict_val.as_dict()?;

    if keys.len() == 1 {
        molt_ok!(dict_insert(&*dict, &keys[0], &value))
    } else if let Some(dval) = dict.get(&keys[0]) {
        molt_ok!(dict_insert(
            &*dict,
            &keys[0],
            &dict_path_insert(dval, &keys[1..], value)?
        ))
    } else {
        let dval = Value::from(dict_new());
        molt_ok!(dict_insert(
            &*dict,
            &keys[0],
            &dict_path_insert(&dval, &keys[1..], value)?
        ))
    }
}

/// Inserts a key and value into a copy of the dictionary, returning the new dictionary.
pub(crate) fn dict_insert(dict: &MoltDict, key: &Value, value: &Value) -> MoltDict {
    let mut new_dict = dict.clone();
    new_dict.insert(key.clone(), value.clone());
    new_dict
}

/// Given a Value containing a dictionary and a list of keys, removes the entry
/// at the end of the path of keys (if any).  All keys but the last must exist in
/// the nested dictionaries.  Returns the modified dictionary value.
pub(crate) fn dict_path_remove(dict_val: &Value, keys: &[Value]) -> MoltResult {
    assert!(!keys.is_empty());

    let dict = dict_val.as_dict()?;

    if keys.len() == 1 {
        molt_ok!(dict_remove(&*dict, &keys[0]))
    } else if let Some(dval) = dict.get(&keys[0]) {
        molt_ok!(dict_insert(
            &*dict,
            &keys[0],
            &dict_path_remove(dval, &keys[1..])?
        ))
    } else {
        molt_err!("key \"{}\" not known in dictionary", keys[0])
    }
}

/// Clones a dictionary and returns a copy with the key removed.
pub(crate) fn dict_remove(dict: &MoltDict, key: &Value) -> MoltDict {
    let mut new_dict = dict.clone();
    new_dict.shift_remove(key);
    new_dict
}

/// Converts a dictionary into a string.
pub(crate) fn dict_to_string(dict: &MoltDict) -> String {
    let mut vec: MoltList = Vec::new();

    for (k, v) in dict {
        vec.push(k.clone());
        vec.push(v.clone());
    }

    list_to_string(&vec)
}

/// Converts a vector of values into a dictionary.  The list must have
/// an even number of elements.
pub(crate) fn list_to_dict(list: &[Value]) -> MoltDict {
    assert!(list.len() % 2 == 0);

    let mut dict = dict_new();

    for i in (0..list.len()).step_by(2) {
        dict.insert(list[i].clone(), list[i + 1].clone());
    }

    dict
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::types::MoltDict;

    #[test]
    fn test_dict_to_string() {
        let mut dict: MoltDict = dict_new();

        assert_eq!(dict_to_string(&dict), "");

        dict.insert("abc".into(), "123".into());

        assert_eq!(dict_to_string(&dict), "abc 123");
    }
}