diff options
-rw-r--r-- | lib/Slic3r.pm | 4 | ||||
-rw-r--r-- | t/custom_gcode.t | 10 | ||||
-rw-r--r-- | xs/src/libslic3r/PlaceholderParser.cpp | 71 | ||||
-rw-r--r-- | xs/src/libslic3r/PlaceholderParser.hpp | 10 | ||||
-rw-r--r-- | xs/xsp/PlaceholderParser.xsp | 9 |
5 files changed, 84 insertions, 20 deletions
diff --git a/lib/Slic3r.pm b/lib/Slic3r.pm index 22a6ee389..66039ddf0 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 1ccf738aa..75ce0b868 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 b9f6b803e..0b33304a8 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 <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"; +} + } diff --git a/xs/src/libslic3r/PlaceholderParser.hpp b/xs/src/libslic3r/PlaceholderParser.hpp index 25264d461..ec2b837ad 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<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; }; diff --git a/xs/xsp/PlaceholderParser.xsp b/xs/xsp/PlaceholderParser.xsp index ab06450f9..244f89cf8 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()); + } + %}; }; |