tau-engine 1.13.1

A document tagging library
Documentation
use std::convert::TryFrom;
use std::fmt;
use std::iter::Iterator;
use std::iter::Peekable;

use aho_corasick::{AhoCorasick, AhoCorasickBuilder, AhoCorasickKind};
use regex::{Regex, RegexSet, RegexSetBuilder};
use serde_yaml::{Mapping, Value as Yaml};
use tracing::debug;

use crate::identifier::{Identifier, IdentifierParser, Pattern};
use crate::tokeniser::{BoolSym, DelSym, MatchSym, MiscSym, ModSym, Token, Tokeniser};

#[derive(Clone, Debug, PartialEq)]
pub enum MatchType {
    Contains(String),
    EndsWith(String),
    Exact(String),
    StartsWith(String),
}

impl MatchType {
    pub fn value(&self) -> &String {
        match self {
            Self::Contains(s) | Self::EndsWith(s) | Self::Exact(s) | Self::StartsWith(s) => s,
        }
    }
}

#[derive(Clone, Debug, PartialEq)]
pub enum Match {
    All,
    Of(u64),
}

#[derive(Clone, Debug)]
pub enum Search {
    AhoCorasick(Box<AhoCorasick>, Vec<MatchType>, bool),
    Any,
    Contains(String),
    EndsWith(String),
    Exact(String),
    Regex(Regex, bool),
    RegexSet(RegexSet, bool),
    StartsWith(String),
}
impl fmt::Display for Search {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            Self::AhoCorasick(_, t, i) => {
                write!(f, "{}aho_corasick({:?})", if *i { "i" } else { "" }, t)
            }
            Self::Any => write!(f, "any"),
            Self::Contains(s) => write!(f, "contains({})", s),
            Self::EndsWith(s) => write!(f, "ends_with({})", s),
            Self::Exact(s) => write!(f, "exact({})", s),
            Self::Regex(s, i) => write!(f, "{}regex({})", if *i { "i" } else { "" }, s),
            Self::RegexSet(s, i) => write!(
                f,
                "{}regex_set({:?})",
                if *i { "i" } else { "" },
                s.patterns()
            ),
            Self::StartsWith(s) => write!(f, "starts_with({})", s),
        }
    }
}
impl PartialEq for Search {
    fn eq(&self, other: &Search) -> bool {
        match (self, other) {
            (Search::Any, Search::Any) => true,
            (Search::AhoCorasick(_, m0, _), Search::AhoCorasick(_, m1, _)) => m0 == m1,
            (Search::Contains(s0), Search::Contains(s1)) => s0 == s1,
            (Search::EndsWith(s0), Search::EndsWith(s1)) => s0 == s1,
            (Search::Exact(s0), Search::Exact(s1)) => s0 == s1,
            (Search::Regex(r0, i0), Search::Regex(r1, i1)) => {
                r0.as_str() == r1.as_str() && i0 == i1
            }
            (Search::RegexSet(r0, i0), Search::RegexSet(r1, i1)) => {
                r0.patterns() == r1.patterns() && i0 == i1
            }
            (Search::StartsWith(s0), Search::StartsWith(s1)) => s0 == s1,
            (_, _) => false,
        }
    }
}

#[derive(Clone, Debug, PartialEq)]
pub enum Expression {
    BooleanGroup(BoolSym, Vec<Expression>),
    BooleanExpression(Box<Expression>, BoolSym, Box<Expression>),
    Boolean(bool),
    Cast(String, ModSym),
    Field(String),
    Float(f64),
    Identifier(String),
    Integer(i64),
    Match(Match, Box<Expression>),
    Matrix(Vec<String>, Vec<Vec<Option<Expression>>>),
    Negate(Box<Expression>),
    Nested(String, Box<Expression>),
    Null,
    Search(Search, String, bool),
}
impl fmt::Display for Expression {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            Self::BooleanGroup(o, g) => write!(
                f,
                "group({} {})",
                o,
                g.iter()
                    .map(|e| e.to_string())
                    .collect::<Vec<String>>()
                    .join(", ")
            ),
            Self::BooleanExpression(l, o, r) => write!(f, "expression({} {} {})", l, o, r),
            Self::Boolean(b) => write!(f, "bool({})", b),
            Self::Cast(s, t) => write!(f, "cast({}({}))", t, s),
            Self::Field(s) => write!(f, "field({})", s),
            Self::Float(n) => write!(f, "float({})", n),
            Self::Identifier(s) => write!(f, "identifier({})", s),
            Self::Integer(i) => write!(f, "int({})", i),
            Self::Match(Match::All, e) => {
                write!(f, "all({})", e)
            }
            Self::Match(Match::Of(i), e) => write!(f, "of({}, {})", e, i),
            Self::Matrix(c, m) => write!(
                f,
                "matrix([{}], [{}])",
                c.iter()
                    .map(|f| f.to_string())
                    .collect::<Vec<String>>()
                    .join(", "),
                m.iter()
                    .map(|c| format!(
                        "[{}]",
                        c.iter()
                            .map(|e| e.as_ref().map(|s| s.to_string()).unwrap_or("".to_owned()))
                            .collect::<Vec<String>>()
                            .join(", ")
                    ))
                    .collect::<Vec<String>>()
                    .join(", ")
            ),
            Self::Negate(e) => write!(f, "negate({})", e),
            Self::Nested(s, e) => write!(f, "nested({}, {})", s, e),
            Self::Null => write!(f, "null"),
            Self::Search(e, s, c) => write!(f, "search({}, {}, {})", s, e, c),
        }
    }
}
impl Expression {
    pub fn is_solvable(&self) -> bool {
        match self {
            Self::Boolean(_)
            | Self::Cast(_, _)
            | Self::Field(_)
            | Self::Float(_)
            | Self::Integer(_)
            | Self::Null => false,
            Self::BooleanGroup(_, _)
            | Self::BooleanExpression(_, _, _)
            | Self::Identifier(_)
            | Self::Match(_, _)
            | Self::Matrix(_, _)
            | Self::Negate(_)
            | Self::Nested(_, _)
            | Self::Search(_, _, _) => true,
        }
    }
}

// Pratt Parser used to parse the token stream
//
// Left-Denotation (LED) - how an operator consumes to the right with a left-context
// Null-Denotation (NUD) - how an operator consumes to the right with no left-context

pub(crate) fn parse(tokens: &[Token]) -> crate::Result<Expression> {
    let mut it = tokens.iter().peekable();
    let expression = parse_expr(&mut it, 0)?;
    if it.peek().is_some() {
        let remaining = it.collect::<Vec<&Token>>();
        return Err(crate::error::parse_invalid_expr(format!(
            "failed to parse the following tokens - '{:?}'",
            remaining
        )));
    }

    debug!("parsed '{:?}' into '{:?}'", tokens, expression);

    Ok(expression)
}

fn parse_expr<'a, I>(it: &mut Peekable<I>, right_binding_power: u8) -> crate::Result<Expression>
where
    I: Iterator<Item = &'a Token>,
{
    let mut left = parse_nud(it)?;
    while let Some(&next) = it.peek() {
        if right_binding_power >= next.binding_power() {
            break;
        }
        left = parse_led(left, it)?;
    }
    Ok(left)
}

