text: ////////////////////////////////////////////////////////////////////////////////
  //
  // Copyright 2013 - 2021, Tomas Babej, Paul Beckingham, Federico Hernandez.
  //
  // Permission is hereby granted, free of charge, to any person obtaining a copy
  // of this software and associated documentation files (the "Software"), to deal
  // in the Software without restriction, including without limitation the rights
  // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  // copies of the Software, and to permit persons to whom the Software is
  // furnished to do so, subject to the following conditions:
  //
  // The above copyright notice and this permission notice shall be included
  // in all copies or substantial portions of the Software.
  //
  // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
  // OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
  // THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
  // SOFTWARE.
  //
  // https://www.opensource.org/licenses/mit-license.php
  //
  ////////////////////////////////////////////////////////////////////////////////
  
  #include <cmake.h>
  #include <Eval.h>
  #include <map>
  #include <time.h>
  #include <Context.h>
  #include <Task.h>
  #include <Color.h>
  #include <shared.h>
  #include <format.h>
  
  extern Task& contextTask;
  
  ////////////////////////////////////////////////////////////////////////////////
  // Supported operators, borrowed from C++, particularly the precedence.
  // Note: table is sorted by length of operator string, so searches match
  //       longest first.
  static struct
  {
    std::string op;
    int         precedence;
    char        type;                  // b=binary, u=unary, c=circumfix
    char        associativity;         // l=left, r=right, _=?
  } operators[] =
  {
    // Operator   Precedence  Type  Associativity
    {  "^",        16,         'b',  'r' },    // Exponent
  
    {  "!",        15,         'u',  'r' },    // Unary not
    {  "_neg_",    15,         'u',  'r' },    // Unary minus
    {  "_pos_",    15,         'u',  'r' },    // Unary plus
  
    {  "_hastag_", 14,         'b',  'l'},     // +tag  [Pseudo-op]
    {  "_notag_",  14,         'b',  'l'},     // -tag  [Pseudo-op]
  
    {  "*",        13,         'b',  'l' },    // Multiplication
    {  "/",        13,         'b',  'l' },    // Division
    {  "%",        13,         'b',  'l' },    // Modulus
  
    {  "+",        12,         'b',  'l' },    // Addition
    {  "-",        12,         'b',  'l' },    // Subtraction
  
    {  "<=",       10,         'b',  'l' },    // Less than or equal
    {  ">=",       10,         'b',  'l' },    // Greater than or equal
    {  ">",        10,         'b',  'l' },    // Greater than
    {  "<",        10,         'b',  'l' },    // Less than
  
    {  "=",         9,         'b',  'l' },    // Equal (partial)
    {  "==",        9,         'b',  'l' },    // Equal (exact)
    {  "!=",        9,         'b',  'l' },    // Inequal (partial)
    {  "!==",       9,         'b',  'l' },    // Inequal (exact)
  
    {  "~",         8,         'b',  'l' },    // Regex match
    {  "!~",        8,         'b',  'l' },    // Regex non-match
  
    {  "and",       5,         'b',  'l' },    // Conjunction
    {  "or",        4,         'b',  'l' },    // Disjunction
    {  "xor",       3,         'b',  'l' },    // Disjunction
  
    {  "(",         0,         'c',  '_' },    // Precedence start
    {  ")",         0,         'c',  '_' },    // Precedence end
  };
  
  #define NUM_OPERATORS (sizeof (operators) / sizeof (operators[0]))
  
  ////////////////////////////////////////////////////////////////////////////////
  // Built-in support for some named constants.
  static bool namedConstants (const std::string& name, Variant& value)
  {
         if (name == "true")  value = Variant (true);
    else if (name == "false") value = Variant (false);
    else if (name == "pi")    value = Variant (3.14159165);
    else
      return false;
  
    return true;
  }
  
  ////////////////////////////////////////////////////////////////////////////////
  Eval::Eval ()
  {
    addSource (namedConstants);
  }
  
  ////////////////////////////////////////////////////////////////////////////////
  void Eval::addSource (bool (*source)(const std::string&, Variant&))
  {
    _sources.push_back (source);
  }
  
  ////////////////////////////////////////////////////////////////////////////////
  void Eval::evaluateInfixExpression (const std::string& e, Variant& v) const
  {
    // Reduce e to a vector of tokens.
    Lexer l (e);
    std::vector <std::pair <std::string, Lexer::Type>> tokens;
    std::string token;
    Lexer::Type type;
    while (l.token (token, type))
      tokens.emplace_back (token, type);
  
    // Parse for syntax checking and operator replacement.
    if (_debug)
      Context::getContext ().debug ("[1;37;42mFILTER[0m Infix        " + dump (tokens));
    infixParse (tokens);
    if (_debug)
      Context::getContext ().debug ("[1;37;42mFILTER[0m Infix parsed " + dump (tokens));
  
    // Convert infix --> postfix.
    infixToPostfix (tokens);
    if (_debug)
      Context::getContext ().debug ("[1;37;42mFILTER[0m Postfix      " + dump (tokens));
  
    // Call the postfix evaluator.
    evaluatePostfixStack (tokens, v);
  }
  
  ////////////////////////////////////////////////////////////////////////////////
  void Eval::evaluatePostfixExpression (const std::string& e, Variant& v) const
  {
    // Reduce e to a vector of tokens.
    Lexer l (e);
    std::vector <std::pair <std::string, Lexer::Type>> tokens;
    std::string token;
    Lexer::Type type;
    while (l.token (token, type))
      tokens.emplace_back (token, type);
  
    if (_debug)
      Context::getContext ().debug ("[1;37;42mFILTER[0m Postfix      " + dump (tokens));
  
    // Call the postfix evaluator.
    evaluatePostfixStack (tokens, v);
  }
  
  ////////////////////////////////////////////////////////////////////////////////
  void Eval::compileExpression (
    const std::vector <std::pair <std::string, Lexer::Type>>& precompiled)
  {
    _compiled = precompiled;
  
    // Parse for syntax checking and operator replacement.
    if (_debug)
      Context::getContext ().debug ("[1;37;42mFILTER[0m Infix        " + dump (_compiled));
    infixParse (_compiled);
    if (_debug)
      Context::getContext ().debug ("[1;37;42mFILTER[0m Infix parsed " + dump (_compiled));
  
    // Convert infix --> postfix.
    infixToPostfix (_compiled);
    if (_debug)
      Context::getContext ().debug ("[1;37;42mFILTER[0m Postfix      " + dump (_compiled));
  }
  
  ////////////////////////////////////////////////////////////////////////////////
  void Eval::evaluateCompiledExpression (Variant& v)
  {
    // Call the postfix evaluator.
    evaluatePostfixStack (_compiled, v);
  }
  
  ////////////////////////////////////////////////////////////////////////////////
  void Eval::debug (bool value)
  {
    _debug = value;
  }
  
  ////////////////////////////////////////////////////////////////////////////////
  // Static.
  std::vector <std::string> Eval::getOperators ()
  {
    std::vector <std::string> all;
    all.reserve(NUM_OPERATORS);
    for (const auto &opr : operators)
      all.push_back (opr.op);
  
    return all;
  }
  
  ////////////////////////////////////////////////////////////////////////////////
  // Static.
  std::vector <std::string> Eval::getBinaryOperators ()
  {
    std::vector <std::string> all;
    for (const auto &opr : operators)
      if (opr.type == 'b')
        all.push_back (opr.op);
  
    return all;
  }
  
  ////////////////////////////////////////////////////////////////////////////////
  void Eval::evaluatePostfixStack (
    const std::vector <std::pair <std::string, Lexer::Type>>& tokens,
    Variant& result) const
  {
    if (tokens.size () == 0)
      throw std::string ("No expression to evaluate.");
  
    // This is stack used by the postfix evaluator.
    std::vector <Variant> values;
    values.reserve(tokens.size());
  
    for (const auto& token : tokens)
    {
      // Unary operators.
      if (token.second == Lexer::Type::op &&
          token.first == "!")
      {
        if (values.size () < 1)
          throw std::string ("The expression could not be evaluated.");
  
        Variant right = values.back ();
        values.pop_back ();
        Variant result = ! right;
        values.push_back (result);
        if (_debug)
          Context::getContext ().debug (format ("Eval {1} ↓'{2}' → ↑'{3}'", token.first, (std::string) right, (std::string) result));
      }
      else if (token.second == Lexer::Type::op &&
               token.first == "_neg_")
      {
        if (values.size () < 1)
          throw std::string ("The expression could not be evaluated.");
  
        Variant right = values.back ();
        values.pop_back ();
  
        Variant result (0);
        result -= right;
        values.push_back (result);
  
        if (_debug)
          Context::getContext ().debug (format ("Eval {1} ↓'{2}' → ↑'{3}'", token.first, (std::string) right, (std::string) result));
      }
      else if (token.second == Lexer::Type::op &&
               token.first == "_pos_")
      {
        // The _pos_ operator is a NOP.
        if (_debug)
          Context::getContext ().debug (format ("[{1}] eval op {2} NOP", values.size (), token.first));
      }
  
      // Binary operators.
      else if (token.second == Lexer::Type::op)
      {
        if (values.size () < 2)
          throw std::string ("The expression could not be evaluated.");
  
        Variant right = values.back ();
        values.pop_back ();
  
        Variant left = values.back ();
        values.pop_back ();
  
        // Ordering these by anticipation frequency of use is a good idea.
        Variant result;
             if (token.first == "and")      result = left && right;
        else if (token.first == "or")       result = left || right;
        else if (token.first == "&&")       result = left && right;
        else if (token.first == "||")       result = left || right;
        else if (token.first == "<")        result = left < right;
        else if (token.first == "<=")       result = left <= right;
        else if (token.first == ">")        result = left > right;
        else if (token.first == ">=")       result = left >= right;
        else if (token.first == "==")       result = left.operator== (right);
        else if (token.first == "!==")      result = left.operator!= (right);
        else if (token.first == "=")        result = left.operator_partial (right);
        else if (token.first == "!=")       result = left.operator_nopartial (right);
        else if (token.first == "+")        result = left + right;
        else if (token.first == "-")        result = left - right;
        else if (token.first == "*")        result = left * right;
        else if (token.first == "/")        result = left / right;
        else if (token.first == "^")        result = left ^ right;
        else if (token.first == "%")        result = left % right;
        else if (token.first == "xor")      result = left.operator_xor (right);
        else if (token.first == "~")        result = left.operator_match (right, contextTask);
        else if (token.first == "!~")       result = left.operator_nomatch (right, contextTask);
        else if (token.first == "_hastag_") result = left.operator_hastag (right, contextTask);
        else if (token.first == "_notag_")  result = left.operator_notag (right, contextTask);
        else
          throw format ("Unsupported operator '{1}'.", token.first);
  
        values.push_back (result);
  
        if (_debug)
          Context::getContext ().debug (format ("Eval ↓'{1}' {2} ↓'{3}' → ↑'{4}'", (std::string) left, token.first, (std::string) right, (std::string) result));
      }
  
      // Literals and identifiers.
      else
      {
        Variant v (token.first);
        switch (token.second)
        {
        case Lexer::Type::number:
          if (Lexer::isAllDigits (token.first))
          {
            v.cast (Variant::type_integer);
            if (_debug)
              Context::getContext ().debug (format ("Eval literal number ↑'{1}'", (std::string) v));
          }
          else
          {
            v.cast (Variant::type_real);
            if (_debug)
              Context::getContext ().debug (format ("Eval literal decimal ↑'{1}'", (std::string) v));
          }
          break;
  
        case Lexer::Type::op:
          throw std::string ("Operator expected.");
          break;
  
        case Lexer::Type::dom:
        case Lexer::Type::identifier:
          {
            bool found = false;
            for (const auto& source : _sources)
            {
              if (source (token.first, v))
              {
                if (_debug)
                  Context::getContext ().debug (format ("Eval identifier source '{1}' → ↑'{2}'", token.first, (std::string) v));
                found = true;
                break;
              }
            }
  
            // An identifier that fails lookup is a string.
            if (! found)
            {
              v.cast (Variant::type_string);
              if (_debug)
                Context::getContext ().debug (format ("Eval identifier source failed '{1}'", token.first));
            }
          }
          break;
  
        case Lexer::Type::date:
          v.cast (Variant::type_date);
          if (_debug)
            Context::getContext ().debug (format ("Eval literal date ↑'{1}'", (std::string) v));
          break;
  
        case Lexer::Type::duration:
          v.cast (Variant::type_duration);
          if (_debug)
            Context::getContext ().debug (format ("Eval literal duration ↑'{1}'", (std::string) v));
          break;
  
        // Nothing to do.
        case Lexer::Type::string:
        default:
          if (_debug)
            Context::getContext ().debug (format ("Eval literal string ↑'{1}'", (std::string) v));
          break;
        }
  
        values.push_back (v);
      }
    }
  
    // If there is more than one variant left on the stack, then the original
    // expression was not valid.
    if (values.size () != 1)
      throw std::string ("The value is not an expression.");
  
    result = values[0];
  }
  
  ////////////////////////////////////////////////////////////////////////////////
  //
  // Grammar:
  //   Logical     --> Regex {( "and" | "or" | "xor" ) Regex}
  //   Regex       --> Equality {( "~" | "!~" ) Equality}
  //   Equality    --> Comparative {( "==" | "=" | "!=" ) Comparative}
  //   Comparative --> Arithmetic {( "<=" | "<" | ">=" | ">" ) Arithmetic}
  //   Arithmetic  --> Geometric {( "+" | "-" ) Geometric}
  //   Geometric   --> Tag {( "*" | "/" | "%" ) Tag}
  //   Tag         --> Unary {( "_hastag_" | "_notag_" ) Unary}
  //   Unary       --> [( "-" | "+" | "!" )] Exponent
  //   Exponent    --> Primitive ["^" Primitive]
  //   Primitive   --> "(" Logical ")" | Variant
  //
  void Eval::infixParse (
    std::vector <std::pair <std::string, Lexer::Type>>& infix) const
  {
    unsigned int i = 0;
    parseLogical (infix, i);
  }
  
  ////////////////////////////////////////////////////////////////////////////////
  // Logical     --> Regex {( "and" | "or" | "xor" ) Regex}
  bool Eval::parseLogical (
    std::vector <std::pair <std::string, Lexer::Type>>& infix,
    unsigned int &i) const
  {
    if (i < infix.size () &&
        parseRegex (infix, i))
    {
      while (i < infix.size () &&
             infix[i].second == Lexer::Type::op &&
             (infix[i].first == "and"  ||
              infix[i].first == "or"   ||
              infix[i].first == "xor"))
      {
        ++i;
        if (! parseRegex (infix, i))
          return false;
      }
  
      return true;
    }
  
    return false;
  }
  
  ////////////////////////////////////////////////////////////////////////////////
  // Regex       --> Equality {( "~" | "!~" ) Equality}
  bool Eval::parseRegex (
    std::vector <std::pair <std::string, Lexer::Type>>& infix,
    unsigned int &i) const
  {
    if (i < infix.size () &&
        parseEquality (infix, i))
    {
      while (i < infix.size () &&
             infix[i].second == Lexer::Type::op &&
             (infix[i].first == "~" ||
              infix[i].first == "!~"))
      {
        ++i;
        if (! parseEquality (infix, i))
          return false;
      }
  
      return true;
    }
  
    return false;
  }
  
  ////////////////////////////////////////////////////////////////////////////////
  // Equality    --> Comparative {( "==" | "=" | "!==" | "!=" ) Comparative}
  bool Eval::parseEquality (
    std::vector <std::pair <std::string, Lexer::Type>>& infix,
    unsigned int &i) const
  {
    if (i < infix.size () &&
        parseComparative (infix, i))
    {
      while (i < infix.size () &&
             infix[i].second == Lexer::Type::op &&
             (infix[i].first == "=="  ||
              infix[i].first == "="   ||
              infix[i].first == "!==" ||
              infix[i].first == "!="))
      {
        ++i;
        if (! parseComparative (infix, i))
          return false;
      }
  
      return true;
    }
  
    return false;
  }
  
  ////////////////////////////////////////////////////////////////////////////////
  // Comparative --> Arithmetic {( "<=" | "<" | ">=" | ">" ) Arithmetic}
  bool Eval::parseComparative (
    std::vector <std::pair <std::string, Lexer::Type>>& infix,
    unsigned int &i) const
  {
    if (i < infix.size () &&
        parseArithmetic (infix, i))
    {
      while (i < infix.size () &&
             infix[i].second == Lexer::Type::op &&
             (infix[i].first == "<=" ||
              infix[i].first == "<"  ||
              infix[i].first == ">=" ||
              infix[i].first == ">"))
      {
        ++i;
        if (! parseArithmetic (infix, i))
          return false;
      }
  
      return true;
    }
  
    return false;
  }
  
  ////////////////////////////////////////////////////////////////////////////////
  // Arithmetic  --> Geometric {( "+" | "-" ) Geometric}
  bool Eval::parseArithmetic (
    std::vector <std::pair <std::string, Lexer::Type>>& infix,
    unsigned int &i) const
  {
    if (i < infix.size () &&
        parseGeometric (infix, i))
    {
      while (i < infix.size () &&
             infix[i].second == Lexer::Type::op &&
             (infix[i].first == "+" ||
              infix[i].first == "-"))
      {
        ++i;
        if (! parseGeometric (infix, i))
          return false;
      }
  
      return true;
    }
  
    return false;
  }
  
  ////////////////////////////////////////////////////////////////////////////////
  // Geometric   --> Tag {( "*" | "/" | "%" ) Tag}
  bool Eval::parseGeometric (
    std::vector <std::pair <std::string, Lexer::Type>>& infix,
    unsigned int &i) const
  {
    if (i < infix.size () &&
        parseTag (infix, i))
    {
      while (i < infix.size () &&
             infix[i].second == Lexer::Type::op &&
             (infix[i].first == "*" ||
              infix[i].first == "/" ||
              infix[i].first == "%"))
      {
        ++i;
        if (! parseTag (infix, i))
          return false;
      }
  
      return true;
    }
  
    return false;
  }
  
  ////////////////////////////////////////////////////////////////////////////////
  // Tag         --> Unary {( "_hastag_" | "_notag_" ) Unary}
  bool Eval::parseTag (
    std::vector <std::pair <std::string, Lexer::Type>>& infix,
    unsigned int &i) const
  {
    if (i < infix.size () &&
        parseUnary (infix, i))
    {
      while (i < infix.size () &&
             infix[i].second == Lexer::Type::op &&
             (infix[i].first == "_hastag_" ||
              infix[i].first == "_notag_"))
      {
        ++i;
        if (! parseUnary (infix, i))
          return false;
      }
  
      return true;
    }
  
    return false;
  }
  
  ////////////////////////////////////////////////////////////////////////////////
  // Unary       --> [( "-" | "+" | "!" )] Exponent
  bool Eval::parseUnary (
    std::vector <std::pair <std::string, Lexer::Type>>& infix,
    unsigned int &i) const
  {
    if (i < infix.size ())
    {
      if (infix[i].first == "-")
      {
        infix[i].first = "_neg_";
        ++i;
      }
      else if (infix[i].first == "+")
      {
        infix[i].first = "_pos_";
        ++i;
      }
      else if (infix[i].first == "!")
      {
        ++i;
      }
    }
  
    return parseExponent (infix, i);
  }
  
  ////////////////////////////////////////////////////////////////////////////////
  // Exponent    --> Primitive ["^" Primitive]
  bool Eval::parseExponent (
    std::vector <std::pair <std::string, Lexer::Type>>& infix,
    unsigned int& i) const
  {
    if (i < infix.size () &&
        parsePrimitive (infix, i))
    {
      while (i < infix.size () &&
             infix[i].second == Lexer::Type::op &&
             infix[i].first == "^")
      {
        ++i;
        if (! parsePrimitive (infix, i))
          return false;
      }
  
      return true;
    }
  
    return false;
  }
  
  ////////////////////////////////////////////////////////////////////////////////
  // Primitive   --> "(" Logical ")" | Variant
  bool Eval::parsePrimitive (
    std::vector <std::pair <std::string, Lexer::Type>>& infix,
    unsigned int &i) const
  {
    if (i < infix.size ())
    {
      if (infix[i].first == "(")
      {
        ++i;
        if (i < infix.size () &&
            parseLogical (infix, i))
        {
          if (i < infix.size () &&
              infix[i].first == ")")
          {
            ++i;
            return true;
          }
        }
      }
      else
      {
        bool found = false;
        for (const auto& source : _sources)
        {
          Variant v;
          if (source (infix[i].first, v))
          {
            found = true;
            break;
          }
        }
  
        if (found)
        {
          ++i;
          return true;
        }
        else if (infix[i].second != Lexer::Type::op)
        {
          ++i;
          return true;
        }
      }
    }
  
    return false;
  }
  
  ////////////////////////////////////////////////////////////////////////////////
  // Dijkstra Shunting Algorithm.
  // https://en.wikipedia.org/wiki/Shunting-yard_algorithm
  //
  //   While there are tokens to be read:
  //     Read a token.
  //     If the token is an operator, o1, then:
  //       while there is an operator token, o2, at the top of the stack, and
  //             either o1 is left-associative and its precedence is less than or
  //             equal to that of o2,
  //             or o1 is right-associative and its precedence is less than that
  //             of o2,
  //         pop o2 off the stack, onto the output queue;
  //       push o1 onto the stack.
  //     If the token is a left parenthesis, then push it onto the stack.
  //     If the token is a right parenthesis:
  //       Until the token at the top of the stack is a left parenthesis, pop
  //       operators off the stack onto the output queue.
  //       Pop the left parenthesis from the stack, but not onto the output queue.
  //       If the token at the top of the stack is a function token, pop it onto
  //       the output queue.
  //       If the stack runs out without finding a left parenthesis, then there
  //       are mismatched parentheses.
  //     If the token is a number, then add it to the output queue.
  //
  //   When there are no more tokens to read:
  //     While there are still operator tokens in the stack:
  //       If the operator token on the top of the stack is a parenthesis, then
  //       there are mismatched parentheses.
  //       Pop the operator onto the output queue.
  //   Exit.
  //
  void Eval::infixToPostfix (
    std::vector <std::pair <std::string, Lexer::Type>>& infix) const
  {
    // Short circuit.
    if (infix.size () == 1)
      return;
  
    // Result.
    std::vector <std::pair <std::string, Lexer::Type>> postfix;
  
    // Shunting yard.
    std::vector <std::pair <std::string, Lexer::Type>> op_stack;
  
    // Operator characteristics.
    char type;
    unsigned int precedence;
    char associativity;
  
    for (auto& token : infix)
    {
      if (token.second == Lexer::Type::op &&
          token.first == "(")
      {
        op_stack.push_back (token);
      }
      else if (token.second == Lexer::Type::op &&
               token.first == ")")
      {
        while (op_stack.size () &&
               op_stack.back ().first != "(")
        {
          postfix.push_back (op_stack.back ());
          op_stack.pop_back ();
        }
  
        if (op_stack.size ())
          op_stack.pop_back ();
        else
          throw std::string ("Mismatched parentheses in expression");
      }
      else if (token.second == Lexer::Type::op &&
               identifyOperator (token.first, type, precedence, associativity))
      {
        char type2;
        unsigned int precedence2;
        char associativity2;
        while (op_stack.size () > 0 &&
               identifyOperator (op_stack.back ().first, type2, precedence2, associativity2) &&
               ((associativity == 'l' && precedence <= precedence2) ||
                (associativity == 'r' && precedence <  precedence2)))
        {
          postfix.push_back (op_stack.back ());
          op_stack.pop_back ();
        }
  
        op_stack.push_back (token);
      }
      else
      {
        postfix.push_back (token);
      }
    }
  
    while (op_stack.size ())
    {
      if (op_stack.back ().first == "(" ||
          op_stack.back ().first == ")")
        throw std::string ("Mismatched parentheses in expression");
  
      postfix.push_back (op_stack.back ());
      op_stack.pop_back ();
    }
  
    infix = postfix;
  }
  
  ////////////////////////////////////////////////////////////////////////////////
  bool Eval::identifyOperator (
    const std::string& op,
    char& type,
    unsigned int& precedence,
    char& associativity) const
  {
    for (const auto& opr : operators)
    {
      if (opr.op == op)
      {
        type          = opr.type;
        precedence    = opr.precedence;
        associativity = opr.associativity;
        return true;
      }
    }
  
    return false;
  }
  
  ////////////////////////////////////////////////////////////////////////////////
  std::string Eval::dump (
    std::vector <std::pair <std::string, Lexer::Type>>& tokens) const
  {
    // Set up a color mapping.
    std::map <Lexer::Type, Color> color_map;
    color_map[Lexer::Type::op]         = Color ("gray14 on gray6");
    color_map[Lexer::Type::number]     = Color ("rgb530 on gray6");
    color_map[Lexer::Type::hex]        = Color ("rgb303 on gray6");
    color_map[Lexer::Type::string]     = Color ("rgb550 on gray6");
    color_map[Lexer::Type::dom]        = Color ("rgb045 on gray6");
    color_map[Lexer::Type::identifier] = Color ("rgb035 on gray6");
    color_map[Lexer::Type::date]       = Color ("rgb150 on gray6");
    color_map[Lexer::Type::duration]   = Color ("rgb531 on gray6");
  
    std::string output;
    for (auto i = tokens.begin (); i != tokens.end (); ++i)
    {
      if (i != tokens.begin ())
        output += ' ';
  
      Color c;
      if (color_map[i->second].nontrivial ())
        c = color_map[i->second];
      else
        c = Color ("rgb000 on gray6");
  
      output += c.colorize (i->first);
    }
  
    return output;
  }
  
  ////////////////////////////////////////////////////////////////////////////////
  