]> git.tdb.fi Git - builder.git/blobdiff - source/booleanevaluator.cpp
Support boolean logic in conditions
[builder.git] / source / booleanevaluator.cpp
diff --git a/source/booleanevaluator.cpp b/source/booleanevaluator.cpp
new file mode 100644 (file)
index 0000000..ecb38fb
--- /dev/null
@@ -0,0 +1,143 @@
+#include <stdexcept>
+#include "booleanevaluator.h"
+
+using namespace std;
+
+/* I'd rather have overloads with different slots, but that creates an
+ambiguity because slots have template constructors. */
+BooleanEvaluator::BooleanEvaluator(const Slot &s, bool allow_compare):
+       slot(s),
+       ops(allow_compare ? "&|!=^" : "&|!")
+{ }
+
+bool BooleanEvaluator::evaluate(const string &str)
+{
+       string buf;
+       last_was_op = true;
+       for(string::const_iterator i=str.begin();; ++i)
+       {
+               if(i!=str.end() && (isalnum(*i) || (!buf.empty() && (*i=='_' || *i=='-'))))
+                       buf += *i;
+               else
+               {
+                       if(!buf.empty())
+                       {
+                               if(!last_was_op)
+                                       throw runtime_error("syntax error at "+buf);
+
+                               char op = (op_stack.empty() ? 0 : op_stack.back());
+                               if(op=='=' || op=='^')
+                               {
+                                       op_stack.pop_back();
+                                       string var = var_stack.back();
+                                       var_stack.pop_back();
+                                       bool value = (slot(var, &buf) == (op=='='));
+                                       value_stack.push_back(value);
+                               }
+                               else
+                                       var_stack.push_back(buf);
+
+                               buf.clear();
+                               last_was_op = false;
+                       }
+
+                       if(i==str.end())
+                               break;
+                       else if(isspace(*i))
+                               ;
+                       else if(ops.find(*i)!=string::npos)
+                               push_op(*i);
+                       else
+                               throw runtime_error("syntax error at "+string(1, *i));
+               }
+       }
+
+       collapse(0);
+
+       bool value = pop_value();
+       if(!value_stack.empty())
+               throw runtime_error("too many values");
+
+       return value;
+}
+
+void BooleanEvaluator::push_op(char op)
+{
+       if(last_was_op!=is_unary(op))
+               throw runtime_error("syntax error at "+string(1, op));
+       // TODO Disallow mixing of ! and =/^
+       if(is_logic(op) && !var_stack.empty())
+               value_stack.push_back(pop_value());
+
+       if(!is_unary(op))
+               collapse(precedence(op));
+       op_stack.push_back(op);
+       last_was_op = true;
+}
+
+bool BooleanEvaluator::pop_value()
+{
+       if(!var_stack.empty())
+       {
+               string var = var_stack.back();
+               var_stack.pop_back();
+               return slot(var, 0);
+       }
+       else if(!value_stack.empty())
+       {
+               bool value = value_stack.back();
+               value_stack.pop_back();
+               return value;
+       }
+
+       throw runtime_error("value stack underflow");
+}
+
+void BooleanEvaluator::collapse(unsigned until)
+{
+       while(!op_stack.empty())
+       {
+               char op = op_stack.back();
+               if(precedence(op)<until)
+                       return;
+
+               op_stack.pop_back();
+               bool value1 = pop_value();
+               if(is_unary(op))
+               {
+                       if(op=='!')
+                               value1 = !value1;
+               }
+               else
+               {
+                       bool value2 = pop_value();
+                       if(op=='&')
+                               value1 = (value1 && value2);
+                       else if(op=='|')
+                               value1 = (value1 || value2);
+               }
+               value_stack.push_back(value1);
+       }
+}
+
+unsigned BooleanEvaluator::precedence(char op)
+{
+       if(op=='&')
+               return 1;
+       else if(op=='=' || op=='^')
+               return 2;
+       else if(op=='!')
+               return 3;
+       else
+               return 0;
+}
+
+bool BooleanEvaluator::is_unary(char op)
+{
+       return op=='!';
+}
+
+bool BooleanEvaluator::is_logic(char op)
+{
+       return (op=='&' || op=='|' || op=='!');
+}