From b9bde648edce79b99d4744c23fa0d500ca3bb9d4 Mon Sep 17 00:00:00 2001
From: Michael Aaron Murphy <mmstickman@gmail.com>
Date: Sun, 1 Jul 2018 11:25:28 -0600
Subject: [PATCH] Concatenate, ConcatenateHead, & Filter Assignment Operators

---
 .../lexers/src}/assignments/keys.rs           | 105 +----------
 members/lexers/src/assignments/mod.rs         | 170 ++++++++++++++++++
 members/lexers/src/assignments/operator.rs    |  55 ++++++
 members/lexers/src/assignments/primitive.rs   |  95 ++++++++++
 members/lexers/src/lib.rs                     |   4 +-
 src/lib/parser/assignments/actions.rs         |  10 +-
 src/lib/parser/assignments/checker.rs         |   6 +-
 src/lib/parser/assignments/mod.rs             |   8 +-
 src/lib/parser/assignments/operator.rs        |  42 -----
 src/lib/parser/assignments/splitter.rs        |  79 --------
 src/lib/parser/mod.rs                         |   2 +-
 src/lib/parser/statement/functions.rs         |   5 +-
 src/lib/parser/statement/parse.rs             |  32 +---
 src/lib/shell/assignments.rs                  |   2 +
 src/lib/shell/flow_control.rs                 |   1 +
 15 files changed, 348 insertions(+), 268 deletions(-)
 rename {src/lib/parser => members/lexers/src}/assignments/keys.rs (68%)
 create mode 100644 members/lexers/src/assignments/mod.rs
 create mode 100644 members/lexers/src/assignments/operator.rs
 create mode 100644 members/lexers/src/assignments/primitive.rs
 delete mode 100644 src/lib/parser/assignments/operator.rs
 delete mode 100644 src/lib/parser/assignments/splitter.rs

diff --git a/src/lib/parser/assignments/keys.rs b/members/lexers/src/assignments/keys.rs
similarity index 68%
rename from src/lib/parser/assignments/keys.rs
rename to members/lexers/src/assignments/keys.rs
index 365827d4..fe2ce475 100644
--- a/src/lib/parser/assignments/keys.rs
+++ b/members/lexers/src/assignments/keys.rs
@@ -1,9 +1,10 @@
 use std::fmt::{self, Display, Formatter};
+use super::Primitive;
 
 /// Keys are used in assignments to define which variable will be set, and whether the correct
 /// types are being assigned.
 #[derive(Debug, PartialEq, Clone)]
-pub(crate) struct Key<'a> {
+pub struct Key<'a> {
     pub kind: Primitive,
     pub name: &'a str,
 }
@@ -11,13 +12,13 @@ pub(crate) struct Key<'a> {
 /// Functions require that their keys to have a longer lifetime, and that is made possible
 /// by eliminating the lifetime requirements via allocating a `String`.
 #[derive(Debug, PartialEq, Clone)]
-pub(crate) struct KeyBuf {
+pub struct KeyBuf {
     pub kind: Primitive,
     pub name: String,
 }
 
 #[derive(Debug, PartialEq)]
-pub(crate) enum TypeError {
+pub enum TypeError {
     Invalid(String),
     BadValue(Primitive),
 }
@@ -49,103 +50,9 @@ impl<'a> From<Key<'a>> for KeyBuf {
     }
 }
 
-/// A primitive defines the type that a requested value should satisfy.
-#[derive(Debug, PartialEq, Clone)]
-pub enum Primitive {
-    Any,
-    AnyArray,
-    Str,
-    StrArray,
-    Boolean,
-    BooleanArray,
-    Integer,
-    IntegerArray,
-    Float,
-    FloatArray,
-    HashMap(Box<Primitive>),
-    BTreeMap(Box<Primitive>),
-    Indexed(String, Box<Primitive>),
-}
-
-impl Primitive {
-    fn parse(data: &str) -> Option<Primitive> {
-        let data = match data {
-            "[]" => Primitive::AnyArray,
-            "str" => Primitive::Str,
-            "str[]" => Primitive::StrArray,
-            "bool" => Primitive::Boolean,
-            "bool[]" => Primitive::BooleanArray,
-            "int" => Primitive::Integer,
-            "int[]" => Primitive::IntegerArray,
-            "float" => Primitive::Float,
-            "float[]" => Primitive::FloatArray,
-            kind => {
-                fn parse_inner_hash_map(inner: &str) -> Option<Primitive> {
-                    match inner {
-                        "" => Some(Primitive::HashMap(Box::new(Primitive::Any))),
-                        _  => Primitive::parse(inner).map(|p| Primitive::HashMap(Box::new(p)))
-                    }
-                }
-                fn parse_inner_btree_map(inner: &str) -> Option<Primitive> {
-                    match inner {
-                        "" => Some(Primitive::BTreeMap(Box::new(Primitive::Any))),
-                        _  => Primitive::parse(inner).map(|p| Primitive::BTreeMap(Box::new(p)))
-                    }
-                }
-
-                let res = if kind.starts_with("hmap[") {
-                    let kind = &kind[5..];
-                    kind.rfind(']').map(|found| &kind[..found]).and_then(parse_inner_hash_map)
-                } else if kind.starts_with("bmap[") {
-                    let kind = &kind[5..];
-                    kind.rfind(']').map(|found| &kind[..found]).and_then(parse_inner_btree_map)
-                } else {
-                    None
-                };
-
-                if let Some(data) = res {
-                    data
-                } else {
-                    return None;
-                }
-            }
-        };
-        Some(data)
-    }
-}
-
-impl Display for Primitive {
-    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
-        match *self {
-            Primitive::Any | Primitive::Str => write!(f, "str"),
-            Primitive::AnyArray => write!(f, "[]"),
-            Primitive::Boolean => write!(f, "bool"),
-            Primitive::BooleanArray => write!(f, "bool[]"),
-            Primitive::Float => write!(f, "float"),
-            Primitive::FloatArray => write!(f, "float[]"),
-            Primitive::Integer => write!(f, "int"),
-            Primitive::IntegerArray => write!(f, "int[]"),
-            Primitive::StrArray => write!(f, "str[]"),
-            Primitive::HashMap(ref kind) => {
-                match **kind {
-                    Primitive::Any | Primitive::Str => write!(f, "hmap[]"),
-                    ref kind => write!(f, "hmap[{}]", kind),
-                }
-            }
-            Primitive::BTreeMap(ref kind) => {
-                match **kind {
-                    Primitive::Any | Primitive::Str => write!(f, "bmap[]"),
-                    ref kind => write!(f, "bmap[{}]", kind),
-                }
-            }
-            Primitive::Indexed(_, ref kind) => write!(f, "{}", kind),
-        }
-    }
-}
-
 /// Quite simply, an iterator that returns keys.
 #[derive(Debug, PartialEq)]
-pub(crate) struct KeyIterator<'a> {
+pub struct KeyIterator<'a> {
     data: &'a str,
     read: usize,
 }
@@ -211,7 +118,7 @@ impl<'a> KeyIterator<'a> {
         }
     }
 
