From 16d62d1608eb0af6f45db2001e6729810f359d6f Mon Sep 17 00:00:00 2001
From: stratact <stratact1@gmail.com>
Date: Tue, 3 Jul 2018 16:05:06 +0000
Subject: [PATCH] Implement BTree-based associative arrays

---
 examples/maps.ion                      | 24 +++++++
 examples/maps.out                      | 13 ++++
 members/lexers/src/assignments/keys.rs | 29 +++++++++
 src/lib/parser/assignments/actions.rs  |  3 +-
 src/lib/parser/assignments/checker.rs  | 50 ++++++++++++---
 src/lib/shell/assignments.rs           | 34 +++++++---
 src/lib/shell/mod.rs                   | 87 +++++++++++++++-----------
 src/lib/shell/variables/mod.rs         | 36 ++++++++++-
 src/lib/types.rs                       |  3 +-
 9 files changed, 223 insertions(+), 56 deletions(-)
 create mode 100644 examples/maps.ion
 create mode 100644 examples/maps.out

diff --git a/examples/maps.ion b/examples/maps.ion
new file mode 100644
index 00000000..64c90c9c
--- /dev/null
+++ b/examples/maps.ion
@@ -0,0 +1,24 @@
+let map:hmap[] = [key1=one key3=three]
+echo @map
+let map[key2] = two
+echo @map
+echo @map[key1]
+echo @map[key2]
+echo @map[key3]
+
+let map:hmap[int] = [uno=1 dos=2 tres=3]
+echo @map
+
+let map:hmap[int[]] = [key1=[1 2 3 4 5] key2=[6 7 8]]
+echo @map
+
+let map:bmap[] = [key1=one key3=three]
+echo @map
+let map[key2] = two
+echo @map
+echo @map[key1]
+echo @map[key2]
+echo @map[key3]
+
+let map:bmap[float] = [ichi=1.0 ni=2.0 san=3.0]
+echo @map
diff --git a/examples/maps.out b/examples/maps.out
new file mode 100644
index 00000000..ba0dd15a
--- /dev/null
+++ b/examples/maps.out
@@ -0,0 +1,13 @@
+one three
+one three two
+one
+two
+three
+3 2 1
+1 2 3 4 5 6 7 8
+one three
+one two three
+one
+two
+three
+1.0 2.0 3.0
diff --git a/members/lexers/src/assignments/keys.rs b/members/lexers/src/assignments/keys.rs
index fe2ce475..cf503166 100644
--- a/members/lexers/src/assignments/keys.rs
+++ b/members/lexers/src/assignments/keys.rs
@@ -165,6 +165,7 @@ mod tests {
         let mut parser = KeyIterator::new("a:int b[] c:bool d e:int[] \
                                            f[0] g[$index] h[1]:int \
                                            i:hmap[] j:hmap[float] k:hmap[int[]] l:hmap[hmap[bool[]]] \
+                                           m:bmap[] n:bmap[int] o:bmap[float[]] p:bmap[hmap[bool]] \
                                            d:a");
         assert_eq!(
             parser.next().unwrap(),
@@ -250,6 +251,34 @@ mod tests {
                 kind: Primitive::HashMap(Box::new(Primitive::HashMap(Box::new(Primitive::BooleanArray)))),
             },)
         );
+        assert_eq!(
+            parser.next().unwrap(),
+            Ok(Key {
+                name: "m",
+                kind: Primitive::BTreeMap(Box::new(Primitive::Any)),
+            },)
+        );
+        assert_eq!(
+            parser.next().unwrap(),
+            Ok(Key {
+                name: "n",
+                kind: Primitive::BTreeMap(Box::new(Primitive::Integer)),
+            },)
+        );
+        assert_eq!(
+            parser.next().unwrap(),
+            Ok(Key {
+                name: "o",
+                kind: Primitive::BTreeMap(Box::new(Primitive::FloatArray)),
+            },)
+        );
+        assert_eq!(
+            parser.next().unwrap(),
+            Ok(Key {
+                name: "p",
+                kind: Primitive::BTreeMap(Box::new(Primitive::HashMap(Box::new(Primitive::Boolean)))),
+            },)
+        );
         assert_eq!(parser.next().unwrap(), Err(TypeError::Invalid("a".into())));
     }
 }