fn parse_led<'a, I>(left: Expression, it: &mut Peekable<I>) -> crate::Result<Expression>
where
    I: Iterator<Item = &'a Token>,
{
    match it.next() {
        Some(t) => match *t {
            Token::Operator(ref s) => {
                let symbol = match *s {
                    BoolSym::And => BoolSym::And,
                    BoolSym::Equal => BoolSym::Equal,
                    BoolSym::GreaterThan => BoolSym::GreaterThan,
                    BoolSym::GreaterThanOrEqual => BoolSym::GreaterThanOrEqual,
                    BoolSym::LessThan => BoolSym::LessThan,
                    BoolSym::LessThanOrEqual => BoolSym::LessThanOrEqual,
                    BoolSym::Or => BoolSym::Or,
                };
                let right = parse_expr(it, t.binding_power())?;
                // Handle special limited cases
                match symbol {
                    BoolSym::Equal => {
                        match left {
                            Expression::Boolean(_)
                            | Expression::Cast(_, _)
                            | Expression::Float(_)
                            | Expression::Integer(_) => {}
                            _ => {
                                return Err(crate::error::parse_led_preceding(format!(
                                    "encountered - '{:?}'",
                                    t
                                )));
                            }
                        }
                        match right {
                            Expression::Boolean(_)
                            | Expression::Cast(_, _)
                            | Expression::Float(_)
                            | Expression::Integer(_) => {}
                            _ => {
                                return Err(crate::error::parse_led_following(format!(
                                    "encountered - '{:?}'",
                                    t
                                )));
                            }
                        }
                        // Type enforcement
                        match (&left, &right) {
                            (
                                Expression::Cast(_, ModSym::Flt),
                                Expression::Cast(_, ModSym::Flt),
                            ) => {}
                            (
                                Expression::Cast(_, ModSym::Int),
                                Expression::Cast(_, ModSym::Int),
                            ) => {}
                            (
                                Expression::Cast(_, ModSym::Str),
                                Expression::Cast(_, ModSym::Str),
                            ) => {}
                            (Expression::Cast(_, ModSym::Flt), Expression::Float(_)) => {}
                            (Expression::Float(_), Expression::Cast(_, ModSym::Flt)) => {}
                            (Expression::Cast(_, ModSym::Int), Expression::Integer(_)) => {}
                            (Expression::Integer(_), Expression::Cast(_, ModSym::Int)) => {}
                            (_, _) => {
                                return Err(crate::error::parse_invalid_expr(format!(
                                    "encountered - '{:?}'",
                                    t
                                )));
                            }
                        }
                    }
                    BoolSym::GreaterThan
                    | BoolSym::GreaterThanOrEqual
                    | BoolSym::LessThan
                    | BoolSym::LessThanOrEqual => {
                        match left {
                            Expression::Cast(_, _)
                            | Expression::Float(_)
                            | Expression::Integer(_) => {}
                            _ => {
                                return Err(crate::error::parse_led_preceding(format!(
                                    "encountered - '{:?}'",
                                    t
                                )));
                            }
                        }
                        match right {
                            Expression::Cast(_, _)
                            | Expression::Float(_)
                            | Expression::Integer(_) => {}
                            _ => {
                                return Err(crate::error::parse_led_following(format!(
                                    "encountered - '{:?}'",
                                    t
                                )));
                            }
                        }
                        // Type enforcement
                        match (&left, &right) {
                            (
                                Expression::Cast(_, ModSym::Flt),
                                Expression::Cast(_, ModSym::Flt),
                            ) => {}
                            (
                                Expression::Cast(_, ModSym::Int),
                                Expression::Cast(_, ModSym::Int),
                            ) => {}
                            (Expression::Cast(_, ModSym::Flt), Expression::Float(_)) => {}
                            (Expression::Float(_), Expression::Cast(_, ModSym::Flt)) => {}
                            (Expression::Cast(_, ModSym::Int), Expression::Integer(_)) => {}
                            (Expression::Integer(_), Expression::Cast(_, ModSym::Int)) => {}
                            (_, _) => {
                                return Err(crate::error::parse_invalid_expr(format!(
                                    "encountered - '{:?}'",
                                    t
                                )));
                            }
                        }
                    }
                    _ => {}
                }
                Ok(Expression::BooleanExpression(
                    Box::new(left),
                    symbol,
                    Box::new(right),
                ))
            }
            Token::Delimiter(_)
            | Token::Float(_)
            | Token::Identifier(_)
            | Token::Integer(_)
            | Token::Miscellaneous(_)
            | Token::Modifier(_)
            | Token::Match(_) => Err(crate::error::parse_invalid_token(format!(
                "LED encountered - '{:?}'",
                t
            ))),
        },
        None => Err(crate::error::parse_invalid_token("LED expected token")),
    }
}

