Просмотр исходного кода

Fixed a regression error: The "current_extruder" identifier was not set
at the placeholder parser.
Implemented a new PlaceholderParser::evaluate_boolean_expression()
functionality to evaluate just a boolean expression using the full
expressive power of the macro processing syntax. This function
will now be used for deciding, which print or filament preset
is compatible with which printer preset.

bubnikv 7 лет назад
Родитель
Сommit
bb61de8379

+ 4 - 0
lib/Slic3r.pm

@@ -80,6 +80,10 @@ my $paused = 0;
 $Slic3r::loglevel = (defined($ENV{'SLIC3R_LOGLEVEL'}) && $ENV{'SLIC3R_LOGLEVEL'} =~ /^[1-9]/) ? $ENV{'SLIC3R_LOGLEVEL'} : 0;
 set_logging_level($Slic3r::loglevel);
 
+# Let the palceholder parser evaluate one expression to initialize its local static macro_processor 
+# class instance in a thread safe manner.
+Slic3r::GCode::PlaceholderParser->new->evaluate_boolean_expression('1==1');
+
 sub spawn_thread {
     my ($cb) = @_;
     @_ = ();

+ 9 - 1
t/custom_gcode.t

@@ -1,4 +1,4 @@
-use Test::More tests => 49;
+use Test::More tests => 55;
 use strict;
 use warnings;
 
@@ -67,6 +67,14 @@ use Slic3r::Test;
     is $parser->process('{2*foo*(3-12)}'), '0', 'math: 2*foo*(3-12)';
     is $parser->process('{2*bar*(3-12)}'), '-36', 'math: 2*bar*(3-12)';
     ok abs($parser->process('{2.5*bar*(3-12)}') - -45) < 1e-7, 'math: 2.5*bar*(3-12)';
+
+    # Test the boolean expression parser.
+    is $parser->evaluate_boolean_expression('12 == 12'), 1, 'boolean expression parser: 12 == 12';
+    is $parser->evaluate_boolean_expression('12 != 12'), 0, 'boolean expression parser: 12 != 12';
+    is $parser->evaluate_boolean_expression('"has some PATTERN embedded" =~ /.*PATTERN.*/'), 1, 'boolean expression parser: regex matches';
+    is $parser->evaluate_boolean_expression('"has some PATTERN embedded" =~ /.*PTRN.*/'),    0, 'boolean expression parser: regex does not match';
+    is $parser->evaluate_boolean_expression('foo + 2 == bar'),                               1, 'boolean expression parser: accessing variables, equal';
+    is $parser->evaluate_boolean_expression('foo + 3 == bar'),                               0, 'boolean expression parser: accessing variables, not equal';
 }
 
 {

+ 54 - 17
xs/src/libslic3r/PlaceholderParser.cpp

@@ -361,6 +361,13 @@ namespace client
             out = self.b();
         }
 
+        static void evaluate_boolean_to_string(expr &self, std::string &out)
+        {
+            if (self.type != TYPE_BOOL)
+                self.throw_exception("Not a boolean expression");
+            out = self.b() ? "true" : "false";
+        }
+
         // Is lhs==rhs? Store the result into lhs.
         static void compare_op(expr &lhs, expr &rhs, char op)
         {
@@ -452,18 +459,23 @@ namespace client
     }
 
     struct MyContext {
-        const PlaceholderParser *pp = nullptr;
-        const DynamicConfig     *config_override = nullptr;
-        const size_t             current_extruder_id = 0;
+        const DynamicConfig     *config                 = nullptr;
+        const DynamicConfig     *config_override        = nullptr;
+        size_t                   current_extruder_id    = 0;
+        // If false, the macro_processor will evaluate a full macro.
+        // If true, the macro processor will evaluate just a boolean condition using the full expressive power of the macro processor.
+        bool                     just_boolean_expression = false;
         std::string              error_message;
 
+        static void             evaluate_full_macro(const MyContext *ctx, bool &result) { result = ! ctx->just_boolean_expression; }
+
         const ConfigOption*     resolve_symbol(const std::string &opt_key) const
         {
             const ConfigOption *opt = nullptr;
             if (config_override != nullptr)
                 opt = config_override->option(opt_key);
             if (opt == nullptr)
-                opt = pp->option(opt_key);
+                opt = config->option(opt_key);
             return opt;
         }
 
@@ -734,13 +746,13 @@ namespace client
     };
 
     ///////////////////////////////////////////////////////////////////////////
-    //  Our calculator grammar
+    //  Our macro_processor grammar
     ///////////////////////////////////////////////////////////////////////////
     // Inspired by the C grammar rules https://www.lysator.liu.se/c/ANSI-C-grammar-y.html
     template <typename Iterator>