diff --git a/src/lib/parser/assignments/actions.rs b/src/lib/parser/assignments/actions.rs
index 795c6860..c997b919 100644
--- a/src/lib/parser/assignments/actions.rs
+++ b/src/lib/parser/assignments/actions.rs
@@ -101,7 +101,8 @@ impl<'a> Action<'a> {
             | Primitive::FloatArray
             | Primitive::IntegerArray
             | Primitive::StrArray
-            | Primitive::HashMap(_) => if is_array(value) {
+            | Primitive::HashMap(_)
+            | Primitive::BTreeMap(_) => if is_array(value) {
                 Ok(Action::UpdateArray(var, operator, value))
             } else {
                 Err(AssignmentError::InvalidValue(var.kind, Primitive::Any))
diff --git a/src/lib/parser/assignments/checker.rs b/src/lib/parser/assignments/checker.rs
index 2dd7f9e2..84fe2e51 100644
--- a/src/lib/parser/assignments/checker.rs
+++ b/src/lib/parser/assignments/checker.rs
@@ -152,7 +152,7 @@ fn get_array<E: Expander>(shell: &E, value: &str) -> VariableType {
 
 fn get_hash_map<E: Expander>(shell: &E, expression: &str, inner_kind: &Primitive) -> Result<VariableType, TypeError> {
     let array = expand_string(expression, shell, false);
-    let mut hash_map: HashMap = HashMap::with_capacity_and_hasher(array.len(), Default::default());
+    let mut hmap: HashMap = HashMap::with_capacity_and_hasher(array.len(), Default::default());
 
     for string in array {
         if let Some(found) = string.find('=') {
@@ -160,13 +160,49 @@ fn get_hash_map<E: Expander>(shell: &E, expression: &str, inner_kind: &Primitive
             let value = &string[found + 1..];
             match value_check(shell, value, inner_kind) {
                 Ok(VariableType::Str(str_)) => {
-                    hash_map.insert(key.into(), VariableType::Str(str_));
+                    hmap.insert(key.into(), VariableType::Str(str_));
                 }
                 Ok(VariableType::Array(array)) => {
-                    hash_map.insert(key.into(), VariableType::Array(array));
+                    hmap.insert(key.into(), VariableType::Array(array));
                 }
                 Ok(VariableType::HashMap(map)) => {
-                    hash_map.insert(key.into(), VariableType::HashMap(map));
+                    hmap.insert(key.into(), VariableType::HashMap(map));
+                }
+                Ok(VariableType::BTreeMap(map)) => {
+                    hmap.insert(key.into(), VariableType::BTreeMap(map));
+                }
+                Err(type_error) => return Err(type_error),
+                _ => (),
+            }
+
+        } else {
+            return Err(TypeError::BadValue(inner_kind.clone()))
+        }
+    }
+
+    Ok(VariableType::HashMap(hmap))
+}
+
+fn get_btree_map<E: Expander>(shell: &E, expression: &str, inner_kind: &Primitive) -> Result<VariableType, TypeError> {
+    let array = expand_string(expression, shell, false);
+    let mut bmap: BTreeMap = BTreeMap::new();
+
+    for string in array {
+        if let Some(found) = string.find('=') {
+            let key = &string[..found];
+            let value = &string[found + 1..];
+            match value_check(shell, value, inner_kind) {
+                Ok(VariableType::Str(str_)) => {
+                    bmap.insert(key.into(), VariableType::Str(str_));
+                }
+                Ok(VariableType::Array(array)) => {
+                    bmap.insert(key.into(), VariableType::Array(array));
+                }
+                Ok(VariableType::HashMap(map)) => {
+                    bmap.insert(key.into(), VariableType::HashMap(map));
+                }
+                Ok(VariableType::BTreeMap(map)) => {
+                    bmap.insert(key.into(), VariableType::BTreeMap(map));
                 }
                 Err(type_error) => return Err(type_error),
                 _ => (),
@@ -177,7 +213,7 @@ fn get_hash_map<E: Expander>(shell: &E, expression: &str, inner_kind: &Primitive
         }
     }
 
-    Ok(VariableType::HashMap(hash_map))
+    Ok(VariableType::BTreeMap(bmap))
 }
 
 pub(crate) fn value_check<E: Expander>(
@@ -228,8 +264,8 @@ pub(crate) fn value_check<E: Expander>(
             is_float_array(get_array!()).map_err(|_| TypeError::BadValue(expected.clone()))
         }
         Primitive::HashMap(ref kind) if is_array => get_hash_map(shell, value, kind),
-        Primitive::BTreeMap(_) if is_array => Err(TypeError::BadValue(expected.clone())),
-        Primitive::Indexed(_, kind) => value_check(shell, value, &*kind),
+        Primitive::BTreeMap(ref kind) if is_array => get_btree_map(shell, value, kind),
+        Primitive::Indexed(_, ref kind) => value_check(shell, value, kind),
         _ => Err(TypeError::BadValue(expected.clone())),
     }
 }
diff --git a/src/lib/shell/assignments.rs b/src/lib/shell/assignments.rs
index 599e52fe..9f4c798e 100644
--- a/src/lib/shell/assignments.rs
+++ b/src/lib/shell/assignments.rs
@@ -163,8 +163,11 @@ impl VariableStore for Shell {
                             Ok(VariableType::Str(value)) => {
                                 collected.insert(key.name, VariableType::Str(value));
                             }
-                            Ok(VariableType::HashMap(map)) => {
-                                collected.insert(key.name, VariableType::HashMap(map));
+                            Ok(VariableType::HashMap(hmap)) => {
+                                collected.insert(key.name, VariableType::HashMap(hmap));
+                            }
+                            Ok(VariableType::BTreeMap(bmap)) => {
+                                collected.insert(key.name, VariableType::BTreeMap(bmap));
                             }
                             Err(why) => {
                                 eprintln!("ion: assignment error: {}: {}", key.name, why);
@@ -371,11 +374,19 @@ impl VariableStore for Shell {
             match action {
                 Ok(Action::UpdateArray(key, _, _)) => {
                     match collected.remove(key.name) {
-                        map @ Some(VariableType::HashMap(_)) => {
+                        hmap @ Some(VariableType::HashMap(_)) => {
                             if let Primitive::HashMap(_) = key.kind {
-                                self.variables.set(key.name, map.unwrap());
+                                self.variables.set(key.name, hmap.unwrap());
+                            } else if let Primitive::Indexed(_, _) = key.kind {
+                                eprintln!("ion: cannot insert hmap into index");
+                                return FAILURE;
+                            }
+                        }
+                        bmap @ Some(VariableType::BTreeMap(_)) => {
+                            if let Primitive::BTreeMap(_) = key.kind {
+                                self.variables.set(key.name, bmap.unwrap());
                             } else if let Primitive::Indexed(_, _) = key.kind {
-                                eprintln!("ion: cannot insert hash map into index");
+                                eprintln!("ion: cannot insert bmap into index");
                                 return FAILURE;
                             }
                         }
@@ -392,8 +403,11 @@ impl VariableStore for Shell {
                                 match value_check(self, index_value, index_kind) {
                                     Ok(VariableType::Str(ref index)) => {
                                         match self.variables.get_mut(key.name) {
-                                            Some(VariableType::HashMap(map)) => {
-                                                map.entry(SmallString::from_str(index)).or_insert(VariableType::Str(value));
+                                            Some(VariableType::HashMap(hmap)) => {
+                                                hmap.entry(SmallString::from_str(index)).or_insert(VariableType::Str(value));
+                                            }
+                                            Some(VariableType::BTreeMap(bmap)) => {
+                                                bmap.entry(index.clone()).or_insert(VariableType::Str(value));
                                             }
                                             Some(VariableType::Array(array)) => {
                                                 let index_num = match index.parse::<usize>() {
@@ -415,7 +429,11 @@ impl VariableStore for Shell {
                                         return FAILURE;
                                     }
                                     Ok(VariableType::HashMap(_)) => {
-                                        eprintln!("ion: index variable cannot be a hash map");
+                                        eprintln!("ion: index variable cannot be a hmap");
+                                        return FAILURE;
+                                    }
+                                    Ok(VariableType::BTreeMap(_)) => {
+                                        eprintln!("ion: index variable cannot be a bmap");
                                         return FAILURE;
                                     }
                                     Err(why) => {
diff --git a/src/lib/shell/mod.rs b/src/lib/shell/mod.rs
index e3f8173b..29b0bb02 100644
--- a/src/lib/shell/mod.rs
+++ b/src/lib/shell/mod.rs
@@ -455,19 +455,16 @@ impl<'a> Expander for Shell {
 
     /// Expand an array variable with some selection
     fn array(&self, name: &str, selection: Select) -> Option<Array> {
-        let mut found = match self.variables.get::<Array>(name) {
-            Some(array) => match selection {
-                Select::None => None,
-                Select::All => Some(array.clone()),
-                Select::Index(id) => id
+        if let Some(array) = self.variables.get::<Array>(name) {
+            match selection {
+                Select::All => return Some(array.clone()),
+                Select::Index(id) => return id
                     .resolve(array.len())
                     .and_then(|n| array.get(n))
                     .map(|x| Array::from_iter(Some(x.to_owned()))),
                 Select::Range(range) => if let Some((start, length)) = range.bounds(array.len()) {
-                    if array.len() <= start {
-                        None
-                    } else {
-                        Some(
+                    if array.len() > start {
+                        return Some(
                             array
                                 .iter()
                                 .skip(start)
@@ -476,41 +473,57 @@ impl<'a> Expander for Shell {
                                 .collect::<Array>(),
                         )
                     }
-                } else {
-                    None
-                },
-                Select::Key(_) => None,
-            },
-            None => None,
-        };
-        if found.is_none() {
-            found = match self.variables.get::<HashMap>(name) {
-                Some(map) => match selection {
-                    Select::All => {
-                        let mut array = Array::new();
-                        for (_, value) in map.iter() {
-                            let f = format!("{}", value);
-                            match *value {
-                                VariableType::Str(_) => array.push(f),
-                                VariableType::Array(_) | VariableType::HashMap(_) => {
-                                    for split in f.split_whitespace() {
-                                        array.push(split.to_owned());
-                                    }
+                }
+                _ => (),
+            }
+        } else if let Some(hmap) = self.variables.get::<HashMap>(name) {
+            match selection {
+                Select::All => {
+                    let mut array = Array::new();
+                    for (_, value) in hmap.iter() {
+                        let f = format!("{}", value);
+                        match *value {
+                            VariableType::Str(_) => array.push(f),
+                            VariableType::Array(_) | VariableType::HashMap(_) | VariableType::BTreeMap(_) => {
+                                for split in f.split_whitespace() {
+                                    array.push(split.to_owned());
                                 }
-                                _ => (),
                             }
+                            _ => (),
                         }
-                        Some(array)
                     }
-                    Select::Key(ref key) => {
-                        Some(array![format!("{}", map.get(key).unwrap_or(&VariableType::Str("".into())))])
+                    return Some(array)
+                }
+                Select::Key(ref key) => {
+                    return Some(array![format!("{}", hmap.get(key).unwrap_or(&VariableType::Str("".into())))])
+                }
+                _ => (),
+            }
+        } else if let Some(bmap) = self.variables.get::<BTreeMap>(name) {
+            match selection {
+                Select::All => {
+                    let mut array = Array::new();
+                    for (_, value) in bmap.iter() {
+                        let f = format!("{}", value);
+                        match *value {
+                            VariableType::Str(_) => array.push(f),
+                            VariableType::Array(_) | VariableType::HashMap(_) | VariableType::BTreeMap(_) => {
+                                for split in f.split_whitespace() {
+                                    array.push(split.to_owned());
+                                }
+                            }
+                            _ => (),
+                        }
                     }
-                    _ => None,
-                },
-                None => None,
+                    return Some(array)
+                }
+                Select::Key(ref key) => {
+                    return Some(array![format!("{}", bmap.get(&(&*key).to_string()).unwrap_or(&VariableType::Str("".into())))])
+                }
+                _ => (),
             }
         }
-        found
+        None
     }
 
     fn tilde(&self, input: &str) -> Option<String> {
diff --git a/src/lib/shell/variables/mod.rs b/src/lib/shell/variables/mod.rs
index c93f2b6f..c9281bb3 100644
--- a/src/lib/shell/variables/mod.rs
+++ b/src/lib/shell/variables/mod.rs
@@ -18,7 +18,7 @@ use std::{
 };
 use sys::{self, geteuid, getpid, getuid, is_root, variables as self_sys};
 use types::{
-    self, Alias, Array, HashMap, Identifier, Key, Value,
+    self, Alias, Array, BTreeMap, HashMap, Identifier, Key, Value,
 };
 use unicode_segmentation::UnicodeSegmentation;
 use xdg::BaseDirectories;
@@ -33,6 +33,7 @@ pub enum VariableType {
     Alias(Alias),
     Array(Array),
     HashMap(HashMap),
+    BTreeMap(BTreeMap),
     Function(Function),
     None,
 }
@@ -73,6 +74,15 @@ impl From<VariableType> for HashMap {
     }
 }
 
+impl From<VariableType> for BTreeMap {
+    fn from(var: VariableType) -> Self {
+        match var {
+            VariableType::BTreeMap(btree_map) => btree_map,
+            _ => BTreeMap::new(),
+        }
+    }
+}
+
 impl From<VariableType> for Function {
     fn from(var: VariableType) -> Self {
         match var {
@@ -106,6 +116,12 @@ impl From<HashMap> for VariableType {
     }
 }
 
+impl From<BTreeMap> for VariableType {
+    fn from(btree_map: BTreeMap) -> Self {
+        VariableType::BTreeMap(btree_map)
+    }
+}
+
 impl From<Function> for VariableType {
     fn from(function: Function) -> Self {
          VariableType::Function(function)
@@ -127,6 +143,15 @@ impl fmt::Display for VariableType {
                 format.pop();
                 write!(f, "{}", format)
             }
+            VariableType::BTreeMap(ref map) => {
+                let mut format = map.into_iter().fold(String::new(), |mut format, (_, var_type)| {
+                    format.push_str(&format!("{}", var_type));
+                    format.push(' ');
+                    format
+                });
+                format.pop();
+                write!(f, "{}", format)
+            }
             _ => write!(f, "")
         }
     }
@@ -139,12 +164,14 @@ pub struct Scope {
     /// Any previous scopes need to be accessed through `super::`.
     namespace: bool
 }
+
 impl Deref for Scope {
     type Target = FnvHashMap<Identifier, VariableType>;
     fn deref(&self) -> &Self::Target {
         &self.vars
     }
 }
+
 impl DerefMut for Scope {
     fn deref_mut(&mut self) -> &mut Self::Target {
         &mut self.vars
@@ -508,7 +535,12 @@ impl Variables {
             }
         } else if specified_type == TypeId::of::<types::HashMap>() {
             match self.get_ref(name) {
-                Some(VariableType::HashMap(hash_map)) => Some(T::from(VariableType::HashMap(hash_map.clone()))),
+                Some(VariableType::HashMap(hmap)) => Some(T::from(VariableType::HashMap(hmap.clone()))),
+                _ => None
+            }
+        } else if specified_type == TypeId::of::<types::BTreeMap>() {
+            match self.get_ref(name) {
+                Some(VariableType::BTreeMap(bmap)) => Some(T::from(VariableType::BTreeMap(bmap.clone()))),
                 _ => None
             }
         } else if specified_type == TypeId::of::<Function>() {
diff --git a/src/lib/types.rs b/src/lib/types.rs
index 96008dba..3a5b6944 100644
--- a/src/lib/types.rs
+++ b/src/lib/types.rs
@@ -2,10 +2,11 @@ use fnv::FnvHashMap;
 use smallstring::SmallString;
 use smallvec::SmallVec;
 use shell::variables::VariableType;
-use std::ops::{Deref, DerefMut};
+use std::{collections::BTreeMap as StdBTreeMap, ops::{Deref, DerefMut}};
 
 pub type Array = SmallVec<[Value; 4]>;
 pub type HashMap = FnvHashMap<Key, VariableType>;
+pub type BTreeMap = StdBTreeMap<String, VariableType>;
 pub type Identifier = SmallString;
 pub type Key = SmallString;
 pub type Value = String;
-- 
GitLab