fn parse_nud<'a, I>(it: &mut Peekable<I>) -> crate::Result<Expression>
where
    I: Iterator<Item = &'a Token>,
{
    match it.next() {
        Some(t) => {
            match *t {
                Token::Delimiter(ref s) => match *s {
                    DelSym::LeftParenthesis => {
                        // Consume up to matching right parenthesis and parse that, we also discard
                        // the matching right parenthesis
                        let mut tokens: Vec<Token> = vec![];
                        let mut depth = 1;
                        for t in it.by_ref() {
                            if t == &Token::Delimiter(DelSym::LeftParenthesis) {
                                depth += 1;
                            } else if t == &Token::Delimiter(DelSym::RightParenthesis) {
                                depth -= 1;
                                if depth == 0 {
                                    break;
                                }
                            }
                            tokens.push(t.clone());
                        }
                        parse(&tokens)
                    }
                    DelSym::Comma | DelSym::RightParenthesis => Err(
                        crate::error::parse_invalid_token(format!("NUD encountered - '{:?}'", t)),
                    ),
                },
                Token::Float(ref n) => Ok(Expression::Float(*n)),
                Token::Identifier(ref n) => Ok(Expression::Identifier(n.to_string())),
                Token::Integer(ref n) => Ok(Expression::Integer(*n)),
                Token::Miscellaneous(ref m) => match *m {
                    MiscSym::Not => {
                        let right = parse_expr(it, t.binding_power())?;
                        match right {
                            Expression::BooleanGroup(_, _)
                            | Expression::BooleanExpression(_, _, _)
                            | Expression::Boolean(_)
                            | Expression::Identifier(_)
                            | Expression::Match(_, _)
                            | Expression::Negate(_)
                            | Expression::Nested(_, _)
                            | Expression::Search(_, _, _) => {}
                            _ => {
                                return Err(crate::error::parse_invalid_token(
                                    "NUD expected a negatable expression",
                                ))
                            }
                        }
                        Ok(Expression::Negate(Box::new(right)))
                    }
                },
                Token::Modifier(ref m) => match *m {
                    ModSym::Flt => {
                        // We expect Flt(column_identifier)
                        if let Some(t) = it.next() {
                            match *t {
                                Token::Delimiter(DelSym::LeftParenthesis) => {}
                                _ => {
                                    return Err(crate::error::parse_invalid_token(format!(
                                        "NUD expected left parenthesis - '{:?}'",
                                        t
                                    )));
                                }
                            }
                        } else {
                            return Err(crate::error::parse_invalid_token(
                                "NUD expected left parenthesis",
                            ));
                        }
                        let token = match it.next() {
                            Some(t) => t,
                            None => {
                                return Err(crate::error::parse_invalid_token(
                                    "NUD expected column identifier",
                                ));
                            }
                        };
                        if let Some(t) = it.next() {
                            match *t {
                                Token::Delimiter(DelSym::RightParenthesis) => {}
                                _ => {
                                    return Err(crate::error::parse_invalid_token(format!(
                                        "NUD expected right parenthesis - '{:?}'",
                                        t
                                    )));
                                }
                            }
                        } else {
                            return Err(crate::error::parse_invalid_token(
                                "NUD expected right parenthesis",
                            ));
                        }
                        match *token {
                            Token::Identifier(ref s) => {
                                Ok(Expression::Cast(s.to_string(), ModSym::Flt))
                            }
                            _ => Err(crate::error::parse_invalid_token(
                                "NUD expected column identifier",
                            )),
                        }
                    }
                    ModSym::Int => {
                        // We expect Int(column_identifier)
                        if let Some(t) = it.next() {
                            match *t {
                                Token::Delimiter(DelSym::LeftParenthesis) => {}
                                _ => {
                                    return Err(crate::error::parse_invalid_token(format!(
                                        "NUD expected left parenthesis - '{:?}'",
                                        t
                                    )));
                                }
                            }
                        } else {
                            return Err(crate::error::parse_invalid_token(
                                "NUD expected left parenthesis",
                            ));
                        }
                        let token = match it.next() {
                            Some(t) => t,
                            None => {
                                return Err(crate::error::parse_invalid_token(
                                    "NUD expected column identifier",
                                ));
                            }
                        };
                        if let Some(t) = it.next() {
                            match *t {
                                Token::Delimiter(DelSym::RightParenthesis) => {}
                                _ => {
                                    return Err(crate::error::parse_invalid_token(format!(
                                        "NUD expected right parenthesis - '{:?}'",
                                        t
                                    )));
                                }
                            }
                        } else {
                            return Err(crate::error::parse_invalid_token(
                                "NUD expected right parenthesis",
                            ));
                        }
                        match *token {
                            Token::Identifier(ref s) => {
                                Ok(Expression::Cast(s.to_string(), ModSym::Int))
                            }
                            _ => Err(crate::error::parse_invalid_token(
                                "NUD expected column identifier",
                            )),
                        }
                    }
                    ModSym::Not => {
                        // We expect Int(column_identifier)
                        if let Some(t) = it.next() {
                            match *t {
                                Token::Delimiter(DelSym::LeftParenthesis) => {}
                                _ => {
                                    return Err(crate::error::parse_invalid_token(format!(
                                        "NUD expected left parenthesis - '{:?}'",
                                        t
                                    )));
                                }
                            }
                        } else {
                            return Err(crate::error::parse_invalid_token(
                                "NUD expected left parenthesis",
                            ));
                        }
                        let token = match it.next() {
                            Some(t) => t,
                            None => {
                                return Err(crate::error::parse_invalid_token(
                                    "NUD expected column identifier",
                                ));
                            }
                        };
                        if let Some(t) = it.next() {
                            match *t {
                                Token::Delimiter(DelSym::RightParenthesis) => {}
                                _ => {
                                    return Err(crate::error::parse_invalid_token(format!(
                                        "NUD expected right parenthesis - '{:?}'",
                                        t
                                    )));
                                }
                            }
                        } else {
                            return Err(crate::error::parse_invalid_token(
                                "NUD expected right parenthesis",
                            ));
                        }
                        match *token {
                            Token::Identifier(ref s) => {
                                Ok(Expression::Cast(s.to_string(), ModSym::Not))
                            }
                            _ => Err(crate::error::parse_invalid_token(
                                "NUD expected column identifier",
                            )),
                        }
                    }
                    ModSym::Str => {
                        // We expect string(column_identifier)
                        if let Some(t) = it.next() {
                            match *t {
                                Token::Delimiter(DelSym::LeftParenthesis) => {}
                                _ => {
                                    return Err(crate::error::parse_invalid_token(format!(
                                        "NUD expected left parenthesis - '{:?}'",
                                        t
                                    )));
                                }
                            }
                        } else {
                            return Err(crate::error::parse_invalid_token(
                                "NUD expected left parenthesis",
                            ));
                        }
                        let token = match it.next() {
                            Some(t) => t,
                            None => {
                                return Err(crate::error::parse_invalid_token(
                                    "NUD expected column identifier",
                                ));
                            }
                        };
                        if let Some(t) = it.next() {
                            match *t {
                                Token::Delimiter(DelSym::RightParenthesis) => {}
                                _ => {
                                    return Err(crate::error::parse_invalid_token(format!(
                                        "NUD expected right parenthesis - '{:?}'",
                                        t
                                    )));
                                }
                            }
                        } else {
                            return Err(crate::error::parse_invalid_token(
                                "NUD expected right parenthesis",
                            ));
                        }
                        match *token {
                            Token::Identifier(ref s) => {
                                Ok(Expression::Cast(s.to_string(), ModSym::Str))
                            }
                            _ => Err(crate::error::parse_invalid_token(
                                "NUD expected column identifier",
                            )),
                        }
                    }
                },
                Token::Match(ref m) => match *m {
                    MatchSym::All => {
                        // We expect all(column_identifier)
                        if let Some(t) = it.next() {
                            match *t {
                                Token::Delimiter(DelSym::LeftParenthesis) => {}
                                _ => {
                                    return Err(crate::error::parse_invalid_token(format!(
                                        "NUD expected left parenthesis - '{:?}'",
                                        t
                                    )));
                                }
                            }
                        } else {
                            return Err(crate::error::parse_invalid_token(
                                "NUD expected left parenthesis",
                            ));
                        }
                        let token = match it.next() {
                            Some(t) => t,
                            None => {
                                return Err(crate::error::parse_invalid_token(
                                    "NUD expected column identifier",
                                ));
                            }
                        };
                        if let Some(t) = it.next() {
                            match *t {
                                Token::Delimiter(DelSym::RightParenthesis) => {}
                                _ => {
                                    return Err(crate::error::parse_invalid_token(format!(
                                        "NUD expected right parenthesis - '{:?}'",
                                        t
                                    )));
                                }
                            }
                        } else {
                            return Err(crate::error::parse_invalid_token(
                                "NUD expected right parenthesis",
                            ));
                        }
                        match *token {
                            Token::Identifier(ref s) => Ok(Expression::Match(
                                Match::All,
                                Box::new(Expression::Identifier(s.to_string())),
                            )),
                            _ => Err(crate::error::parse_invalid_token(
                                "NUD expected column identifier",
                            )),
                        }
                    }
                    MatchSym::Of => {
                        // We expect of(column_identifier, 1)
                        if let Some(t) = it.next() {
                            match *t {
                                Token::Delimiter(DelSym::LeftParenthesis) => {}
                                _ => {
                                    return Err(crate::error::parse_invalid_token(format!(
                                        "NUD expected left parenthesis - '{:?}'",
                                        t
                                    )));
                                }
                            }
                        } else {
                            return Err(crate::error::parse_invalid_token(
                                "NUD expected left parenthesis",
                            ));
                        }
                        let token = match it.next() {
                            Some(t) => t,
                            None => {
                                return Err(crate::error::parse_invalid_token(
                                    "NUD expected column identifier",
                                ));
                            }
                        };
                        if let Some(t) = it.next() {
                            match *t {
                                Token::Delimiter(DelSym::Comma) => {}
                                _ => {
                                    return Err(crate::error::parse_invalid_token(format!(
                                        "NUD expected comma - '{:?}'",
                                        t
                                    )));
                                }
                            }
                        } else {
                            return Err(crate::error::parse_invalid_token("NUD expected comma"));
                        }
                        let count = match it.next() {
                            Some(t) => match t {
                                Token::Integer(c) => match u64::try_from(*c) {
                                    Ok(u) => u,
                                    Err(_) => {
                                        return Err(crate::error::parse_invalid_token(format!(
                                            "NUD expected positive integer - '{:?}'",
                                            t
                                        )));
                                    }
                                },
                                _ => {
                                    return Err(crate::error::parse_invalid_token(format!(
                                        "NUD expected integer - '{:?}'",
                                        t
                                    )));
                                }
                            },
                            None => {
                                return Err(crate::error::parse_invalid_token(
                                    "NUD expected integer",
                                ));
                            }
                        };
                        if let Some(t) = it.next() {
                            match *t {
                                Token::Delimiter(DelSym::RightParenthesis) => {}
                                _ => {
                                    return Err(crate::error::parse_invalid_token(format!(
                                        "NUD expected right parenthesis - '{:?}'",
                                        t
                                    )));
                                }
                            }
                        } else {
                            return Err(crate::error::parse_invalid_token(
                                "NUD expected right parenthesis",
                            ));
                        }
                        match *token {
                            Token::Identifier(ref s) => Ok(Expression::Match(
                                Match::Of(count),
                                Box::new(Expression::Identifier(s.to_string())),
                            )),
                            _ => Err(crate::error::parse_invalid_token(
                                "NUD expected column identifier",
                            )),
                        }
                    }
                },
                Token::Operator(_) => Err(crate::error::parse_invalid_token(format!(
                    "NUD encountered - '{:?}'",
                    t
                ))),
            }
        }
        None => Err(crate::error::parse_invalid_token("NUD expected token")),
    }
}