-    struct calculator : qi::grammar<Iterator, std::string(const MyContext*), spirit::ascii::space_type>
+    struct macro_processor : qi::grammar<Iterator, std::string(const MyContext*), qi::locals<bool>, spirit::ascii::space_type>
     {
-        calculator() : calculator::base_type(start)
+        macro_processor() : macro_processor::base_type(start)
         {
             using namespace qi::labels;
             qi::alpha_type              alpha;
@@ -772,7 +784,13 @@ namespace client
             // Starting symbol of the grammer.
             // The leading eps is required by the "expectation point" operator ">".
             // Without it, some of the errors would not trigger the error handler.
-            start = eps > text_block(_r1);
+            // Also the start symbol switches between the "full macro syntax" and a "boolean expression only",
+            // depending on the context->just_boolean_expression flag. This way a single static expression parser
+            // could serve both purposes.
+            start = eps[px::bind(&MyContext::evaluate_full_macro, _r1, _a)] >
+                (       eps(_a==true) > text_block(_r1) [_val=_1]
+                    |   bool_expr(_r1) [ px::bind(&expr<Iterator>::evaluate_boolean_to_string, _1, _val) ]
+                );
             start.name("start");
             qi::on_error<qi::fail>(start, px::bind(&MyContext::process_error_message<Iterator>, _r1, _4, _1, _2, _3));
 
@@ -944,7 +962,7 @@ namespace client
         }
 
         // The start of the grammar.
-        qi::rule<Iterator, std::string(const MyContext*), spirit::ascii::space_type> start;
+        qi::rule<Iterator, std::string(const MyContext*), qi::locals<bool>, spirit::ascii::space_type> start;
         // A free-form text.
         qi::rule<Iterator, std::string(), spirit::ascii::space_type> text;
         // A free-form text, possibly empty, possibly containing macro expansions.
@@ -979,24 +997,23 @@ namespace client
     };
 }
 
-std::string PlaceholderParser::process(const std::string &templ, unsigned int current_extruder_id, const DynamicConfig *config_override) const
+static std::string process_macro(const std::string &templ, client::MyContext &context)
 {
     typedef std::string::const_iterator iterator_type;
-    typedef client::calculator<iterator_type> calculator;
+    typedef client::macro_processor<iterator_type> macro_processor;
 
     // Our whitespace skipper.
     spirit::ascii::space_type   space;
-    // Our grammar.
-    calculator                  calc;
+    // Our grammar, statically allocated inside the method, meaning it will be allocated the first time
+    // PlaceholderParser::process() runs.
+    //FIXME this kind of initialization is not thread safe!
+    static macro_processor      macro_processor_instance;
     // Iterators over the source template.
     std::string::const_iterator iter = templ.begin();
     std::string::const_iterator end  = templ.end();
     // Accumulator for the processed template.
     std::string                 output;
-    client::MyContext context;
-    context.pp = this;
-    context.config_override = config_override;
-    bool res = phrase_parse(iter, end, calc(&context), space, output);
+    bool res = phrase_parse(iter, end, macro_processor_instance(&context), space, output);
     if (! context.error_message.empty()) {
         if (context.error_message.back() != '\n' && context.error_message.back() != '\r')
             context.error_message += '\n';
@@ -1005,4 +1022,24 @@ std::string PlaceholderParser::process(const std::string &templ, unsigned int cu
     return output;
 }
 
+std::string PlaceholderParser::process(const std::string &templ, unsigned int current_extruder_id, const DynamicConfig *config_override) const
+{
+    client::MyContext context;
+    context.config              = &this->config();
+    context.config_override     = config_override;
+    context.current_extruder_id = current_extruder_id;
+    return process_macro(templ, context);
+}
+
+// Evaluate a boolean expression using the full expressive power of the PlaceholderParser boolean expression syntax.
+// Throws std::runtime_error on syntax or runtime error.
+bool PlaceholderParser::evaluate_boolean_expression(const std::string &templ, const DynamicConfig &config)
+{
+    client::MyContext context;
+    context.config                  = &config;
+    // Let the macro processor parse just a boolean expression, not the full macro language.
+    context.just_boolean_expression = true;
+    return process_macro(templ, context) == "true";
+}
+
 }

+ 8 - 2
xs/src/libslic3r/PlaceholderParser.hpp

@@ -26,11 +26,17 @@ public:
     void set(const std::string &key, double value)              { this->set(key, new ConfigOptionFloat(value)); }
     void set(const std::string &key, const std::vector<std::string> &values) { this->set(key, new ConfigOptionStrings(values)); }
     void set(const std::string &key, ConfigOption *opt)         { m_config.set_key_value(key, opt); }
-    const ConfigOption* option(const std::string &key) const    { return m_config.option(key); }
+    const DynamicConfig&    config() const                      { return m_config; }
+    const ConfigOption*     option(const std::string &key) const { return m_config.option(key); }
 
-    // Fill in the template.
+    // Fill in the template using a macro processing language.
+    // Throws std::runtime_error on syntax or runtime error.
     std::string process(const std::string &templ, unsigned int current_extruder_id, const DynamicConfig *config_override = nullptr) const;
     
+    // Evaluate a boolean expression using the full expressive power of the PlaceholderParser boolean expression syntax.
+    // Throws std::runtime_error on syntax or runtime error.
+    static bool evaluate_boolean_expression(const std::string &templ, const DynamicConfig &config);
+
 private:
     DynamicConfig m_config;
 };

+ 9 - 0
xs/xsp/PlaceholderParser.xsp

@@ -21,4 +21,13 @@
                 croak(e.what());
             }
         %};
+
+    bool evaluate_boolean_expression(const char *str) const
+        %code%{
+            try {
+                RETVAL = THIS->evaluate_boolean_expression(str, THIS->config());
+            } catch (std::exception& e) {
+                croak(e.what());
+            }
+        %};        
 };