From bb61de83795b10f77107e81384b62405ba2bbc89 Mon Sep 17 00:00:00 2001 From: bubnikv Date: Mon, 18 Dec 2017 12:14:09 +0100 Subject: [PATCH] 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. --- lib/Slic3r.pm | 4 ++ t/custom_gcode.t | 10 +++- xs/src/libslic3r/PlaceholderParser.cpp | 71 ++++++++++++++++++++------ xs/src/libslic3r/PlaceholderParser.hpp | 10 +++- xs/xsp/PlaceholderParser.xsp | 9 ++++ 5 files changed, 84 insertions(+), 20 deletions(-) diff --git a/lib/Slic3r.pm b/lib/Slic3r.pm index 22a6ee3891..66039ddf02 100644 --- a/lib/Slic3r.pm +++ b/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) = @_; @_ = (); diff --git a/t/custom_gcode.t b/t/custom_gcode.t index 1ccf738aa5..75ce0b868e 100644 --- a/t/custom_gcode.t +++ b/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'; } { diff --git a/xs/src/libslic3r/PlaceholderParser.cpp b/xs/src/libslic3r/PlaceholderParser.cpp index b9f6b803e5..0b33304a80 100644 --- a/xs/src/libslic3r/PlaceholderParser.cpp +++ b/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 - struct calculator : qi::grammar + struct macro_processor : qi::grammar, 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::evaluate_boolean_to_string, _1, _val) ] + ); start.name("start"); qi::on_error(start, px::bind(&MyContext::process_error_message, _r1, _4, _1, _2, _3)); @@ -944,7 +962,7 @@ namespace client } // The start of the grammar. - qi::rule start; + qi::rule, spirit::ascii::space_type> start; // A free-form text. qi::rule 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 calculator; + typedef client::macro_processor 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"; +} + } diff --git a/xs/src/libslic3r/PlaceholderParser.hpp b/xs/src/libslic3r/PlaceholderParser.hpp index 25264d461f..ec2b837ad6 100644 --- a/xs/src/libslic3r/PlaceholderParser.hpp +++ b/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 &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; }; diff --git a/xs/xsp/PlaceholderParser.xsp b/xs/xsp/PlaceholderParser.xsp index ab06450f9b..244f89cf88 100644 --- a/xs/xsp/PlaceholderParser.xsp +++ b/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()); + } + %}; };