pub fn parse_identifier(yaml: &Yaml) -> crate::Result<Expression> {
    match yaml {
        Yaml::Mapping(m) => parse_mapping(m),
        Yaml::Sequence(s) => {
            // We allow a sequence of maps only on the root
            let mut it = s.iter();
            match it.next() {
                Some(v) => match &v {
                    Yaml::Mapping(m) => {
                        let mut expressions = vec![parse_mapping(m)?];
                        for value in it {
                            // NOTE: A sequence can only be one type
                            if let Yaml::Mapping(mapping) = value {
                                expressions.push(parse_mapping(mapping)?);
                            } else {
                                return Err(crate::error::parse_invalid_ident(format!(
                                    "expected a sequence of mappings, encountered - {:?}",
                                    yaml
                                )));
                            }
                        }
                        Ok(Expression::BooleanGroup(BoolSym::Or, expressions))
                    }
                    _ => Err(crate::error::parse_invalid_ident(format!(
                        "expected a sequence of mappings, encountered - {:?}",
                        yaml
                    ))),
                },
                None => Err(crate::error::parse_invalid_ident(format!(
                    "expected a non empty sequence of mappings, encountered - {:?}",
                    yaml
                ))),
            }
        }
        _ => Err(crate::error::parse_invalid_ident(format!(
            "expected mapping or sequence, encountered - {:?}",
            yaml
        ))),
    }
}

