mirror of
https://github.com/GothenburgBitFactory/taskwarrior.git
synced 2025-06-26 10:54:26 +02:00
860 lines
26 KiB
C++
860 lines
26 KiB
C++
////////////////////////////////////////////////////////////////////////////////
|
||
//
|
||
// Copyright 2013 - 2020, 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.push_back (std::pair <std::string, Lexer::Type> (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.push_back (std::pair <std::string, Lexer::Type> (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;
|
||
for (unsigned int i = 0; i < NUM_OPERATORS; ++i)
|
||
all.push_back (operators[i].op);
|
||
|
||
return all;
|
||
}
|
||
|
||
////////////////////////////////////////////////////////////////////////////////
|
||
// Static.
|
||
std::vector <std::string> Eval::getBinaryOperators ()
|
||
{
|
||
std::vector <std::string> all;
|
||
for (unsigned int i = 0; i < NUM_OPERATORS; ++i)
|
||
if (operators[i].type == 'b')
|
||
all.push_back (operators[i].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;
|
||
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 (auto source = _sources.begin (); source != _sources.end (); ++source)
|
||
{
|
||
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 (auto source = _sources.begin (); source != _sources.end (); ++source)
|
||
{
|
||
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.
|
||
// http://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 (unsigned int i = 0; i < NUM_OPERATORS; ++i)
|
||
{
|
||
if (operators[i].op == op)
|
||
{
|
||
type = operators[i].type;
|
||
precedence = operators[i].precedence;
|
||
associativity = operators[i].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;
|
||
}
|
||
|
||
////////////////////////////////////////////////////////////////////////////////
|