-    pub(crate) fn new(data: &'a str) -> KeyIterator<'a> { KeyIterator { data, read: 0 } }
+    pub fn new(data: &'a str) -> KeyIterator<'a> { KeyIterator { data, read: 0 } }
 }
 
 impl<'a> Iterator for KeyIterator<'a> {
diff --git a/members/lexers/src/assignments/mod.rs b/members/lexers/src/assignments/mod.rs
new file mode 100644
index 00000000..c128309c
--- /dev/null
+++ b/members/lexers/src/assignments/mod.rs
@@ -0,0 +1,170 @@
+mod keys;
+mod operator;
+mod primitive;
+
+pub use self::keys::{Key, KeyBuf, KeyIterator, TypeError};
+pub use self::operator::Operator;
+pub use self::primitive::Primitive;
+
+/// Given an valid assignment expression, this will split it into `keys`,
+/// `operator`, `values`.
+pub fn assignment_lexer<'a>(statement: &'a str) -> (Option<&'a str>, Option<Operator>, Option<&'a str>) {
+    let statement = statement.trim();
+    if statement.is_empty() {
+        return (None, None, None);
+    }
+
+    let (mut read, mut start) = (0, 0);
+    let as_bytes = statement.as_bytes();
+    let mut bytes = statement.bytes();
+    let mut operator = None;
+
+    while let Some(byte) = bytes.next() {
+        if b'=' == byte {
+            operator = Some(Operator::Equal);
+            if as_bytes.get(read + 1).is_none() {
+                return (Some(&statement[..read].trim()), operator, None);
+            }
+            start = read;
+            read += 1;
+            break;
+        } else {
+            match find_operator(as_bytes, read) {
+                None => (),
+                Some((op, found)) => {
+                    operator = Some(op);
+                    start = read;
+                    read = found;
+                    break
+                }
+            }
+        }
+        read += 1;
+    }
+
+    if statement.len() == read {
+        return (Some(statement.trim()), None, None);
+    }
+
+    let keys = statement[..start].trim_right();
+
+    if read == statement.len() {
+        return (Some(keys), operator, None);
+    }
+
+    let values = &statement[read..];
+    (Some(keys), operator, Some(values.trim()))
+}
+
+fn find_operator(bytes: &[u8], read: usize) -> Option<(Operator, usize)> {
+    if bytes.len() <= read + 3 {
+        None
+    } else if bytes[read+1] == b'=' {
+        Operator::parse_single(bytes[read]).map(|op| (op, read + 2))
+    } else if bytes[read+2] == b'=' {
+        Operator::parse_double(&bytes[read..read+2]).map(|op| (op, read + 3))
+    } else {
+        None
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+
+    #[test]
+    fn assignment_splitting() {
+        assert_eq!(
+            assignment_lexer(""),
+            (None, None, None)
+        );
+        assert_eq!(
+            assignment_lexer("abc"),
+            (Some("abc"), None, None)
+        );
+
+        assert_eq!(
+            assignment_lexer("abc+=def"),
+            (Some("abc"), Some(Operator::Add), Some("def"))
+        );
+
+        assert_eq!(
+            assignment_lexer("abc ="),
+            (Some("abc"), Some(Operator::Equal), None)
+        );
+
+        assert_eq!(
+            assignment_lexer("abc =  "),
+            (Some("abc"), Some(Operator::Equal), None)
+        );
+
+        assert_eq!(
+            assignment_lexer("abc = def"),
+            (Some("abc"), Some(Operator::Equal), Some("def"))
+        );
+
+        assert_eq!(
+            assignment_lexer("abc=def"),
+            (Some("abc"), Some(Operator::Equal), Some("def"))
+        );
+
+        assert_eq!(
+            assignment_lexer("def ghi += 124 523"),
+            (Some("def ghi"), Some(Operator::Add), Some("124 523"))
+        )
+    }
+
+    #[test]
+    fn arithmetic_assignments() {
+        assert_eq!(
+            assignment_lexer("abc //= def"),
+            (Some("abc"), Some(Operator::IntegerDivide), Some("def"))
+        );
+
+        assert_eq!(
+            assignment_lexer("abc **= def"),
+            (Some("abc"), Some(Operator::Exponent), Some("def"))
+        );
+
+        assert_eq!(
+            assignment_lexer("abc += def"),
+            (Some("abc"), Some(Operator::Add), Some("def"))
+        );
+
+        assert_eq!(
+            assignment_lexer("abc -= def"),
+            (Some("abc"), Some(Operator::Subtract), Some("def"))
+        );
+
+        assert_eq!(
+            assignment_lexer("abc /= def"),
+            (Some("abc"), Some(Operator::Divide), Some("def"))
+        );
+
+        assert_eq!(
+            assignment_lexer("abc *= def"),
+            (Some("abc"), Some(Operator::Multiply), Some("def"))
+        );
+    }
+
+    #[test]
+    fn concatenate_assignments() {
+        assert_eq!(
+            assignment_lexer("abc ++= def"),
+            (Some("abc"), Some(Operator::Concatenate), Some("def"))
+        );
+
+        assert_eq!(
+            assignment_lexer("abc::=def"),
+            (Some("abc"), Some(Operator::ConcatenateHead), Some("def"))
+        );
+    }
+
+    #[test]
+    fn filter_assignment() {
+        assert_eq!(
+            assignment_lexer("abc \\\\= def"),
+            (Some("abc"), Some(Operator::Filter), Some("def"))
+        )
+    }
+}
diff --git a/members/lexers/src/assignments/operator.rs b/members/lexers/src/assignments/operator.rs
new file mode 100644
index 00000000..96126a72
--- /dev/null
+++ b/members/lexers/src/assignments/operator.rs
@@ -0,0 +1,55 @@
+use std::fmt::{self, Display, Formatter};
+
+#[derive(Debug, PartialEq, Clone, Copy)]
+pub enum Operator {
+    Add,
+    Concatenate,
+    ConcatenateHead,
+    Divide,
+    Equal,
+    Exponent,
+    Filter,
+    IntegerDivide,
+    Multiply,
+    Subtract,
+}
+
+impl Operator {
+    pub(crate) fn parse_single(data: u8) -> Option<Operator> {
+        match data {
+            b'+' => Some(Operator::Add),
+            b'-' => Some(Operator::Subtract),
+            b'/' => Some(Operator::Divide),
+            b'*' => Some(Operator::Multiply),
+            _    => None,
+        }
+    }
+
+    pub(crate) fn parse_double(data: &[u8]) -> Option<Operator> {
+        match data {
+            b"//"   => Some(Operator::IntegerDivide),
+            b"**"   => Some(Operator::Exponent),
+            b"++"   => Some(Operator::Concatenate),
+            b"::"   => Some(Operator::ConcatenateHead),
+            b"\\\\" => Some(Operator::Filter),
+            _       => None,
+        }
+    }
+}
+
+impl Display for Operator {
+    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
+        write!(f, "{}", match *self {
+            Operator::Add => "+=",
+            Operator::Concatenate => "++=",
+            Operator::ConcatenateHead => "::=",
+            Operator::Filter => "\\\\=",
+            Operator::Divide => "/=",
+            Operator::Equal => "=",
+            Operator::Exponent => "**=",
+            Operator::IntegerDivide => "//=",
+            Operator::Multiply => "*=",
+            Operator::Subtract => "-=",
+        })
+    }
+}
diff --git a/members/lexers/src/assignments/primitive.rs b/members/lexers/src/assignments/primitive.rs
new file mode 100644
index 00000000..001a519b
--- /dev/null
+++ b/members/lexers/src/assignments/primitive.rs
@@ -0,0 +1,95 @@
+use std::fmt::{self, Display, Formatter};
+
+/// A primitive defines the type that a requested value should satisfy.
+#[derive(Debug, PartialEq, Clone)]
+pub enum Primitive {
+    Any,
+    AnyArray,
+    Str,
+    StrArray,
+    Boolean,
+    BooleanArray,
+    Integer,
+    IntegerArray,
+    Float,
+    FloatArray,
+    HashMap(Box<Primitive>),
+    BTreeMap(Box<Primitive>),
+    Indexed(String, Box<Primitive>),
+}
+
+impl Primitive {
+    pub(crate) fn parse(data: &str) -> Option<Primitive> {
+        let data = match data {
+            "[]" => Primitive::AnyArray,
+            "str" => Primitive::Str,
+            "str[]" => Primitive::StrArray,
+            "bool" => Primitive::Boolean,
+            "bool[]" => Primitive::BooleanArray,
+            "int" => Primitive::Integer,
+            "int[]" => Primitive::IntegerArray,
+            "float" => Primitive::Float,
+            "float[]" => Primitive::FloatArray,
+            kind => {
+                fn parse_inner_hash_map(inner: &str) -> Option<Primitive> {
+                    match inner {
+                        "" => Some(Primitive::HashMap(Box::new(Primitive::Any))),
+                        _  => Primitive::parse(inner).map(|p| Primitive::HashMap(Box::new(p)))
+                    }
+                }
+                fn parse_inner_btree_map(inner: &str) -> Option<Primitive> {
+                    match inner {
+                        "" => Some(Primitive::BTreeMap(Box::new(Primitive::Any))),
+                        _  => Primitive::parse(inner).map(|p| Primitive::BTreeMap(Box::new(p)))
+                    }
+                }
+
+                let res = if kind.starts_with("hmap[") {
+                    let kind = &kind[5..];
+                    kind.rfind(']').map(|found| &kind[..found]).and_then(parse_inner_hash_map)
+                } else if kind.starts_with("bmap[") {
+                    let kind = &kind[5..];
+                    kind.rfind(']').map(|found| &kind[..found]).and_then(parse_inner_btree_map)
+                } else {
+                    None
+                };
+
+                if let Some(data) = res {
+                    data
+                } else {
+                    return None;
+                }
+            }
+        };
+        Some(data)
+    }
+}
+
+impl Display for Primitive {
+    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
+        match *self {
+            Primitive::Any | Primitive::Str => write!(f, "str"),
+            Primitive::AnyArray => write!(f, "[]"),
+            Primitive::Boolean => write!(f, "bool"),
+            Primitive::BooleanArray => write!(f, "bool[]"),
+            Primitive::Float => write!(f, "float"),
+            Primitive::FloatArray => write!(f, "float[]"),
+            Primitive::Integer => write!(f, "int"),
+            Primitive::IntegerArray => write!(f, "int[]"),
+            Primitive::StrArray => write!(f, "str[]"),
+            Primitive::HashMap(ref kind) => {
+                match **kind {
+                    Primitive::Any | Primitive::Str => write!(f, "hmap[]"),
+                    ref kind => write!(f, "hmap[{}]", kind),
+                }
+            }
+            Primitive::BTreeMap(ref kind) => {
+                match **kind {
+                    Primitive::Any | Primitive::Str => write!(f, "bmap[]"),
+                    ref kind => write!(f, "bmap[{}]", kind),
+                }
+            }
+            Primitive::Indexed(_, ref kind) => write!(f, "{}", kind),
+        }
+    }
+}
diff --git a/members/lexers/src/lib.rs b/members/lexers/src/lib.rs
index 35638230..7d273c01 100644
--- a/members/lexers/src/lib.rs
+++ b/members/lexers/src/lib.rs
@@ -1,7 +1,9 @@
+#![feature(nll)]
 #[macro_use]
 extern crate bitflags;
 