// TODO: Extract common code and try to make this function a little bit more readable
fn parse_mapping(mapping: &Mapping) -> crate::Result<Expression> {
    let mut expressions = vec![];
    for (k, v) in mapping {
        let mut misc: Option<ModSym> = None;
        let (e, f) = match k {
            Yaml::String(s) => {
                // NOTE: Tokenise splits on whitespace, but this is undesired for keys, merge them
                // back together
                let mut identifier = vec![];
                let mut tokens = vec![];
                for token in s.tokenise()? {
                    match token {
                        Token::Identifier(s) => identifier.push(s),
                        _ => {
                            if !identifier.is_empty() {
                                tokens.push(Token::Identifier(identifier.join(" ")));
                                identifier.clear();
                            }
                            tokens.push(token);
                        }
                    }
                }
                if !identifier.is_empty() {
                    tokens.push(Token::Identifier(identifier.join(" ")));
                    identifier.clear();
                }
                let expr = parse(&tokens)?;
                let (e, s) = match expr {
                    Expression::Cast(f, s) => {
                        misc = Some(s.clone());
                        match s {
                            ModSym::Flt => (Expression::Cast(f.clone(), s), f),
                            ModSym::Int => (Expression::Cast(f.clone(), s), f),
                            ModSym::Not => (Expression::Field(f.clone()), f),
                            ModSym::Str => (Expression::Cast(f.clone(), s), f),
                        }
                    }
                    Expression::Identifier(s) => (Expression::Field(s.clone()), s),
                    Expression::Match(m, i) => {
                        if let Yaml::Sequence(_) = v {
                            match *i {
                                Expression::Identifier(s) => (
                                    Expression::Match(m, Box::new(Expression::Field(s.clone()))),
                                    s,
                                ),
                                _ => {
                                    return Err(crate::error::parse_invalid_ident(format!(
                                        "match condition mut contain a field, encountered - {:?}",
                                        k
                                    )))
                                }
                            }
                        } else {
                            return Err(crate::error::parse_invalid_ident(format!(
                                "match condition is only valid for sequences, encountered - {:?}",
                                k
                            )));
                        }
                    }
                    _ => {
                        return Err(crate::error::parse_invalid_ident(format!(
                        "mapping key must be a string or valid match condition, encountered - {:?}",
                        k
                    )))
                    }
                };
                (e, s)
            }
            _ => {
                return Err(crate::error::parse_invalid_ident(format!(
                    "mapping key must be a string, encountered - {:?}",
                    k
                )))
            }
        };
        let expression = match v {
            Yaml::Bool(b) => {
                if let Some(ModSym::Int) = misc {
                    Expression::BooleanExpression(
                        Box::new(e.clone()),
                        BoolSym::Equal,
                        Box::new(Expression::Integer(if *b { 1 } else { 0 })),
                    )
                } else if let Some(ModSym::Str) = misc {
                    Expression::Search(Search::Exact(b.to_string()), f.to_owned(), true)
                } else {
                    Expression::BooleanExpression(
                        Box::new(e.clone()),
                        BoolSym::Equal,
                        Box::new(Expression::Boolean(*b)),
                    )
                }
            }
            Yaml::Number(n) => {
                if let Some(i) = n.as_i64() {
                    if let Some(ModSym::Str) = misc {
                        Expression::Search(Search::Exact(i.to_string()), f.to_owned(), true)
                    } else {
                        Expression::BooleanExpression(
                            Box::new(e.clone()),
                            BoolSym::Equal,
                            Box::new(Expression::Integer(i)),
                        )
                    }
                } else if let Some(i) = n.as_f64() {
                    if let Some(ModSym::Int) = misc {
                        return Err(crate::error::parse_invalid_ident(format!(
                            "float cannot be cast into an integer, encountered - {:?}",
                            k
                        )));
                    } else if let Some(ModSym::Str) = misc {
                        Expression::Search(Search::Exact(i.to_string()), f.to_owned(), true)
                    } else {
                        Expression::BooleanExpression(
                            Box::new(e.clone()),
                            BoolSym::Equal,
                            Box::new(Expression::Float(i)),
                        )
                    }
                } else {
                    return Err(crate::error::parse_invalid_ident(format!(
                        "number must be a signed integer or float, encountered - {:?}",
                        k
                    )));
                }
            }
            Yaml::Null => Expression::BooleanExpression(
                Box::new(e.clone()),
                BoolSym::Equal,
                Box::new(Expression::Null),
            ),
            Yaml::String(ref s) => {
                let identifier = s.to_owned().into_identifier()?;
                let mut cast = false;
                if let Some(ref m) = misc {
                    if let ModSym::Str = m {
                        cast = true;
                    }
                    match &identifier.pattern {
                        Pattern::Any
                        | Pattern::Regex(_)
                        | Pattern::Contains(_)
                        | Pattern::EndsWith(_)
                        | Pattern::Exact(_)
                        | Pattern::StartsWith(_) => {
                            if let ModSym::Int = m {
                                return Err(crate::error::parse_invalid_ident(format!(
                                    "cannot cast string to integer, encountered - {:?}",
                                    k
                                )));
                            }
                        }
                        Pattern::Equal(_)
                        | Pattern::GreaterThan(_)
                        | Pattern::GreaterThanOrEqual(_)
                        | Pattern::LessThan(_)
                        | Pattern::LessThanOrEqual(_)
                        | Pattern::FEqual(_)
                        | Pattern::FGreaterThan(_)
                        | Pattern::FGreaterThanOrEqual(_)
                        | Pattern::FLessThan(_)
                        | Pattern::FLessThanOrEqual(_) => {
                            if let ModSym::Str = m {
                                return Err(crate::error::parse_invalid_ident(format!(
                                    "cannot cast integer to string, encountered - {:?}",
                                    k
                                )));
                            }
                        }
                    }
                }
                match identifier.pattern {
                    Pattern::Equal(i) => Expression::BooleanExpression(
                        Box::new(e.clone()),
                        BoolSym::Equal,
                        Box::new(Expression::Integer(i)),
                    ),
                    Pattern::GreaterThan(i) => Expression::BooleanExpression(
                        Box::new(e.clone()),
                        BoolSym::GreaterThan,
                        Box::new(Expression::Integer(i)),
                    ),
                    Pattern::GreaterThanOrEqual(i) => Expression::BooleanExpression(
                        Box::new(e.clone()),
                        BoolSym::GreaterThanOrEqual,
                        Box::new(Expression::Integer(i)),
                    ),
                    Pattern::LessThan(i) => Expression::BooleanExpression(
                        Box::new(e.clone()),
                        BoolSym::LessThan,
                        Box::new(Expression::Integer(i)),
                    ),
                    Pattern::LessThanOrEqual(i) => Expression::BooleanExpression(
                        Box::new(e.clone()),
                        BoolSym::LessThanOrEqual,
                        Box::new(Expression::Integer(i)),
                    ),
                    Pattern::FEqual(i) => Expression::BooleanExpression(
                        Box::new(e.clone()),
                        BoolSym::Equal,
                        Box::new(Expression::Float(i)),
                    ),
                    Pattern::FGreaterThan(i) => Expression::BooleanExpression(
                        Box::new(e.clone()),
                        BoolSym::GreaterThan,
                        Box::new(Expression::Float(i)),
                    ),
                    Pattern::FGreaterThanOrEqual(i) => Expression::BooleanExpression(
                        Box::new(e.clone()),
                        BoolSym::GreaterThanOrEqual,
                        Box::new(Expression::Float(i)),
                    ),
                    Pattern::FLessThan(i) => Expression::BooleanExpression(
                        Box::new(e.clone()),
                        BoolSym::LessThan,
                        Box::new(Expression::Float(i)),
                    ),
                    Pattern::FLessThanOrEqual(i) => Expression::BooleanExpression(
                        Box::new(e.clone()),
                        BoolSym::LessThanOrEqual,
                        Box::new(Expression::Float(i)),
                    ),
                    Pattern::Any => Expression::Search(Search::Any, f.to_owned(), cast),
                    Pattern::Regex(c) => Expression::Search(
                        Search::Regex(c, identifier.ignore_case),
                        f.to_owned(),
                        cast,
                    ),
                    Pattern::Contains(c) => Expression::Search(
                        if identifier.ignore_case {
                            Search::AhoCorasick(
                                Box::new(
                                    AhoCorasickBuilder::new()
                                        .ascii_case_insensitive(true)
                                        .kind(Some(AhoCorasickKind::DFA))
                                        .build(vec![c.clone()])
                                        .expect("failed to build dfa"),
                                ),
                                vec![MatchType::Contains(c)],
                                true,
                            )
                        } else {
                            Search::Contains(c)
                        },
                        f.to_owned(),
                        cast,
                    ),
                    Pattern::EndsWith(c) => Expression::Search(
                        if identifier.ignore_case {
                            Search::AhoCorasick(
                                Box::new(
                                    AhoCorasickBuilder::new()
                                        .ascii_case_insensitive(true)
                                        .kind(Some(AhoCorasickKind::DFA))
                                        .build(vec![c.clone()])
                                        .expect("failed to build dfa"),
                                ),
                                vec![MatchType::EndsWith(c)],
                                true,
                            )
                        } else {
                            Search::EndsWith(c)
                        },
                        f.to_owned(),
                        cast,
                    ),
                    Pattern::Exact(c) => Expression::Search(
                        if !c.is_empty() && identifier.ignore_case {
                            Search::AhoCorasick(
                                Box::new(
                                    AhoCorasickBuilder::new()
                                        .ascii_case_insensitive(true)
                                        .kind(Some(AhoCorasickKind::DFA))
                                        .build(vec![c.clone()])
                                        .expect("failed to build dfa"),
                                ),
                                vec![MatchType::Exact(c)],
                                true,
                            )
                        } else {
                            Search::Exact(c)
                        },
                        f.to_owned(),
                        cast,
                    ),
                    Pattern::StartsWith(c) => Expression::Search(
                        if identifier.ignore_case {
                            Search::AhoCorasick(
                                Box::new(
                                    AhoCorasickBuilder::new()
                                        .ascii_case_insensitive(true)
                                        .kind(Some(AhoCorasickKind::DFA))
                                        .build(vec![c.clone()])
                                        .expect("failed to build dfa"),
                                ),
                                vec![MatchType::StartsWith(c)],
                                true,
                            )
                        } else {
                            Search::StartsWith(c)
                        },
                        f.to_owned(),
                        cast,
                    ),
                }
            }
            Yaml::Mapping(ref m) => {
                if misc.is_some() {
                    return Err(crate::error::parse_invalid_ident(format!(
                        "nested mappings are not supported when casting or negating a field, encountered - {:?}",
                        k
                    )));
                }
                Expression::Nested(f.to_owned(), Box::new(parse_mapping(m)?))
            }
            Yaml::Sequence(ref s) => {
                // TODO: This block could probably be cleaned...
                // Now we need to be as fast as possible it turns out that builtin strings functions are
                // fastest when we only need to check a single condition, when we need to check more that
                // one AhoCorasick becomes the quicker, even though AC should be as fast as starts_with and
                // contains... We also want to order in terms of quickest on average:
                //
                //  1. ExactMatch
                //  2. StartsWith
                //  3. EndsWith
                //  4. Contains
                //  5. AhoCorasick
                //  6. Regex
                //
                //  And for the above use AhoCorasick when list is more than one for 2,3,4
                let mut exact: Vec<Identifier> = vec![];
                let mut starts_with: Vec<Identifier> = vec![];
                let mut ends_with: Vec<Identifier> = vec![];
                let mut contains: Vec<Identifier> = vec![];
                let mut regex: Vec<Identifier> = vec![];
                let mut rest: Vec<Expression> = vec![]; // NOTE: Don't care about speed of numbers atm

                let mut boolean = false;
                let mut cast = false;
                let mut mapping = false;
                let mut number = false;
                let mut string = false;

                let unmatched_e = if let Expression::Match(_, e) = &e {
                    *e.clone()
                } else {
                    e.clone()
                };

                for value in s {
                    let identifier = match value {
                        Yaml::Bool(b) => {
                            if let Some(ModSym::Int) = misc {
                                number = true;
                                rest.push(Expression::BooleanExpression(
                                    Box::new(unmatched_e.clone()),
                                    BoolSym::Equal,
                                    Box::new(Expression::Integer(if *b { 1 } else { 0 })),
                                ))
                            } else if let Some(ModSym::Str) = misc {
                                string = true;
                                exact.push(Identifier {
                                    ignore_case: false,
                                    pattern: Pattern::Exact(b.to_string()),
                                });
                            } else {
                                boolean = true;
                                rest.push(Expression::BooleanExpression(
                                    Box::new(e.clone()),
                                    BoolSym::Equal,
                                    Box::new(Expression::Boolean(*b)),
                                ))
                            }
                            continue;
                        }
                        Yaml::Null => {
                            rest.push(Expression::BooleanExpression(
                                Box::new(unmatched_e.clone()),
                                BoolSym::Equal,
                                Box::new(Expression::Null),
                            ));
                            continue;
                        }
                        Yaml::Number(n) => {
                            if let Some(i) = n.as_i64() {
                                if let Some(ModSym::Str) = misc {
                                    string = true;
                                    exact.push(Identifier {
                                        ignore_case: false,
                                        pattern: Pattern::Exact(i.to_string()),
                                    });
                                } else {
                                    number = true;
                                    rest.push(Expression::BooleanExpression(
                                        Box::new(e.clone()),
                                        BoolSym::Equal,
                                        Box::new(Expression::Integer(i)),
                                    ));
                                }
                                continue;
                            } else if let Some(i) = n.as_f64() {
                                if let Some(ModSym::Int) = misc {
                                    return Err(crate::error::parse_invalid_ident(format!(
                                        "float cannot be cast into an integer, encountered - {:?}",
                                        k
                                    )));
                                } else if let Some(ModSym::Str) = misc {
                                    string = true;
                                    exact.push(Identifier {
                                        ignore_case: false,
                                        pattern: Pattern::Exact(i.to_string()),
                                    });
                                } else {
                                    number = true;
                                    rest.push(Expression::BooleanExpression(
                                        Box::new(e.clone()),
                                        BoolSym::Equal,
                                        Box::new(Expression::Float(i)),
                                    ))
                                }
                                continue;
                            }
                            return Err(crate::error::parse_invalid_ident(format!(
                                "number must be a signed integer or float, encountered - {:?}",
                                k
                            )));
                        }
                        Yaml::String(s) => s.clone().into_identifier()?,

                        Yaml::Mapping(m) => {
                            if misc.is_some() {
                                return Err(crate::error::parse_invalid_ident(format!(
                                    "nested mappings are not supported when casting or negating a field, encountered - {:?}",
                                    k
                                )));
                            }
                            mapping = true;
                            // FIXME: We should be nesting at the end of the squence, currently we
                            // have to shake to remove this...
                            rest.push(Expression::Nested(
                                f.to_owned(),
                                Box::new(parse_mapping(m)?),
                            ));
                            continue;
                        }
                        _ => {
                            return Err(crate::error::parse_invalid_ident(format!(
                                "value must be a mapping or string, encountered - {:?}",
                                k
                            )))
                        }
                    };
                    if let Some(ref m) = misc {
                        if let ModSym::Str = m {
                            cast = true;
                        }
                        match &identifier.pattern {
                            Pattern::Any
                            | Pattern::Regex(_)
                            | Pattern::Contains(_)
                            | Pattern::EndsWith(_)
                            | Pattern::Exact(_)
                            | Pattern::StartsWith(_) => {
                                if let ModSym::Int = m {
                                    return Err(crate::error::parse_invalid_ident(format!(
                                        "cannot cast string to integer, encountered - {:?}",
                                        k
                                    )));
                                }
                            }
                            Pattern::Equal(_)
                            | Pattern::GreaterThan(_)
                            | Pattern::GreaterThanOrEqual(_)
                            | Pattern::LessThan(_)
                            | Pattern::LessThanOrEqual(_)
                            | Pattern::FEqual(_)
                            | Pattern::FGreaterThan(_)
                            | Pattern::FGreaterThanOrEqual(_)
                            | Pattern::FLessThan(_)
                            | Pattern::FLessThanOrEqual(_) => {
                                if let ModSym::Str = m {
                                    return Err(crate::error::parse_invalid_ident(format!(
                                        "cannot cast integer to string, encountered - {:?}",
                                        k
                                    )));
                                }
                            }
                        }
                    }
                    match identifier.pattern {
                        Pattern::Exact(_) => {
                            string = true;
                            exact.push(identifier)
                        }
                        Pattern::StartsWith(_) => {
                            string = true;
                            starts_with.push(identifier)
                        }
                        Pattern::EndsWith(_) => {
                            string = true;
                            ends_with.push(identifier)
                        }
                        Pattern::Contains(_) => {
                            string = true;
                            contains.push(identifier)
                        }
                        Pattern::Regex(_) => {
                            string = true;
                            regex.push(identifier)
                        }
                        Pattern::Any => {
                            string = true;
                            rest.push(Expression::Search(Search::Any, f.to_owned(), cast))
                        }
                        Pattern::Equal(i) => {
                            number = true;
                            rest.push(Expression::BooleanExpression(
                                Box::new(e.clone()),
                                BoolSym::Equal,
                                Box::new(Expression::Integer(i)),
                            ))
                        }
                        Pattern::GreaterThan(i) => {
                            number = true;
                            rest.push(Expression::BooleanExpression(
                                Box::new(e.clone()),
                                BoolSym::GreaterThan,
                                Box::new(Expression::Integer(i)),
                            ))
                        }
                        Pattern::GreaterThanOrEqual(i) => {
                            number = true;
                            rest.push(Expression::BooleanExpression(
                                Box::new(e.clone()),
                                BoolSym::GreaterThanOrEqual,
                                Box::new(Expression::Integer(i)),
                            ))
                        }
                        Pattern::LessThan(i) => {
                            number = true;
                            rest.push(Expression::BooleanExpression(
                                Box::new(e.clone()),
                                BoolSym::LessThan,
                                Box::new(Expression::Integer(i)),
                            ))
                        }
                        Pattern::LessThanOrEqual(i) => {
                            number = true;
                            rest.push(Expression::BooleanExpression(
                                Box::new(e.clone()),
                                BoolSym::LessThanOrEqual,
                                Box::new(Expression::Integer(i)),
                            ))
                        }
                        Pattern::FEqual(i) => {
                            number = true;
                            rest.push(Expression::BooleanExpression(
                                Box::new(e.clone()),
                                BoolSym::Equal,
                                Box::new(Expression::Float(i)),
                            ))
                        }
                        Pattern::FGreaterThan(i) => {
                            number = true;
                            rest.push(Expression::BooleanExpression(
                                Box::new(e.clone()),
                                BoolSym::GreaterThan,
                                Box::new(Expression::Float(i)),
                            ))
                        }
                        Pattern::FGreaterThanOrEqual(i) => {
                            number = true;
                            rest.push(Expression::BooleanExpression(
                                Box::new(e.clone()),
                                BoolSym::GreaterThanOrEqual,
                                Box::new(Expression::Float(i)),
                            ))
                        }
                        Pattern::FLessThan(i) => {
                            number = true;
                            rest.push(Expression::BooleanExpression(
                                Box::new(e.clone()),
                                BoolSym::LessThan,
                                Box::new(Expression::Float(i)),
                            ))
                        }
                        Pattern::FLessThanOrEqual(i) => {
                            number = true;
                            rest.push(Expression::BooleanExpression(
                                Box::new(e.clone()),
                                BoolSym::LessThanOrEqual,
                                Box::new(Expression::Float(i)),
                            ))
                        }
                    }
                }
                let mut multiple = false;
                let mut group: Vec<Expression> = vec![];
                let mut context: Vec<MatchType> = vec![];
                let mut needles: Vec<String> = vec![];
                let mut icontext: Vec<MatchType> = vec![];
                let mut ineedles: Vec<String> = vec![];
                let mut regex_set: Vec<Regex> = vec![];
                let mut iregex_set: Vec<Regex> = vec![];
                for i in starts_with.into_iter() {
                    if let Pattern::StartsWith(s) = i.pattern {
                        if i.ignore_case {
                            icontext.push(MatchType::StartsWith(s.clone()));
                            ineedles.push(s);
                        } else {
                            context.push(MatchType::StartsWith(s.clone()));
                            needles.push(s);
                        }
                    }
                }
                for i in contains.into_iter() {
                    if let Pattern::Contains(s) = i.pattern {
                        if i.ignore_case {
                            icontext.push(MatchType::Contains(s.clone()));
                            ineedles.push(s);
                        } else {
                            context.push(MatchType::Contains(s.clone()));
                            needles.push(s);
                        }
                    }
                }
                for i in ends_with.into_iter() {
                    if let Pattern::EndsWith(s) = i.pattern {
                        if i.ignore_case {
                            icontext.push(MatchType::EndsWith(s.clone()));
                            ineedles.push(s);
                        } else {
                            context.push(MatchType::EndsWith(s.clone()));
                            needles.push(s);
                        }
                    }
                }
                for i in exact.into_iter() {
                    if let Pattern::Exact(s) = i.pattern {
                        // NOTE: Do not allow empty string into the needles as it causes massive slow down,
                        // don't ask me why I have not looked into it!
                        if s.is_empty() {
                            group.push(Expression::Search(Search::Exact(s), f.to_owned(), cast));
                        } else if i.ignore_case {
                            icontext.push(MatchType::Exact(s.clone()));
                            ineedles.push(s);
                        } else {
                            context.push(MatchType::Exact(s.clone()));
                            needles.push(s);
                        }
                    }
                }
                for i in regex.into_iter() {
                    if let Pattern::Regex(r) = i.pattern {
                        if i.ignore_case {
                            iregex_set.push(r);
                        } else {
                            regex_set.push(r);
                        }
                    }
                }
                if !needles.is_empty() {
                    if needles.len() == 1 {
                        let s = match context.into_iter().next().expect("failed to get context") {
                            MatchType::Contains(c) => Search::Contains(c),
                            MatchType::EndsWith(c) => Search::EndsWith(c),
                            MatchType::Exact(c) => Search::Exact(c),
                            MatchType::StartsWith(c) => Search::StartsWith(c),
                        };
                        group.push(Expression::Search(s, f.to_owned(), cast));
                    } else {
                        multiple = true;
                        group.push(Expression::Search(
                            Search::AhoCorasick(
                                Box::new(
                                    AhoCorasickBuilder::new()
                                        .kind(Some(AhoCorasickKind::DFA))
                                        .build(needles)
                                        .expect("failed to build dfa"),
                                ),
                                context,
                                false,
                            ),
                            f.to_owned(),
                            cast,
                        ));
                    }
                }
                if !ineedles.is_empty() {
                    multiple = true;
                    group.push(Expression::Search(
                        Search::AhoCorasick(
                            Box::new(
                                AhoCorasickBuilder::new()
                                    .ascii_case_insensitive(true)
                                    .kind(Some(AhoCorasickKind::DFA))
                                    .build(ineedles)
                                    .expect("failed to build dfa"),
                            ),
                            icontext,
                            true,
                        ),
                        f.to_owned(),
                        cast,
                    ));
                }
                if !regex_set.is_empty() {
                    if regex_set.len() == 1 {
                        group.push(Expression::Search(
                            Search::Regex(
                                regex_set.into_iter().next().expect("failed to get regex"),
                                false,
                            ),
                            f.to_owned(),
                            cast,
                        ));
                    } else {
                        multiple = true;
                        group.push(Expression::Search(
                            Search::RegexSet(
                                RegexSetBuilder::new(
                                    regex_set
                                        .into_iter()
                                        .map(|r| r.as_str().to_string())
                                        .collect::<Vec<_>>(),
                                )
                                .build()
                                .expect("could not build regex set"),
                                false,
                            ),
                            f.to_owned(),
                            cast,
                        ));
                    }
                }
                if !iregex_set.is_empty() {
                    if iregex_set.len() == 1 {
                        group.push(Expression::Search(
                            Search::Regex(
                                iregex_set.into_iter().next().expect("failed to get regex"),
                                true,
                            ),
                            f.to_owned(),
                            cast,
                        ));
                    } else {
                        multiple = true;
                        group.push(Expression::Search(
                            Search::RegexSet(
                                RegexSetBuilder::new(
                                    iregex_set
                                        .into_iter()
                                        .map(|r| r.as_str().to_string())
                                        .collect::<Vec<_>>(),
                                )
                                .case_insensitive(true)
                                .build()
                                .expect("could not build regex set"),
                                true,
                            ),
                            f.to_owned(),
                            cast,
                        ));
                    }
                }
                group.extend(rest);
                if let Expression::Match(Match::All, _) | Expression::Match(Match::Of(_), _) = &e {
                    if boolean as i32 + mapping as i32 + number as i32 + string as i32 > 1 {
                        return Err(crate::error::parse_invalid_ident(
                            "when using sequence modifiers the all expressions must be of the same type",
                        ));
                    }
                }
                if let Some(misc) = &misc {
                    if let ModSym::Int = misc {
                        if boolean || mapping || string {
                            return Err(crate::error::parse_invalid_ident(
                                "when casting to int all expressions must be of type int",
                            ));
                        }
                    }
                    if let ModSym::Str = &misc {
                        if boolean || mapping || number {
                            return Err(crate::error::parse_invalid_ident(
                                "when casting to str all expressions must be of type str",
                            ));
                        }
                    }
                }
                if group.is_empty() {
                    return Err(crate::error::parse_invalid_ident("failed to parse mapping"));
                } else if !multiple && group.len() == 1 {
                    group.into_iter().next().expect("could not get expression")
                } else if let Expression::Match(m, _) = e {
                    if group.len() == 1 {
                        let group = group.into_iter().next().expect("could not get expression");
                        Expression::Match(m, Box::new(group))
                    } else {
                        Expression::Match(m, Box::new(Expression::BooleanGroup(BoolSym::Or, group)))
                    }
                } else {
                    Expression::BooleanGroup(BoolSym::Or, group)
                }
            }
            Yaml::Tagged(_) => {
                return Err(crate::error::parse_invalid_ident(
                    "!Tag syntax is not supported",
                ));
            }
        };
        if let Some(ModSym::Not) = misc {
            expressions.push(Expression::Negate(Box::new(expression)));
        } else {
            expressions.push(expression);
        }
    }
    if expressions.is_empty() {
        return Err(crate::error::parse_invalid_ident("failed to parse mapping"));
    } else if expressions.len() == 1 {
        return Ok(expressions.into_iter().next().expect("missing expression"));
    }
    Ok(Expression::BooleanGroup(BoolSym::And, expressions))
}

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

    use serde_yaml::Value as Yaml;

    #[test]
    fn parse_bool_group_match_search() {
        let identifier = r"all(foo): [bar, '*']";
        let yaml: Yaml = serde_yaml::from_str(identifier).unwrap();
        let e = super::parse_identifier(&yaml).unwrap();
        assert_eq!(
            Expression::Match(
                Match::All,
                Box::new(Expression::BooleanGroup(
                    BoolSym::Or,
                    vec![
                        Expression::Search(
                            Search::Exact("bar".to_owned()),
                            "foo".to_owned(),
                            false
                        ),
                        Expression::Search(Search::Any, "foo".to_owned(), false)
                    ]
                ))
            ),
            e
        );
    }

    #[test]
    fn parse_bool_group_match_search_shake() {
        let identifier = r"all(foo): [bar]";
        let yaml: Yaml = serde_yaml::from_str(identifier).unwrap();
        let e = super::parse_identifier(&yaml).unwrap();
        assert_eq!(
            Expression::Search(Search::Exact("bar".to_owned()), "foo".to_string(), false),
            e
        );
    }

    #[test]
    fn parse_bool_expr() {
        let e = parse(&vec![
            Token::Identifier("foo".to_string()),
            Token::Operator(BoolSym::And),
            Token::Identifier("bar".to_string()),
        ])
        .unwrap();
        assert_eq!(
            Expression::BooleanExpression(
                Box::new(Expression::Identifier("foo".to_string())),
                BoolSym::And,
                Box::new(Expression::Identifier("bar".to_string()))
            ),
            e
        );
    }

    #[test]
    fn parse_cast() {
        let e = parse(&vec![
            Token::Modifier(ModSym::Int),
            Token::Delimiter(DelSym::LeftParenthesis),
            Token::Identifier("identifier".to_owned()),
            Token::Delimiter(DelSym::RightParenthesis),
        ])
        .unwrap();
        assert_eq!(Expression::Cast("identifier".to_string(), ModSym::Int), e);

        let e = parse(&vec![
            Token::Modifier(ModSym::Not),
            Token::Delimiter(DelSym::LeftParenthesis),
            Token::Identifier("identifier".to_owned()),
            Token::Delimiter(DelSym::RightParenthesis),
        ])
        .unwrap();
        assert_eq!(Expression::Cast("identifier".to_string(), ModSym::Not), e);

        let e = parse(&vec![
            Token::Modifier(ModSym::Str),
            Token::Delimiter(DelSym::LeftParenthesis),
            Token::Identifier("identifier".to_owned()),
            Token::Delimiter(DelSym::RightParenthesis),
        ])
        .unwrap();
        assert_eq!(Expression::Cast("identifier".to_string(), ModSym::Str), e);
    }

    #[test]
    fn parse_identifier() {
        let e = parse(&vec![Token::Identifier("condition".to_string())]).unwrap();
        assert_eq!(Expression::Identifier("condition".to_string()), e);
    }

    #[test]
    fn parse_integer() {
        let e = parse(&vec![Token::Integer(1)]).unwrap();
        assert_eq!(Expression::Integer(1), e);
    }

    #[test]
    fn parse_negate() {
        let e = parse(&vec![
            Token::Miscellaneous(MiscSym::Not),
            Token::Delimiter(DelSym::LeftParenthesis),
            Token::Identifier("foo".to_string()),
            Token::Operator(BoolSym::Or),
            Token::Identifier("bar".to_string()),
            Token::Delimiter(DelSym::RightParenthesis),
        ])
        .unwrap();
        assert_eq!(
            Expression::Negate(Box::new(Expression::BooleanExpression(
                Box::new(Expression::Identifier("foo".to_string())),
                BoolSym::Or,
                Box::new(Expression::Identifier("bar".to_string()))
            ))),
            e
        );
    }

    #[test]
    fn parse_nested() {
        let identifier = r"foo: {bar: baz}";
        let yaml: Yaml = serde_yaml::from_str(identifier).unwrap();
        let e = super::parse_identifier(&yaml).unwrap();
        assert_eq!(
            Expression::Nested(
                "foo".to_owned(),
                Box::new(Expression::Search(
                    Search::Exact("baz".to_owned()),
                    "bar".to_owned(),
                    false
                ))
            ),
            e
        );
    }

    #[test]
    fn parse_expression_0() {
        let t = parse(&vec![
            Token::Delimiter(DelSym::LeftParenthesis),
            Token::Identifier("foo".to_string()),
            Token::Operator(BoolSym::And),
            Token::Identifier("bar".to_string()),
            Token::Delimiter(DelSym::RightParenthesis),
            Token::Operator(BoolSym::Or),
            Token::Identifier("fooz".to_string()),
        ])
        .unwrap();
        assert_eq!(
            Expression::BooleanExpression(
                Box::new(Expression::BooleanExpression(
                    Box::new(Expression::Identifier("foo".to_string())),
                    BoolSym::And,
                    Box::new(Expression::Identifier("bar".to_string()))
                )),
                BoolSym::Or,
                Box::new(Expression::Identifier("fooz".to_string()))
            ),
            t
        );
    }

    #[test]
    fn parse_expression_1() {
        let t = parse(&vec![
            Token::Identifier("foo".to_string()),
            Token::Operator(BoolSym::And),
            Token::Delimiter(DelSym::LeftParenthesis),
            Token::Identifier("bar".to_string()),
            Token::Operator(BoolSym::Or),
            Token::Identifier("fooz".to_string()),
            Token::Delimiter(DelSym::RightParenthesis),
        ])
        .unwrap();
        assert_eq!(
            Expression::BooleanExpression(
                Box::new(Expression::Identifier("foo".to_string())),
                BoolSym::And,
                Box::new(Expression::BooleanExpression(
                    Box::new(Expression::Identifier("bar".to_string())),
                    BoolSym::Or,
                    Box::new(Expression::Identifier("fooz".to_string()))
                ))
            ),
            t
        );
    }

    #[test]
    fn parse_expression_2() {
        let t = parse(&vec![
            Token::Identifier("foo".to_string()),
            Token::Operator(BoolSym::And),
            Token::Delimiter(DelSym::LeftParenthesis),
            Token::Miscellaneous(MiscSym::Not),
            Token::Identifier("bar".to_string()),
            Token::Operator(BoolSym::And),
            Token::Miscellaneous(MiscSym::Not),
            Token::Identifier("fooz".to_string()),
            Token::Delimiter(DelSym::RightParenthesis),
        ])
        .unwrap();
        assert_eq!(
            Expression::BooleanExpression(
                Box::new(Expression::Identifier("foo".to_string())),
                BoolSym::And,
                Box::new(Expression::BooleanExpression(
                    Box::new(Expression::Negate(Box::new(Expression::Identifier(
                        "bar".to_string()
                    )))),
                    BoolSym::And,
                    Box::new(Expression::Negate(Box::new(Expression::Identifier(
                        "fooz".to_string()
                    ))))
                ))
            ),
            t
        );
    }

    #[test]
    fn parse_identifiers_0() {
        let identifier = "[foo: bar]";
        let yaml: Yaml = serde_yaml::from_str(&identifier).unwrap();
        let e = super::parse_identifier(&yaml).unwrap();
        assert_eq!(
            Expression::BooleanGroup(
                BoolSym::Or,
                vec![Expression::Search(
                    Search::Exact("bar".to_owned()),
                    "foo".to_owned(),
                    false
                )]
            ),
            e
        );
    }

    #[test]
    fn parse_invalid_0() {
        let e = parse(&vec![
            Token::Miscellaneous(MiscSym::Not),
            Token::Modifier(ModSym::Int),
            Token::Delimiter(DelSym::LeftParenthesis),
            Token::Identifier("condition".to_string()),
            Token::Delimiter(DelSym::RightParenthesis),
        ]);
        assert!(e.is_err());
    }
}