From a402b1b83dced4a8e6a23ff1c6ac0a8e82a5a63a Mon Sep 17 00:00:00 2001 From: bubnikv Date: Tue, 19 Dec 2017 16:48:14 +0100 Subject: [PATCH] Implemented <,>,<=,>=,or,and,||,&& operators. --- t/custom_gcode.t | 26 +++- xs/src/libslic3r/PlaceholderParser.cpp | 181 ++++++++++++++++++------- xs/src/libslic3r/PlaceholderParser.hpp | 2 +- xs/src/slic3r/GUI/Preset.cpp | 23 +++- xs/src/slic3r/GUI/Preset.hpp | 4 +- 5 files changed, 178 insertions(+), 58 deletions(-) diff --git a/t/custom_gcode.t b/t/custom_gcode.t index 75ce0b868..5d3602483 100644 --- a/t/custom_gcode.t +++ b/t/custom_gcode.t @@ -1,4 +1,4 @@ -use Test::More tests => 55; +use Test::More tests => 71; use strict; use warnings; @@ -47,9 +47,13 @@ use Slic3r::Test; { my $parser = Slic3r::GCode::PlaceholderParser->new; - $parser->apply_config(my $config = Slic3r::Config::new_from_defaults); + my $config = Slic3r::Config::new_from_defaults; + $config->set('printer_notes', ' PRINTER_VENDOR_PRUSA3D PRINTER_MODEL_MK2 '); + $config->set('nozzle_diameter', [0.6, 0.6, 0.6, 0.6]); + $parser->apply_config($config); $parser->set('foo' => 0); $parser->set('bar' => 2); + $parser->set('num_extruders' => 4); is $parser->process('[temperature_[foo]]'), $config->temperature->[0], "nested config options (legacy syntax)"; @@ -75,6 +79,24 @@ use Slic3r::Test; 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'; + + is $parser->evaluate_boolean_expression('(12 == 12) and (13 != 14)'), 1, 'boolean expression parser: (12 == 12) and (13 != 14)'; + is $parser->evaluate_boolean_expression('(12 == 12) && (13 != 14)'), 1, 'boolean expression parser: (12 == 12) && (13 != 14)'; + is $parser->evaluate_boolean_expression('(12 == 12) or (13 == 14)'), 1, 'boolean expression parser: (12 == 12) or (13 == 14)'; + is $parser->evaluate_boolean_expression('(12 == 12) || (13 == 14)'), 1, 'boolean expression parser: (12 == 12) || (13 == 14)'; + is $parser->evaluate_boolean_expression('(12 == 12) and not (13 == 14)'), 1, 'boolean expression parser: (12 == 12) and not (13 == 14)'; + is $parser->evaluate_boolean_expression('(12 == 12) ? (1 - 1 == 0) : (2 * 2 == 3)'), 1, 'boolean expression parser: ternary true'; + is $parser->evaluate_boolean_expression('(12 == 21/2) ? (1 - 1 == 0) : (2 * 2 == 3)'), 0, 'boolean expression parser: ternary false'; + is $parser->evaluate_boolean_expression('(12 == 13) ? (1 - 1 == 3) : (2 * 2 == 4)"'), 1, 'boolean expression parser: ternary false'; + is $parser->evaluate_boolean_expression('(12 == 2 * 6) ? (1 - 1 == 3) : (2 * 2 == 4)"'), 0, 'boolean expression parser: ternary true'; + is $parser->evaluate_boolean_expression('12 < 3'), 0, 'boolean expression parser: lower than - false'; + is $parser->evaluate_boolean_expression('12 < 22'), 1, 'boolean expression parser: lower than - true'; + is $parser->evaluate_boolean_expression('12 > 3'), 1, 'boolean expression parser: lower than - true'; + is $parser->evaluate_boolean_expression('12 > 22'), 0, 'boolean expression parser: lower than - false'; + + is $parser->evaluate_boolean_expression('printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK2.*/ and nozzle_diameter[0]==0.6 and num_extruders>1'), 1, 'complex expression'; + is $parser->evaluate_boolean_expression('printer_notes=~/.*PRINTER_VEwerfNDOR_PRUSA3D.*/ or printer_notes=~/.*PRINTertER_MODEL_MK2.*/ or (nozzle_diameter[0]==0.6 and num_extruders>1)'), 1, 'complex expression2'; + is $parser->evaluate_boolean_expression('printer_notes=~/.*PRINTER_VEwerfNDOR_PRUSA3D.*/ or printer_notes=~/.*PRINTertER_MODEL_MK2.*/ or (nozzle_diameter[0]==0.3 and num_extruders>1)'), 0, 'complex expression3'; } { diff --git a/xs/src/libslic3r/PlaceholderParser.cpp b/xs/src/libslic3r/PlaceholderParser.cpp index 0b33304a8..027ef4310 100644 --- a/xs/src/libslic3r/PlaceholderParser.cpp +++ b/xs/src/libslic3r/PlaceholderParser.cpp @@ -369,29 +369,50 @@ namespace client } // Is lhs==rhs? Store the result into lhs. - static void compare_op(expr &lhs, expr &rhs, char op) + static void compare_op(expr &lhs, expr &rhs, char op, bool invert) { bool value = false; if ((lhs.type == TYPE_INT || lhs.type == TYPE_DOUBLE) && (rhs.type == TYPE_INT || rhs.type == TYPE_DOUBLE)) { // Both types are numeric. - value = (lhs.type == TYPE_DOUBLE || rhs.type == TYPE_DOUBLE) ? - (lhs.as_d() == rhs.as_d()) : (lhs.i() == rhs.i()); + switch (op) { + case '=': + value = (lhs.type == TYPE_DOUBLE || rhs.type == TYPE_DOUBLE) ? + (std::abs(lhs.as_d() - rhs.as_d()) < 1e-8) : (lhs.i() == rhs.i()); + break; + case '<': + value = (lhs.type == TYPE_DOUBLE || rhs.type == TYPE_DOUBLE) ? + (lhs.as_d() < rhs.as_d()) : (lhs.i() < rhs.i()); + break; + case '>': + default: + value = (lhs.type == TYPE_DOUBLE || rhs.type == TYPE_DOUBLE) ? + (lhs.as_d() > rhs.as_d()) : (lhs.i() > rhs.i()); + break; + } } else if (lhs.type == TYPE_BOOL && rhs.type == TYPE_BOOL) { // Both type are bool. + if (op != '=') + boost::throw_exception(qi::expectation_failure( + lhs.it_range.begin(), rhs.it_range.end(), spirit::info("*Cannot compare the types."))); value = lhs.b() == rhs.b(); } else if (lhs.type == TYPE_STRING || rhs.type == TYPE_STRING) { // One type is string, the other could be converted to string. - value = lhs.to_string() == rhs.to_string(); + value = (op == '=') ? (lhs.to_string() == rhs.to_string()) : + (op == '<') ? (lhs.to_string() < rhs.to_string()) : (lhs.to_string() > rhs.to_string()); } else { boost::throw_exception(qi::expectation_failure( lhs.it_range.begin(), rhs.it_range.end(), spirit::info("*Cannot compare the types."))); } lhs.type = TYPE_BOOL; - lhs.data.b = (op == '=') ? value : !value; + lhs.data.b = invert ? ! value : value; } - static void equal (expr &lhs, expr &rhs) { compare_op(lhs, rhs, '='); } - static void not_equal(expr &lhs, expr &rhs) { compare_op(lhs, rhs, '!'); } + static void equal (expr &lhs, expr &rhs) { compare_op(lhs, rhs, '=', false); } + static void not_equal(expr &lhs, expr &rhs) { compare_op(lhs, rhs, '=', true ); } + static void lower (expr &lhs, expr &rhs) { compare_op(lhs, rhs, '<', false); } + static void greater (expr &lhs, expr &rhs) { compare_op(lhs, rhs, '>', false); } + static void leq (expr &lhs, expr &rhs) { compare_op(lhs, rhs, '>', true ); } + static void geq (expr &lhs, expr &rhs) { compare_op(lhs, rhs, '<', true ); } static void regex_op(expr &lhs, boost::iterator_range &rhs, char op) { @@ -421,6 +442,32 @@ namespace client static void regex_matches (expr &lhs, boost::iterator_range &rhs) { return regex_op(lhs, rhs, '='); } static void regex_doesnt_match(expr &lhs, boost::iterator_range &rhs) { return regex_op(lhs, rhs, '!'); } + static void logical_op(expr &lhs, expr &rhs, char op) + { + bool value = false; + if (lhs.type == TYPE_BOOL && rhs.type == TYPE_BOOL) { + value = (op == '|') ? (lhs.b() || rhs.b()) : (lhs.b() && rhs.b()); + } else { + boost::throw_exception(qi::expectation_failure( + lhs.it_range.begin(), rhs.it_range.end(), spirit::info("*Cannot apply logical operation to non-boolean operators."))); + } + lhs.type = TYPE_BOOL; + lhs.data.b = value; + } + static void logical_or (expr &lhs, expr &rhs) { logical_op(lhs, rhs, '|'); } + static void logical_and(expr &lhs, expr &rhs) { logical_op(lhs, rhs, '&'); } + + static void ternary_op(expr &lhs, expr &rhs1, expr &rhs2) + { + bool value = false; + if (lhs.type != TYPE_BOOL) + lhs.throw_exception("Not a boolean expression"); + if (lhs.b()) + lhs = std::move(rhs1); + else + lhs = std::move(rhs2); + } + static void set_if(bool &cond, bool ¬_yet_consumed, std::string &str_in, std::string &str_out) { if (cond && not_yet_consumed) { @@ -789,7 +836,7 @@ namespace client // 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) ] + | conditional_expression(_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)); @@ -852,34 +899,54 @@ namespace client raw[lexeme[(alpha | '_') >> *(alnum | '_')]]; identifier.name("identifier"); - bool_expr = - additive_expression(_r1) [_val = _1] - >> *( ("==" > additive_expression(_r1) ) [px::bind(&expr::equal, _val, _1)] - | ("!=" > additive_expression(_r1) ) [px::bind(&expr::not_equal, _val, _1)] - | ("<>" > additive_expression(_r1) ) [px::bind(&expr::not_equal, _val, _1)] - | ("=~" > regular_expression ) [px::bind(&expr::regex_matches, _val, _1)] - | ("!~" > regular_expression ) [px::bind(&expr::regex_doesnt_match, _val, _1)] + conditional_expression = + logical_or_expression(_r1) [_val = _1] + >> -('?' > conditional_expression(_r1) > ':' > conditional_expression(_r1)) [px::bind(&expr::ternary_op, _val, _1, _2)]; + + logical_or_expression = + logical_and_expression(_r1) [_val = _1] + >> *( ((kw["or"] | "||") > logical_and_expression(_r1) ) [px::bind(&expr::logical_or, _val, _1)] ); + + logical_and_expression = + equality_expression(_r1) [_val = _1] + >> *( ((kw["and"] | "&&") > equality_expression(_r1) ) [px::bind(&expr::logical_and, _val, _1)] ); + + equality_expression = + relational_expression(_r1) [_val = _1] + >> *( ("==" > relational_expression(_r1) ) [px::bind(&expr::equal, _val, _1)] + | ("!=" > relational_expression(_r1) ) [px::bind(&expr::not_equal, _val, _1)] + | ("<>" > relational_expression(_r1) ) [px::bind(&expr::not_equal, _val, _1)] + | ("=~" > regular_expression ) [px::bind(&expr::regex_matches, _val, _1)] + | ("!~" > regular_expression ) [px::bind(&expr::regex_doesnt_match, _val, _1)] ); - bool_expr.name("bool expression"); + equality_expression.name("bool expression"); // Evaluate a boolean expression stored as expr into a boolean value. - // Throw if the bool_expr does not produce a expr of boolean type. - bool_expr_eval = bool_expr(_r1) [ px::bind(&expr::evaluate_boolean, _1, _val) ]; + // Throw if the equality_expression does not produce a expr of boolean type. + bool_expr_eval = conditional_expression(_r1) [ px::bind(&expr::evaluate_boolean, _1, _val) ]; bool_expr_eval.name("bool_expr_eval"); + relational_expression = + additive_expression(_r1) [_val = _1] + >> *( (lit('<') > additive_expression(_r1) ) [px::bind(&expr::lower, _val, _1)] + | (lit('>') > additive_expression(_r1) ) [px::bind(&expr::greater, _val, _1)] + | ("<=" > additive_expression(_r1) ) [px::bind(&expr::leq, _val, _1)] + | (">=" > additive_expression(_r1) ) [px::bind(&expr::geq, _val, _1)] + ); + additive_expression = - term(_r1) [_val = _1] - >> *( (lit('+') > term(_r1) ) [_val += _1] - | (lit('-') > term(_r1) ) [_val -= _1] + multiplicative_expression(_r1) [_val = _1] + >> *( (lit('+') > multiplicative_expression(_r1) ) [_val += _1] + | (lit('-') > multiplicative_expression(_r1) ) [_val -= _1] ); additive_expression.name("additive_expression"); - term = - factor(_r1) [_val = _1] - >> *( (lit('*') > factor(_r1) ) [_val *= _1] - | (lit('/') > factor(_r1) ) [_val /= _1] + multiplicative_expression = + unary_expression(_r1) [_val = _1] + >> *( (lit('*') > unary_expression(_r1) ) [_val *= _1] + | (lit('/') > unary_expression(_r1) ) [_val /= _1] ); - term.name("term"); + multiplicative_expression.name("multiplicative_expression"); struct FactorActions { static void set_start_pos(Iterator &start_pos, expr &out) @@ -899,19 +966,19 @@ namespace client static void not_(expr &value, expr &out) { out = value.unary_not(out.it_range.begin()); } }; - factor = iter_pos[px::bind(&FactorActions::set_start_pos, _1, _val)] >> ( - scalar_variable_reference(_r1) [ _val = _1 ] - | (lit('(') > additive_expression(_r1) > ')' > iter_pos) [ px::bind(&FactorActions::expr_, _1, _2, _val) ] - | (lit('-') > factor(_r1) ) [ px::bind(&FactorActions::minus_, _1, _val) ] - | (lit('+') > factor(_r1) > iter_pos) [ px::bind(&FactorActions::expr_, _1, _2, _val) ] - | ((kw["not"] | '!') > factor(_r1) > iter_pos) [ px::bind(&FactorActions::not_, _1, _val) ] - | (strict_double > iter_pos) [ px::bind(&FactorActions::double_, _1, _2, _val) ] - | (int_ > iter_pos) [ px::bind(&FactorActions::int_, _1, _2, _val) ] - | (kw[bool_] > iter_pos) [ px::bind(&FactorActions::bool_, _1, _2, _val) ] + unary_expression = iter_pos[px::bind(&FactorActions::set_start_pos, _1, _val)] >> ( + scalar_variable_reference(_r1) [ _val = _1 ] + | (lit('(') > conditional_expression(_r1) > ')' > iter_pos) [ px::bind(&FactorActions::expr_, _1, _2, _val) ] + | (lit('-') > unary_expression(_r1) ) [ px::bind(&FactorActions::minus_, _1, _val) ] + | (lit('+') > unary_expression(_r1) > iter_pos) [ px::bind(&FactorActions::expr_, _1, _2, _val) ] + | ((kw["not"] | '!') > unary_expression(_r1) > iter_pos) [ px::bind(&FactorActions::not_, _1, _val) ] + | (strict_double > iter_pos) [ px::bind(&FactorActions::double_, _1, _2, _val) ] + | (int_ > iter_pos) [ px::bind(&FactorActions::int_, _1, _2, _val) ] + | (kw[bool_] > iter_pos) [ px::bind(&FactorActions::bool_, _1, _2, _val) ] | raw[lexeme['"' > *((utf8char - char_('\\') - char_('"')) | ('\\' > char_)) > '"']] - [ px::bind(&FactorActions::string_, _1, _val) ] + [ px::bind(&FactorActions::string_, _1, _val) ] ); - factor.name("factor"); + unary_expression.name("unary_expression"); scalar_variable_reference = variable_reference(_r1)[_a=_1] >> @@ -950,17 +1017,24 @@ namespace client debug(switch_output); debug(legacy_variable_expansion); debug(identifier); - debug(bool_expr); + debug(conditional_expression); + debug(logical_or_expression); + debug(logical_and_expression); + debug(equality_expression); debug(bool_expr_eval); + debug(relational_expression); debug(additive_expression); - debug(term); - debug(factor); + debug(multiplicative_expression); + debug(unary_expression); debug(scalar_variable_reference); debug(variable_reference); debug(regular_expression); } } + // Generic expression over expr. + typedef qi::rule(const MyContext*), spirit::ascii::space_type> RuleExpression; + // The start of the grammar. qi::rule, spirit::ascii::space_type> start; // A free-form text. @@ -973,18 +1047,26 @@ namespace client qi::rule legacy_variable_expansion; // Parsed identifier name. qi::rule(), spirit::ascii::space_type> identifier; - // Math expression consisting of +- operators over terms. - qi::rule(const MyContext*), spirit::ascii::space_type> additive_expression; + // Ternary operator (?:) over logical_or_expression. + RuleExpression conditional_expression; + // Logical or over logical_and_expressions. + RuleExpression logical_or_expression; + // Logical and over relational_expressions. + RuleExpression logical_and_expression; + // <, >, <=, >= + RuleExpression relational_expression; + // Math expression consisting of +- operators over multiplicative_expressions. + RuleExpression additive_expression; + // Boolean expressions over expressions. + RuleExpression equality_expression; + // Math expression consisting of */ operators over factors. + RuleExpression multiplicative_expression; + // Number literals, functions, braced expressions, variable references, variable indexing references. + RuleExpression unary_expression; // Rule to capture a regular expression enclosed in //. qi::rule(), spirit::ascii::space_type> regular_expression; - // Boolean expressions over expressions. - qi::rule(const MyContext*), spirit::ascii::space_type> bool_expr; // Evaluate boolean expression into bool. qi::rule bool_expr_eval; - // Math expression consisting of */ operators over factors. - qi::rule(const MyContext*), spirit::ascii::space_type> term; - // Number literals, functions, braced expressions, variable references, variable indexing references. - qi::rule(const MyContext*), spirit::ascii::space_type> factor; // Reference of a scalar variable, or reference to a field of a vector variable. qi::rule(const MyContext*), qi::locals, int>, spirit::ascii::space_type> scalar_variable_reference; // Rule to translate an identifier to a ConfigOption, or to fail. @@ -1033,10 +1115,11 @@ std::string PlaceholderParser::process(const std::string &templ, unsigned int cu // 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) +bool PlaceholderParser::evaluate_boolean_expression(const std::string &templ, const DynamicConfig &config, const DynamicConfig *config_override) { client::MyContext context; context.config = &config; + context.config_override = config_override; // 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 ec2b837ad..4e0aa9ee2 100644 --- a/xs/src/libslic3r/PlaceholderParser.hpp +++ b/xs/src/libslic3r/PlaceholderParser.hpp @@ -35,7 +35,7 @@ public: // 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); + static bool evaluate_boolean_expression(const std::string &templ, const DynamicConfig &config, const DynamicConfig *config_override = nullptr); private: DynamicConfig m_config; diff --git a/xs/src/slic3r/GUI/Preset.cpp b/xs/src/slic3r/GUI/Preset.cpp index a132af133..adc996030 100644 --- a/xs/src/slic3r/GUI/Preset.cpp +++ b/xs/src/slic3r/GUI/Preset.cpp @@ -142,12 +142,12 @@ std::string Preset::label() const return this->name + (this->is_dirty ? g_suffix_modified : ""); } -bool Preset::is_compatible_with_printer(const Preset &active_printer) const +bool Preset::is_compatible_with_printer(const Preset &active_printer, const DynamicPrintConfig *extra_config) const { auto *condition = dynamic_cast(this->config.option("compatible_printers_condition")); if (condition != nullptr && ! condition->value.empty()) { try { - return PlaceholderParser::evaluate_boolean_expression(condition->value, active_printer.config); + return PlaceholderParser::evaluate_boolean_expression(condition->value, active_printer.config, extra_config); } catch (const std::runtime_error &err) { //FIXME in case of an error, return "compatible with everything". printf("Preset::is_compatible_with_printer - parsing error of compatible_printers_condition %s:\n%s\n", active_printer.name.c_str(), err.what()); @@ -161,9 +161,18 @@ bool Preset::is_compatible_with_printer(const Preset &active_printer) const compatible_printers->values.end(); } -bool Preset::update_compatible_with_printer(const Preset &active_printer) +bool Preset::is_compatible_with_printer(const Preset &active_printer) const { - return this->is_compatible = is_compatible_with_printer(active_printer); + DynamicPrintConfig config; + config.set_key_value("printer_preset", new ConfigOptionString(active_printer.name)); + config.set_key_value("num_extruders", new ConfigOptionInt( + (int)static_cast(active_printer.config.option("nozzle_diameter"))->values.size())); + return this->is_compatible_with_printer(active_printer, &config); +} + +bool Preset::update_compatible_with_printer(const Preset &active_printer, const DynamicPrintConfig *extra_config) +{ + return this->is_compatible = is_compatible_with_printer(active_printer, extra_config); } const std::vector& Preset::print_options() @@ -408,11 +417,15 @@ void PresetCollection::set_default_suppressed(bool default_suppressed) void PresetCollection::update_compatible_with_printer(const Preset &active_printer, bool select_other_if_incompatible) { + DynamicPrintConfig config; + config.set_key_value("printer_preset", new ConfigOptionString(active_printer.name)); + config.set_key_value("num_extruders", new ConfigOptionInt( + (int)static_cast(active_printer.config.option("nozzle_diameter"))->values.size())); for (size_t idx_preset = 1; idx_preset < m_presets.size(); ++ idx_preset) { bool selected = idx_preset == m_idx_selected; Preset &preset_selected = m_presets[idx_preset]; Preset &preset_edited = selected ? m_edited_preset : preset_selected; - if (! preset_edited.update_compatible_with_printer(active_printer) && + if (! preset_edited.update_compatible_with_printer(active_printer, &config) && selected && select_other_if_incompatible) m_idx_selected = (size_t)-1; if (selected) diff --git a/xs/src/slic3r/GUI/Preset.hpp b/xs/src/slic3r/GUI/Preset.hpp index 36bc0b335..bfb7d6e20 100644 --- a/xs/src/slic3r/GUI/Preset.hpp +++ b/xs/src/slic3r/GUI/Preset.hpp @@ -79,9 +79,11 @@ public: void set_dirty(bool dirty = true) { this->is_dirty = dirty; } void reset_dirty() { this->is_dirty = false; } + bool is_compatible_with_printer(const Preset &active_printer, const DynamicPrintConfig *extra_config) const; bool is_compatible_with_printer(const Preset &active_printer) const; + // Mark this preset as compatible if it is compatible with active_printer. - bool update_compatible_with_printer(const Preset &active_printer); + bool update_compatible_with_printer(const Preset &active_printer, const DynamicPrintConfig *extra_config); // Resize the extruder specific fields, initialize them with the content of the 1st extruder. void set_num_extruders(unsigned int n) { set_num_extruders(this->config, n); }