+pub mod assignments;
 pub mod arguments;
 pub mod designators;
 
-pub use self::{arguments::*, designators::*};
+pub use self::{assignments::*, arguments::*, designators::*};
diff --git a/src/lib/parser/assignments/actions.rs b/src/lib/parser/assignments/actions.rs
index ebf6c0a1..011b5403 100644
--- a/src/lib/parser/assignments/actions.rs
+++ b/src/lib/parser/assignments/actions.rs
@@ -1,10 +1,9 @@
-use lexers::ArgumentSplitter;
-use super::{checker::*, *};
+use lexers::{ArgumentSplitter, assignments::{Key, KeyIterator, Operator, Primitive, TypeError}};
+use super::checker::*;
 use std::fmt::{self, Display, Formatter};
 
 #[derive(Debug, PartialEq)]
 pub(crate) enum AssignmentError<'a> {
-    InvalidOperator(&'a str),
     InvalidValue(Primitive, Primitive),
     TypeError(TypeError),
     ExtraValues(&'a str, &'a str),
@@ -14,7 +13,6 @@ pub(crate) enum AssignmentError<'a> {
 impl<'a> Display for AssignmentError<'a> {
     fn fmt(&self, f: &mut Formatter) -> fmt::Result {
         match *self {
-            AssignmentError::InvalidOperator(op) => write!(f, "invalid operator supplied: {}", op),
             AssignmentError::InvalidValue(ref expected, ref actual) => {
                 write!(f, "expected {}, but received {}", expected, actual)
             }
@@ -124,10 +122,10 @@ mod tests {
     use super::*;
 
     fn split(input: &str) -> (String, Operator, String) {
-        let (keys, op, vals) = split_assignment(input);
+        let (keys, op, vals) = assignment_lexer(input);
         (
             keys.unwrap().into(),
-            Operator::parse(op.unwrap()).unwrap(),
+            op.unwrap(),
             vals.unwrap().into(),
         )
     }
diff --git a/src/lib/parser/assignments/checker.rs b/src/lib/parser/assignments/checker.rs
index 335f0a97..2dd7f9e2 100644
--- a/src/lib/parser/assignments/checker.rs
+++ b/src/lib/parser/assignments/checker.rs
@@ -1,7 +1,5 @@
-use super::{
-    super::{expand_string, Expander}, Primitive, TypeError,
-};
-
+use super::super::{expand_string, Expander};
+use lexers::assignments::{Primitive, TypeError};
 use shell::variables::VariableType;
 use types::*;
 use std::iter::Iterator;
diff --git a/src/lib/parser/assignments/mod.rs b/src/lib/parser/assignments/mod.rs
index a63aab31..fb853171 100644
--- a/src/lib/parser/assignments/mod.rs
+++ b/src/lib/parser/assignments/mod.rs
@@ -1,11 +1,5 @@
 mod actions;
 mod checker;
-mod keys;
-mod operator;
-mod splitter;
-
-pub use self::keys::Primitive;
 pub(crate) use self::{
-    actions::{Action, AssignmentActions, AssignmentError}, checker::{is_array, value_check},
-    keys::{Key, KeyBuf, KeyIterator, TypeError}, operator::Operator, splitter::split_assignment,
+    actions::{Action, AssignmentActions}, checker::{is_array, value_check}
 };
diff --git a/src/lib/parser/assignments/operator.rs b/src/lib/parser/assignments/operator.rs
deleted file mode 100644
index 332444b5..00000000
--- a/src/lib/parser/assignments/operator.rs
+++ /dev/null
@@ -1,42 +0,0 @@
-use super::AssignmentError;
-use std::fmt::{self, Display, Formatter};
-
-#[derive(Debug, PartialEq, Clone, Copy)]
-pub(crate) enum Operator {
-    Add,
-    Subtract,
-    Divide,
-    IntegerDivide,
-    Multiply,
-    Exponent,
-    Equal,
-}
-
-impl Operator {
-    pub(crate) fn parse(data: &str) -> Result<Operator, AssignmentError> {
-        match data {
-            "=" => Ok(Operator::Equal),
-            "+=" => Ok(Operator::Add),
-            "-=" => Ok(Operator::Subtract),
-            "/=" => Ok(Operator::Divide),
-            "//=" => Ok(Operator::IntegerDivide),
-            "*=" => Ok(Operator::Multiply),
-            "**=" => Ok(Operator::Exponent),
-            _ => Err(AssignmentError::InvalidOperator(data)),
-        }
-    }
-}
-
-impl Display for Operator {
-    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
-        match *self {
-            Operator::Add => write!(f, "+="),
-            Operator::Subtract => write!(f, "-="),
-            Operator::Divide => write!(f, "/="),
-            Operator::IntegerDivide => write!(f, "//="),
-            Operator::Multiply => write!(f, "*="),
-            Operator::Exponent => write!(f, "**="),
-            Operator::Equal => write!(f, "="),
-        }
-    }
-}
diff --git a/src/lib/parser/assignments/splitter.rs b/src/lib/parser/assignments/splitter.rs
deleted file mode 100644
index 1dba4168..00000000
--- a/src/lib/parser/assignments/splitter.rs
+++ /dev/null
@@ -1,79 +0,0 @@
-/// Given an valid assignment expression, this will split it into `keys`,
-/// `operator`, `values`.
-pub(crate) fn split_assignment(statement: &str) -> (Option<&str>, Option<&str>, Option<&str>) {
-    let statement = statement.trim();
-    if statement.is_empty() {
-        return (None, None, None);
-    }
-
-    let mut read = 0;
-    let mut bytes = statement.bytes();
-    let mut start = 0;
-
-    while let Some(byte) = bytes.next() {
-        if b'=' == byte {
-            if statement.as_bytes().get(read + 1).is_none() {
-                return (Some(&statement[..read].trim()), Some("="), None);
-            }
-            start = read;
-            read += 1;
-            break;
-        } else if is_operator(byte) {
-            start = read;
-            read += 1;
-            while let Some(byte) = bytes.next() {
-                read += 1;
-                if byte == b'=' {
-                    break;
-                }
-            }
-            break;
-        }
-        read += 1;
-    }
-
-    if statement.len() == read {
-        return (Some(statement.trim()), None, None);
-    }
-
-    let keys = statement[..start].trim_right();
-
-    let operator = &statement[start..read];
-    if read == statement.len() {
-        return (Some(keys), Some(operator), None);
-    }
-
-    let values = &statement[read..];
-    (Some(keys), Some(operator), Some(values.trim()))
-}
-
-fn is_operator(byte: u8) -> bool { byte == b'+' || byte == b'-' || byte == b'*' || byte == b'/' }
-
-#[cfg(test)]
-mod tests {
-    use super::*;
-
-    #[test]
-    fn assignment_splitting() {
-        assert_eq!(split_assignment(""), (None, None, None));
-        assert_eq!(split_assignment("abc"), (Some("abc"), None, None));
-        assert_eq!(
-            split_assignment("abc+=def"),
-            (Some("abc"), Some("+="), Some("def"))
-        );
-        assert_eq!(split_assignment("abc ="), (Some("abc"), Some("="), None));
-        assert_eq!(split_assignment("abc =  "), (Some("abc"), Some("="), None));
-        assert_eq!(
-            split_assignment("abc = def"),
-            (Some("abc"), Some("="), Some("def"))
-        );
-        assert_eq!(
-            split_assignment("abc=def"),
-            (Some("abc"), Some("="), Some("def"))
-        );
-        assert_eq!(
-            split_assignment("def ghi += 124 523"),
-            (Some("def ghi"), Some("+="), Some("124 523"))
-        )
-    }
-}
diff --git a/src/lib/parser/mod.rs b/src/lib/parser/mod.rs
index 9dcd0efe..c28a4b18 100644
--- a/src/lib/parser/mod.rs
+++ b/src/lib/parser/mod.rs
@@ -5,7 +5,7 @@ mod quotes;
 pub(crate) mod shell_expand;
 mod statement;
 
-pub use self::{assignments::Primitive, quotes::Terminator};
+pub use self::quotes::Terminator;
 pub(crate) use self::{
     loops::ForExpression, shell_expand::{expand_string, Expander, Select},
     statement::{parse_and_validate, StatementSplitter},
diff --git a/src/lib/parser/statement/functions.rs b/src/lib/parser/statement/functions.rs
index a78f58f7..c12421b1 100644
--- a/src/lib/parser/statement/functions.rs
+++ b/src/lib/parser/statement/functions.rs
@@ -1,6 +1,5 @@
-use super::{
-    super::assignments::{KeyBuf, KeyIterator, TypeError}, split_pattern,
-};
+use super::split_pattern;
+use lexers::assignments::{KeyBuf, KeyIterator, TypeError};
 
 /// The arguments expression given to a function declaration goes into here, which will be
 /// converted into a tuple consisting of a `KeyIterator` iterator, which will collect type
diff --git a/src/lib/parser/statement/parse.rs b/src/lib/parser/statement/parse.rs
index 1efead19..bab71b16 100644
--- a/src/lib/parser/statement/parse.rs
+++ b/src/lib/parser/statement/parse.rs
@@ -1,6 +1,6 @@
-use lexers::ArgumentSplitter;
+use lexers::{assignment_lexer, ArgumentSplitter};
 use super::{
-    super::{assignments::{split_assignment, Operator}, pipelines::{self, Pipeline}},
+    super::pipelines::{self, Pipeline},
     case, functions::{collect_arguments, parse_function},
 };
 use shell::flow_control::{Case, ElseIf, ExportAction, LocalAction, Statement};
@@ -36,7 +36,7 @@ pub(crate) fn parse(code: &str) -> Statement {
         }
         _ if cmd.starts_with("let ") => {
             // Split the let expression and ensure that the statement is valid.
-            let (keys, op, vals) = split_assignment(cmd[4..].trim_left());
+            let (keys, op, vals) = assignment_lexer(cmd[4..].trim_left());
             let (keys, op, values) = match vals {
                 Some(vals) => {
                     // If the values exist, then the keys and operator also exists.
@@ -52,24 +52,14 @@ pub(crate) fn parse(code: &str) -> Statement {
                 }
             };
 
-            // After also ensuring the the operator is a valid operator, create the let
-            // statement.
-            match Operator::parse(op) {
-                Ok(operator) => {
-                    return Statement::Let(LocalAction::Assign(keys, operator, values));
-                }
-                Err(why) => {
-                    eprintln!("ion: assignment error: {}", why);
-                    return Statement::Default;
-                }
-            }
+            return Statement::Let(LocalAction::Assign(keys, op, values));
         }
         "export" => {
             return Statement::Export(ExportAction::List);
         }
         _ if cmd.starts_with("export ") => {
             // Split the let expression and ensure that the statement is valid.
-            let (keys, op, vals) = split_assignment(cmd[7..].trim_left());
+            let (keys, op, vals) = assignment_lexer(cmd[7..].trim_left());
             let (keys, op, values) = match vals {
                 Some(vals) => {
                     // If the values exist, then the keys and operator also exists.
@@ -87,17 +77,7 @@ pub(crate) fn parse(code: &str) -> Statement {
                 }
             };
 
-            // After also ensuring the the operator is a valid operator, create the let
-            // statement.
-            match Operator::parse(op) {
-                Ok(operator) => {
-                    return Statement::Export(ExportAction::Assign(keys, operator, values));
-                }
-                Err(why) => {
-                    eprintln!("ion: assignment error: {}", why);
-                    return Statement::Default;
-                }
-            }
+            return Statement::Export(ExportAction::Assign(keys, op, values));
         }
         _ if cmd.starts_with("if ") => {
             return collect(cmd[3..].trim_left(), |pipeline| Statement::If {
diff --git a/src/lib/shell/assignments.rs b/src/lib/shell/assignments.rs
index 0fad4fe7..dd3ed026 100644
--- a/src/lib/shell/assignments.rs
+++ b/src/lib/shell/assignments.rs
@@ -2,6 +2,7 @@ use super::{
     flow_control::{ExportAction, LocalAction}, status::*, Shell,
 };
 use itoa;
+use lexers::assignments::{Operator, Primitive};
 use parser::assignments::*;
 use smallvec::SmallVec;
 use shell::{
@@ -457,6 +458,7 @@ fn math<'a, F: FnMut(&[u8])>(
             return Err(MathError::Unsupported);
         },
         Operator::Equal => writefn(value.as_bytes()),
+        _ => return Err(MathError::Unsupported)
     };
 
     Ok(())
diff --git a/src/lib/shell/flow_control.rs b/src/lib/shell/flow_control.rs
index 6b7402a2..2515fb55 100644
--- a/src/lib/shell/flow_control.rs
+++ b/src/lib/shell/flow_control.rs
@@ -3,6 +3,7 @@ use parser::{assignments::*, pipelines::Pipeline};
 use smallvec::SmallVec;
 use std::fmt::{self, Display, Formatter};
 use types::Identifier;
+use lexers::assignments::{KeyBuf, Operator, Primitive};
 
 #[derive(Debug, PartialEq, Clone)]
 pub(crate) struct ElseIf {
-- 
GitLab