]> git.tdb.fi Git - builder.git/commitdiff
Support boolean logic in conditions
authorMikko Rasa <tdb@tdb.fi>
Thu, 3 Oct 2013 13:27:12 +0000 (16:27 +0300)
committerMikko Rasa <tdb@tdb.fi>
Thu, 3 Oct 2013 13:27:12 +0000 (16:27 +0300)
Parentheses are missing and a few corner cases trigger obscure errors, but
it's good enough for now.

source/booleanevaluator.cpp [new file with mode: 0644]
source/booleanevaluator.h [new file with mode: 0644]
source/component.cpp
source/package.cpp
source/sourcepackage.cpp
source/sourcepackage.h

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=='!');
+}
diff --git a/source/booleanevaluator.h b/source/booleanevaluator.h
new file mode 100644 (file)
index 0000000..08ed432
--- /dev/null
@@ -0,0 +1,34 @@
+#ifndef BOOLEANEVALUATOR_H_
+#define BOOLEANEVALUATOR_H_
+
+#include <string>
+#include <vector>
+#include <sigc++/slot.h>
+
+class BooleanEvaluator
+{
+public:
+       typedef sigc::slot<bool, const std::string &, const std::string *> Slot;
+
+private:
+       Slot slot;
+       std::string ops;
+       std::vector<std::string> var_stack;
+       std::vector<unsigned char> value_stack;
+       std::vector<char> op_stack;
+       bool last_was_op;
+
+public:
+       BooleanEvaluator(const Slot &, bool = true);
+
+       bool evaluate(const std::string &);
+private:
+       void push_op(char);
+       bool pop_value();
+       void collapse(unsigned);
+       unsigned precedence(char);
+       bool is_unary(char);
+       bool is_logic(char);
+};
+
+#endif
index d23e1b3d1cc34825250b44b81570094514c69682..fa6fd5670a5e1cf56251224e7aaf7b80279f02ed 100644 (file)
@@ -4,6 +4,7 @@
 #include <msp/fs/utils.h>
 #include <msp/io/print.h>
 #include <msp/strings/lexicalcast.h>
+#include "booleanevaluator.h"
 #include "builder.h"
 #include "component.h"
 #include "csourcefile.h"
@@ -348,7 +349,8 @@ void Component::Loader::if_arch(const string &cond)
 
 void Component::Loader::if_feature(const string &cond)
 {
-       bool match = obj.package.match_feature(cond);
+       BooleanEvaluator eval(sigc::mem_fun(&obj.package, &SourcePackage::match_feature));
+       bool match = eval.evaluate(cond);
        obj.package.get_builder().get_logger().log("configure",
                format("%s/%s: feature %s %smatched", obj.package.get_name(), obj.name, cond, (match ? "" : "not ")));
        if(match)
index aa9160e11b8e62ab03c16a0e961dc579bbe0894f..19ee511a814cf34bf46efd20f9409c7394b5d879 100644 (file)
@@ -1,6 +1,7 @@
 #include <msp/io/print.h>
 #include <msp/strings/lexicalcast.h>
 #include <msp/strings/utils.h>
+#include "booleanevaluator.h"
 #include "builder.h"
 #include "package.h"
 
@@ -38,7 +39,8 @@ Package::Loader::Loader(Package &p):
 
 void Package::Loader::if_arch(const string &cond)
 {
-       bool match = obj.builder.get_current_arch().match_name(cond);
+       BooleanEvaluator eval(sigc::hide<1>(sigc::mem_fun(&obj.builder.get_current_arch(), &Architecture::match_name)), false);
+       bool match = eval.evaluate(cond);
        obj.builder.get_logger().log("configure", format("%s: arch %s %smatched", obj.name, cond, (match ? "" : "not ")));
        if(match)
                load_sub_with(*this);
index dbb759205fe0eef45f3b0b1ded50cfe5ba80a077..ddfa00fb9bf3175eb59a7391b2e16cd38555370b 100644 (file)
@@ -4,6 +4,7 @@
 #include <msp/strings/lexicalcast.h>
 #include <msp/strings/utils.h>
 #include "binarypackage.h"
+#include "booleanevaluator.h"
 #include "builder.h"
 #include "file.h"
 #include "pkgconfigfile.h"
@@ -63,25 +64,13 @@ FS::Path SourcePackage::get_output_directory() const
                return source_dir/arch.get_name();
 }
 
-bool SourcePackage::match_feature(const string &cond) const
+bool SourcePackage::match_feature(const string &feat, const string *comp) const
 {
-       string::size_type equals = cond.find('=');
-       if(equals!=string::npos)
-       {
-               if(equals==0)
-                       throw invalid_argument("SourcePackage::match_feature");
-               bool negate = cond[equals-1]=='!';
-               string feat = cond.substr(0, equals-negate);
-               string value = config.get_option("with_"+feat).value;
-               return (value==cond.substr(equals+1))!=negate;
-       }
+       string value = config.get_option("with_"+feat).value;
+       if(comp)
+               return value==*comp;
        else
-       {
-               bool negate = (cond[0]=='!');
-               string feat = cond.substr(negate);
-               string value = config.get_option("with_"+feat).value;
-               return lexical_cast<bool>(value)!=negate;
-       }
+               return lexical_cast<bool>(value);
 }
 
 void SourcePackage::set_build_type(const BuildType &t)
@@ -229,7 +218,8 @@ void SourcePackage::Loader::generate(const string &tag)
 
 void SourcePackage::Loader::if_feature(const string &cond)
 {
-       bool match = obj.match_feature(cond);
+       BooleanEvaluator eval(sigc::mem_fun(&obj, &SourcePackage::match_feature));
+       bool match = eval.evaluate(cond);
        obj.builder.get_logger().log("configure", format("%s: feature %s %smatched", obj.name, cond, (match ? "" : "not ")));
        if(match)
                load_sub_with(*this);
index 1333bdb5ca536a75946a5a4108e4d6acf0fe9c8a..fe79dccc25647470708bc686d6d31dc0374943a8 100644 (file)
@@ -86,7 +86,7 @@ public:
        const Toolchain &get_toolchain() const { return local_tools; }
        const ComponentList &get_components() const { return components; }
        const Config &get_config() const { return config; }
-       bool match_feature(const std::string &) const;
+       bool match_feature(const std::string &, const std::string *) const;
        void set_build_type(const BuildType &);
        const BuildInfo &get_build_info() const { return build_info; }
 private: