From 5b115b79723fb7bf16f303df8bef6d8c6fa766d2 Mon Sep 17 00:00:00 2001 From: Vojtech Bubnik Date: Fri, 24 Mar 2023 13:55:48 +0100 Subject: [PATCH] PlaceholderParser: Implemented skipping of inactive if / else / endif and ternary operator branches, thus missing variables or addressing outside of the variable range in an inactive branch will not trigger an error. --- src/libslic3r/PlaceholderParser.cpp | 517 +++++++++++++------- tests/libslic3r/test_placeholder_parser.cpp | 32 +- 2 files changed, 370 insertions(+), 179 deletions(-) diff --git a/src/libslic3r/PlaceholderParser.cpp b/src/libslic3r/PlaceholderParser.cpp index 94a1259b6..06a4d3960 100644 --- a/src/libslic3r/PlaceholderParser.cpp +++ b/src/libslic3r/PlaceholderParser.cpp @@ -177,6 +177,7 @@ namespace client int index { -1 }; boost::iterator_range it_range; + bool empty() const { return opt == nullptr; } bool has_index() const { return index != -1; } }; @@ -292,6 +293,9 @@ namespace client { std::string out; switch (this->type()) { + case TYPE_EMPTY: + // Inside an if / else block to be skipped. + break; case TYPE_BOOL: out = this->b() ? "true" : "false"; break; case TYPE_INT: out = std::to_string(this->i()); break; case TYPE_DOUBLE: @@ -321,6 +325,9 @@ namespace client expr unary_minus(const Iterator start_pos) const { switch (this->type()) { + case TYPE_EMPTY: + // Inside an if / else block to be skipped. + return expr(); case TYPE_INT : return expr(- this->i(), start_pos, this->it_range.end()); case TYPE_DOUBLE: @@ -336,6 +343,9 @@ namespace client expr unary_integer(const Iterator start_pos) const { switch (this->type()) { + case TYPE_EMPTY: + // Inside an if / else block to be skipped. + return expr(); case TYPE_INT: return expr(this->i(), start_pos, this->it_range.end()); case TYPE_DOUBLE: @@ -351,6 +361,9 @@ namespace client expr round(const Iterator start_pos) const { switch (this->type()) { + case TYPE_EMPTY: + // Inside an if / else block to be skipped. + return expr(); case TYPE_INT: return expr(this->i(), start_pos, this->it_range.end()); case TYPE_DOUBLE: @@ -366,6 +379,9 @@ namespace client expr unary_not(const Iterator start_pos) const { switch (this->type()) { + case TYPE_EMPTY: + // Inside an if / else block to be skipped. + return expr(); case TYPE_BOOL: return expr(! this->b(), start_pos, this->it_range.end()); default: @@ -378,7 +394,9 @@ namespace client expr &operator+=(const expr &rhs) { - if (this->type() == TYPE_STRING) { + if (this->type() == TYPE_EMPTY) { + // Inside an if / else block to be skipped. + } else if (this->type() == TYPE_STRING) { // Convert the right hand side to string and append. *m_data.s += rhs.to_string(); } else if (rhs.type() == TYPE_STRING) { @@ -399,72 +417,98 @@ namespace client expr &operator-=(const expr &rhs) { - const char *err_msg = "Cannot subtract non-numeric types."; - this->throw_if_not_numeric(err_msg); - rhs.throw_if_not_numeric(err_msg); - if (this->type() == TYPE_DOUBLE || rhs.type() == TYPE_DOUBLE) - this->set_d_lite(this->as_d() - rhs.as_d()); - else - m_data.i -= rhs.i(); - this->it_range = boost::iterator_range(this->it_range.begin(), rhs.it_range.end()); + if (this->type() == TYPE_EMPTY) { + // Inside an if / else block to be skipped. + this->reset(); + } else { + const char *err_msg = "Cannot subtract non-numeric types."; + this->throw_if_not_numeric(err_msg); + rhs.throw_if_not_numeric(err_msg); + if (this->type() == TYPE_DOUBLE || rhs.type() == TYPE_DOUBLE) + this->set_d_lite(this->as_d() - rhs.as_d()); + else + m_data.i -= rhs.i(); + this->it_range = boost::iterator_range(this->it_range.begin(), rhs.it_range.end()); + } return *this; } expr &operator*=(const expr &rhs) { - const char *err_msg = "Cannot multiply with non-numeric type."; - this->throw_if_not_numeric(err_msg); - rhs.throw_if_not_numeric(err_msg); - if (this->type() == TYPE_DOUBLE || rhs.type() == TYPE_DOUBLE) - this->set_d_lite(this->as_d() * rhs.as_d()); - else - m_data.i *= rhs.i(); - this->it_range = boost::iterator_range(this->it_range.begin(), rhs.it_range.end()); + if (this->type() == TYPE_EMPTY) { + // Inside an if / else block to be skipped. + this->reset(); + } else { + const char *err_msg = "Cannot multiply with non-numeric type."; + this->throw_if_not_numeric(err_msg); + rhs.throw_if_not_numeric(err_msg); + if (this->type() == TYPE_DOUBLE || rhs.type() == TYPE_DOUBLE) + this->set_d_lite(this->as_d() * rhs.as_d()); + else + m_data.i *= rhs.i(); + this->it_range = boost::iterator_range(this->it_range.begin(), rhs.it_range.end()); + } return *this; } expr &operator/=(const expr &rhs) { - this->throw_if_not_numeric("Cannot divide a non-numeric type."); - rhs.throw_if_not_numeric("Cannot divide with a non-numeric type."); - if (rhs.type() == TYPE_INT ? (rhs.i() == 0) : (rhs.d() == 0.)) - rhs.throw_exception("Division by zero"); - if (this->type() == TYPE_DOUBLE || rhs.type() == TYPE_DOUBLE) - this->set_d_lite(this->as_d() / rhs.as_d()); - else - m_data.i /= rhs.i(); - this->it_range = boost::iterator_range(this->it_range.begin(), rhs.it_range.end()); + if (this->type() == TYPE_EMPTY) { + // Inside an if / else block to be skipped. + this->reset(); + } else { + this->throw_if_not_numeric("Cannot divide a non-numeric type."); + rhs.throw_if_not_numeric("Cannot divide with a non-numeric type."); + if (rhs.type() == TYPE_INT ? (rhs.i() == 0) : (rhs.d() == 0.)) + rhs.throw_exception("Division by zero"); + if (this->type() == TYPE_DOUBLE || rhs.type() == TYPE_DOUBLE) + this->set_d_lite(this->as_d() / rhs.as_d()); + else + m_data.i /= rhs.i(); + this->it_range = boost::iterator_range(this->it_range.begin(), rhs.it_range.end()); + } return *this; } expr &operator%=(const expr &rhs) { - this->throw_if_not_numeric("Cannot divide a non-numeric type."); - rhs.throw_if_not_numeric("Cannot divide with a non-numeric type."); - if (rhs.type() == TYPE_INT ? (rhs.i() == 0) : (rhs.d() == 0.)) - rhs.throw_exception("Division by zero"); - if (this->type() == TYPE_DOUBLE || rhs.type() == TYPE_DOUBLE) - this->set_d_lite(std::fmod(this->as_d(), rhs.as_d())); - else - m_data.i %= rhs.i(); - this->it_range = boost::iterator_range(this->it_range.begin(), rhs.it_range.end()); + if (this->type() == TYPE_EMPTY) { + // Inside an if / else block to be skipped. + this->reset(); + } else { + this->throw_if_not_numeric("Cannot divide a non-numeric type."); + rhs.throw_if_not_numeric("Cannot divide with a non-numeric type."); + if (rhs.type() == TYPE_INT ? (rhs.i() == 0) : (rhs.d() == 0.)) + rhs.throw_exception("Division by zero"); + if (this->type() == TYPE_DOUBLE || rhs.type() == TYPE_DOUBLE) + this->set_d_lite(std::fmod(this->as_d(), rhs.as_d())); + else + m_data.i %= rhs.i(); + this->it_range = boost::iterator_range(this->it_range.begin(), rhs.it_range.end()); + } return *this; } static void to_string2(expr &self, std::string &out) { - out = self.to_string(); + if (self.type() != TYPE_EMPTY) + // Not inside an if / else block to be skipped + out = self.to_string(); } static void evaluate_boolean(expr &self, bool &out) { - if (self.type() != TYPE_BOOL) - self.throw_exception("Not a boolean expression"); - out = self.b(); + if (self.type() != TYPE_EMPTY) { + // Not inside an if / else block to be skipped + if (self.type() != TYPE_BOOL) + self.throw_exception("Not a boolean expression"); + out = self.b(); + } } static void evaluate_boolean_to_string(expr &self, std::string &out) { + assert(self.type() != TYPE_EMPTY); if (self.type() != TYPE_BOOL) self.throw_exception("Not a boolean expression"); out = self.b() ? "true" : "false"; @@ -473,6 +517,9 @@ namespace client // Is lhs==rhs? Store the result into lhs. static void compare_op(expr &lhs, expr &rhs, char op, bool invert) { + if (lhs.type() == TYPE_EMPTY) + // Inside an if / else block to be skipped + return; bool value = false; if (lhs.numeric_type() && rhs.numeric_type()) { // Both types are numeric. @@ -529,6 +576,9 @@ namespace client // Store the result into param1. static void function_2params(expr ¶m1, expr ¶m2, Function2ParamsType fun) { + if (param1.type() == TYPE_EMPTY) + // Inside an if / else block to be skipped + return; throw_if_not_numeric(param1); throw_if_not_numeric(param2); if (param1.type() == TYPE_DOUBLE || param2.type() == TYPE_DOUBLE) { @@ -556,6 +606,9 @@ namespace client // Store the result into param1. static void random(expr ¶m1, expr ¶m2, std::mt19937 &rng) { + if (param1.type() == TYPE_EMPTY) + // Inside an if / else block to be skipped + return; throw_if_not_numeric(param1); throw_if_not_numeric(param2); if (param1.type() == TYPE_DOUBLE || param2.type() == TYPE_DOUBLE) @@ -569,6 +622,9 @@ namespace client template static void digits(expr ¶m1, expr ¶m2, expr ¶m3) { + if (param1.type() == TYPE_EMPTY) + // Inside an if / else block to be skipped + return; throw_if_not_numeric(param1); if (param2.type() != TYPE_INT) param2.throw_exception("digits: second parameter must be integer"); @@ -590,6 +646,9 @@ namespace client static void regex_op(const expr &lhs, boost::iterator_range &rhs, char op, expr &out) { + if (lhs.type() == TYPE_EMPTY) + // Inside an if / else block to be skipped + return; const std::string *subject = nullptr; if (lhs.type() == TYPE_STRING) { // One type is string, the other could be converted to string. @@ -618,6 +677,11 @@ namespace client } template static void one_of_test(const expr &match, const expr &pattern, expr &out) { + if (match.type() == TYPE_EMPTY) { + // Inside an if / else block to be skipped + out.reset(); + return; + } if (! out.b()) { if (match.type() != TYPE_STRING) match.throw_exception("one_of(): First parameter (the string to match against) has to be a string value"); @@ -635,6 +699,11 @@ namespace client } } static void one_of_test_regex(const expr &match, boost::iterator_range &pattern, expr &out) { + if (match.type() == TYPE_EMPTY) { + // Inside an if / else block to be skipped + out.reset(); + return; + } if (! out.b()) { if (match.type() != TYPE_STRING) match.throw_exception("one_of(): First parameter (the string to match against) has to be a string value"); @@ -644,6 +713,9 @@ namespace client static void logical_op(expr &lhs, expr &rhs, char op) { + if (lhs.type() == TYPE_EMPTY) + // Inside an if / else block to be skipped + return; bool value = false; if (lhs.type() == TYPE_BOOL && rhs.type() == TYPE_BOOL) { value = (op == '|') ? (lhs.b() || rhs.b()) : (lhs.b() && rhs.b()); @@ -656,24 +728,6 @@ namespace client 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) - { - 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) { - str_out = str_in; - not_yet_consumed = false; - } - } - void throw_exception(const char *message) const { boost::throw_exception(qi::expectation_failure( @@ -745,6 +799,33 @@ namespace client // Should the parser consider the parsed string to be a macro or a boolean expression? static bool evaluate_full_macro(const MyContext *ctx) { return ! ctx->just_boolean_expression; } + // Entering a conditional block. + static void block_enter(const MyContext *ctx, const bool condition) + { + if (ctx->skipping() || ! condition) + ++ ctx->m_depth_suppressed; + } + // Exiting a conditional block. + static void block_exit(const MyContext *ctx, const bool condition, bool ¬_yet_consumed, std::string &data_in, std::string &data_out) + { + if (ctx->skipping()) + -- ctx->m_depth_suppressed; + else if (condition && not_yet_consumed) { + data_out = std::move(data_in); + not_yet_consumed = false; + } + } + template + static void block_exit_ternary(const MyContext* ctx, const bool condition, DataType &data_in, DataType &data_out) + { + if (ctx->skipping()) + -- ctx->m_depth_suppressed; + else if (condition) + data_out = std::move(data_in); + } + // Inside a block, which is conditionally suppressed? + bool skipping() const { return m_depth_suppressed > 0; } + const ConfigOption* optptr(const t_config_option_key &opt_key) const override { const ConfigOption *opt = nullptr; @@ -758,7 +839,7 @@ namespace client } const ConfigOption* resolve_symbol(const std::string &opt_key) const { return this->optptr(opt_key); } - ConfigOption* resolve_output_symbol(const std::string &opt_key) const { + ConfigOption* resolve_output_symbol(const std::string &opt_key) const { ConfigOption *out = nullptr; if (this->config_outputs) out = this->config_outputs->optptr(opt_key, false); @@ -783,6 +864,9 @@ namespace client boost::iterator_range &opt_key, std::string &output) { + if (ctx->skipping()) + return; + std::string opt_key_str(opt_key.begin(), opt_key.end()); const ConfigOption *opt = ctx->resolve_symbol(opt_key_str); size_t idx = ctx->current_extruder_id; @@ -820,6 +904,9 @@ namespace client boost::iterator_range &opt_vector_index, std::string &output) { + if (ctx->skipping()) + return; + std::string opt_key_str(opt_key.begin(), opt_key.end()); const ConfigOption *opt = ctx->resolve_symbol(opt_key_str); if (opt == nullptr) { @@ -850,15 +937,17 @@ namespace client boost::iterator_range &opt_key, OptWithPos &output) { - const std::string key{ opt_key.begin(), opt_key.end() }; - const ConfigOption *opt = ctx->resolve_symbol(key); - if (opt == nullptr) { - opt = ctx->resolve_output_symbol(key); - if (opt == nullptr) - ctx->throw_exception("Not a variable name", opt_key); - output.writable = true; + if (! ctx->skipping()) { + const std::string key{ opt_key.begin(), opt_key.end() }; + const ConfigOption *opt = ctx->resolve_symbol(key); + if (opt == nullptr) { + opt = ctx->resolve_output_symbol(key); + if (opt == nullptr) + ctx->throw_exception("Not a variable name", opt_key); + output.writable = true; + } + output.opt = opt; } - output.opt = opt; output.it_range = opt_key; } @@ -870,12 +959,15 @@ namespace client Iterator it_end, OptWithPos &output) { - if (! opt.opt->is_vector()) - ctx->throw_exception("Cannot index a scalar variable", opt.it_range); - if (index < 0) - ctx->throw_exception("Referencing a vector variable with a negative index", opt.it_range); - output = opt; - output.index = index; + if (! ctx->skipping()) { + if (! opt.opt->is_vector()) + ctx->throw_exception("Cannot index a scalar variable", opt.it_range); + if (index < 0) + ctx->throw_exception("Referencing a vector variable with a negative index", opt.it_range); + output = opt; + output.index = index; + } else + output = opt; output.it_range.end() = it_end; } @@ -887,6 +979,9 @@ namespace client OptWithPos &opt, expr &output) { + if (ctx->skipping()) + return; + assert(opt.opt->is_scalar()); switch (opt.opt->type()) { @@ -948,6 +1043,9 @@ namespace client OptWithPos &opt, expr &output) { + if (ctx->skipping()) + return; + assert(opt.opt->is_vector()); if (! opt.has_index()) ctx->throw_exception("Referencing a vector variable when scalar is expected", opt.it_range); @@ -1102,10 +1200,12 @@ namespace client OptWithPos &opt, expr &output) { - if (opt.opt->is_vector()) - vector_element_to_expr(ctx, opt, output); - else - scalar_variable_to_expr(ctx, opt, output); + if (! ctx->skipping()) { + if (opt.opt->is_vector()) + vector_element_to_expr(ctx, opt, output); + else + scalar_variable_to_expr(ctx, opt, output); + } output.it_range = opt.it_range; } @@ -1117,7 +1217,8 @@ namespace client OptWithPos &opt, expr &output) { - if (opt.opt->is_vector()) { + if (ctx->skipping()) { + } else if (opt.opt->is_vector()) { if (! opt.has_index()) ctx->throw_exception("Referencing a vector variable when scalar is expected", opt.it_range); const ConfigOptionVectorBase *vec = static_cast(opt.opt); @@ -1145,26 +1246,26 @@ namespace client const boost::iterator_range &it_range, NewOldVariable &out) { - t_config_option_key key(std::string(it_range.begin(), it_range.end())); - if (const ConfigOption* opt = ctx->resolve_symbol(key); opt) - ctx->throw_exception("Symbol is already defined in read-only system dictionary", it_range); - if (ctx->config_outputs && ctx->config_outputs->optptr(key)) - ctx->throw_exception("Symbol is already defined as system output variable", it_range); - - bool has_global_dictionary = ctx->context_data != nullptr && ctx->context_data->global_config; - if (global_variable) { - if (! has_global_dictionary) - ctx->throw_exception("Global variables are not available in this context", it_range); - if (ctx->config_local.optptr(key)) - ctx->throw_exception("Variable name already defined in local scope", it_range); - out.opt = ctx->context_data->global_config->optptr(key); - } else { - if (has_global_dictionary && ctx->context_data->global_config->optptr(key)) - ctx->throw_exception("Variable name already defined in global scope", it_range); - out.opt = ctx->config_local.optptr(key); + if (! ctx->skipping()) { + t_config_option_key key(std::string(it_range.begin(), it_range.end())); + if (const ConfigOption* opt = ctx->resolve_symbol(key); opt) + ctx->throw_exception("Symbol is already defined in read-only system dictionary", it_range); + if (ctx->config_outputs && ctx->config_outputs->optptr(key)) + ctx->throw_exception("Symbol is already defined as system output variable", it_range); + bool has_global_dictionary = ctx->context_data != nullptr && ctx->context_data->global_config; + if (global_variable) { + if (! has_global_dictionary) + ctx->throw_exception("Global variables are not available in this context", it_range); + if (ctx->config_local.optptr(key)) + ctx->throw_exception("Variable name already defined in local scope", it_range); + out.opt = ctx->context_data->global_config->optptr(key); + } else { + if (has_global_dictionary && ctx->context_data->global_config->optptr(key)) + ctx->throw_exception("Variable name already defined in global scope", it_range); + out.opt = ctx->config_local.optptr(key); + } + out.name = std::move(key); } - - out.name = std::move(key); out.it_range = it_range; } @@ -1175,11 +1276,13 @@ namespace client OptWithPos &opt, const expr ¶m) { - check_writable(ctx, opt); - if (opt.opt->is_vector()) - vector_variable_element_assign_scalar(ctx, opt, param); - else - scalar_variable_assign_scalar(ctx, opt, param); + if (! ctx->skipping()) { + check_writable(ctx, opt); + if (opt.opt->is_vector()) + vector_variable_element_assign_scalar(ctx, opt, param); + else + scalar_variable_assign_scalar(ctx, opt, param); + } } template @@ -1189,7 +1292,8 @@ namespace client NewOldVariable &lhs, const expr &rhs) { - if (lhs.opt) { + if (ctx->skipping()) { + } else if (lhs.opt) { if (lhs.opt->is_vector()) rhs.throw_exception("Cannot assign a scalar value to a vector variable."); OptWithPos lhs_opt{ lhs.opt, lhs.it_range, true }; @@ -1215,7 +1319,8 @@ namespace client const expr &rhs_count, const expr &rhs_value) { - if (lhs.opt) { + if (ctx->skipping()) { + } else if (lhs.opt) { if (lhs.opt->is_scalar()) rhs_value.throw_exception("Cannot assign a vector value to a scalar variable."); OptWithPos lhs_opt{ lhs.opt, lhs.it_range, true }; @@ -1241,10 +1346,12 @@ namespace client const expr &rhs_count, const expr &rhs_value) { - check_writable(ctx, lhs); - if (lhs.opt->is_scalar()) - rhs_value.throw_exception("Cannot assign a vector value to a scalar variable."); - vector_variable_assign_expr_with_count(ctx, lhs, rhs_count, rhs_value); + if (! ctx->skipping()) { + check_writable(ctx, lhs); + if (lhs.opt->is_scalar()) + rhs_value.throw_exception("Cannot assign a vector value to a scalar variable."); + vector_variable_assign_expr_with_count(ctx, lhs, rhs_count, rhs_value); + } } template @@ -1262,6 +1369,9 @@ namespace client OptWithPos &lhs, const std::vector> &il) { + if (ctx->skipping()) + return; + check_writable(ctx, lhs); if (lhs.opt->is_scalar()) { @@ -1311,6 +1421,9 @@ namespace client NewOldVariable &lhs, const std::vector> &il) { + if (ctx->skipping()) + return; + if (lhs.opt) { // Assign to an existing vector variable. OptWithPos lhs_opt{ lhs.opt, lhs.it_range, true }; @@ -1355,7 +1468,7 @@ namespace client template static bool is_vector_variable_reference(const OptWithPos &var) { - return ! var.has_index() && var.opt->is_vector(); + return ! var.empty() && ! var.has_index() && var.opt->is_vector(); } // Called when checking whether the NewOldVariable could be assigned a vectir right hand side. @@ -1370,6 +1483,9 @@ namespace client OptWithPos &lhs, const OptWithPos &rhs) { + if (ctx->skipping()) + return; + check_writable(ctx, lhs); assert(lhs.opt->is_vector()); if (rhs.has_index() || ! rhs.opt->is_vector()) @@ -1399,6 +1515,10 @@ namespace client NewOldVariable &lhs, const OptWithPos &rhs) { + if (ctx->skipping()) + // Skipping, continue parsing. + return true; + if (lhs.opt) { assert(lhs.opt->is_vector()); OptWithPos lhs_opt{ lhs.opt, lhs.it_range, true }; @@ -1422,9 +1542,11 @@ namespace client } template - static void initializer_list_append(std::vector> &list, expr &expr) + static void initializer_list_append(std::vector> &list, expr ¶m) { - list.emplace_back(std::move(expr)); + if (param.type() != expr::TYPE_EMPTY) + // not skipping + list.emplace_back(std::move(param)); } template @@ -1433,9 +1555,11 @@ namespace client OptWithPos &opt, expr &out) { - if (opt.has_index() || ! opt.opt->is_vector()) - ctx->throw_exception("parameter of empty() is not a vector variable", opt.it_range); - out.set_b(static_cast(opt.opt)->size() == 0); + if (! ctx->skipping()) { + if (opt.has_index() || ! opt.opt->is_vector()) + ctx->throw_exception("parameter of empty() is not a vector variable", opt.it_range); + out.set_b(static_cast(opt.opt)->size() == 0); + } out.it_range = opt.it_range; } @@ -1445,9 +1569,11 @@ namespace client OptWithPos &opt, expr &out) { - if (opt.has_index() || ! opt.opt->is_vector()) - ctx->throw_exception("parameter of size() is not a vector variable", opt.it_range); - out.set_i(int(static_cast(opt.opt)->size())); + if (! ctx->skipping()) { + if (opt.has_index() || ! opt.opt->is_vector()) + ctx->throw_exception("parameter of size() is not a vector variable", opt.it_range); + out.set_i(int(static_cast(opt.opt)->size())); + } out.it_range = opt.it_range; } @@ -1456,14 +1582,19 @@ namespace client template static void evaluate_index(expr &expr_index, int &output) { - if (expr_index.type() != expr::TYPE_INT) - expr_index.throw_exception("Non-integer index is not allowed to address a vector variable."); - output = expr_index.i(); + if (expr_index.type() != expr::TYPE_EMPTY) { + if (expr_index.type() != expr::TYPE_INT) + expr_index.throw_exception("Non-integer index is not allowed to address a vector variable."); + output = expr_index.i(); + } } template static void random(const MyContext *ctx, expr ¶m1, expr ¶m2) { + if (ctx->skipping()) + return; + if (ctx->context_data == nullptr) ctx->throw_exception("Random number generator not available in this context.", boost::iterator_range(param1.it_range.begin(), param2.it_range.end())); @@ -1525,6 +1656,10 @@ namespace client msg += ' '; msg += "^\n"; } + + private: + // For skipping execution of inactive conditional branches. + mutable int m_depth_suppressed{ 0 }; }; template @@ -1537,17 +1672,24 @@ namespace client std::vector table; static void init(const expr &x) { - if (!x.numeric_type()) - x.throw_exception("Interpolation value must be a number."); + if (x.type() != expr::TYPE_EMPTY) { + if (!x.numeric_type()) + x.throw_exception("Interpolation value must be a number."); + } } static void add_pair(const expr &x, const expr &y, InterpolateTableContext &table) { - if (! x.numeric_type()) - x.throw_exception("X value of a table point must be a number."); - if (! y.numeric_type()) - y.throw_exception("Y value of a table point must be a number."); - table.table.push_back({ x.as_d(), x.it_range, y.as_d() }); + if (x.type() != expr::TYPE_EMPTY) { + if (! x.numeric_type()) + x.throw_exception("X value of a table point must be a number."); + if (! y.numeric_type()) + y.throw_exception("Y value of a table point must be a number."); + table.table.push_back({ x.as_d(), x.it_range, y.as_d() }); + } } static void evaluate(const expr &expr_x, const InterpolateTableContext &table, expr &out) { + if (expr_x.type() == expr::TYPE_EMPTY) + return; + // Check whether the table X values are sorted. double x = expr_x.as_d(); bool evaluated = false; @@ -1698,6 +1840,52 @@ namespace client } }; + template + struct FactorActions { + static void set_start_pos(Iterator &start_pos, expr &out) + { out.it_range = boost::iterator_range(start_pos, start_pos); } + static void int_(const MyContext *ctx, int &value, Iterator &end_pos, expr &out) { + if (ctx->skipping()) { + out.reset(); + out.it_range.end() = end_pos; + } else + out = expr(value, out.it_range.begin(), end_pos); + } + static void double_(const MyContext *ctx, double &value, Iterator &end_pos, expr &out) { + if (ctx->skipping()) { + out.reset(); + out.it_range.end() = end_pos; + } else + out = expr(value, out.it_range.begin(), end_pos); + } + static void bool_(const MyContext *ctx, bool &value, Iterator &end_pos, expr &out) { + if (ctx->skipping()) { + out.reset(); + out.it_range.end() = end_pos; + } else + out = expr(value, out.it_range.begin(), end_pos); + } + static void string_(const MyContext *ctx, boost::iterator_range &it_range, expr &out) { + if (ctx->skipping()) { + out.reset(); + out.it_range = it_range; + } else + out = expr(std::string(it_range.begin() + 1, it_range.end() - 1), it_range.begin(), it_range.end()); + } + static void expr_(expr &value, Iterator &end_pos, expr &out) + { auto begin_pos = out.it_range.begin(); out = expr(std::move(value), begin_pos, end_pos); } + static void minus_(expr &value, expr &out) + { out = value.unary_minus(out.it_range.begin()); } + static void not_(expr &value, expr &out) + { out = value.unary_not(out.it_range.begin()); } + static void to_int(expr &value, expr &out) + { out = value.unary_integer(out.it_range.begin()); } + static void round(expr &value, expr &out) + { out = value.round(out.it_range.begin()); } + // For indicating "no optional parameter". + static void noexpr(expr &out) { out.reset(); } + }; + /////////////////////////////////////////////////////////////////////////// // Our macro_processor grammar /////////////////////////////////////////////////////////////////////////// @@ -1771,19 +1959,19 @@ namespace client // | (kw["switch"] > switch_output(_r1) [_val = _1]) | (assignment_statement(_r1) [_val = _1]) | (new_variable_statement(_r1) [_val = _1]) - | (additive_expression(_r1) [ px::bind(&expr::to_string2, _1, _val) ]) + | (conditional_expression(_r1) [ px::bind(&expr::to_string2, _1, _val) ]) ; macro.name("macro"); // An if expression enclosed in {} (the outmost {} are already parsed by the caller). if_else_output = - eps[_b=true] > - bool_expr_eval(_r1)[_a=_1] > '}' > - text_block(_r1)[px::bind(&expr::set_if, _a, _b, _1, _val)] > '{' > - *(kw["elsif"] > bool_expr_eval(_r1)[_a=_1] > '}' > - text_block(_r1)[px::bind(&expr::set_if, _a, _b, _1, _val)] > '{') > - -(kw["else"] > lit('}') > - text_block(_r1)[px::bind(&expr::set_if, _b, _b, _1, _val)] > '{') > + eps[_a=true] > + (bool_expr_eval(_r1)[px::bind(&MyContext::block_enter, _r1, _1)] > '}' > + text_block(_r1))[px::bind(&MyContext::block_exit, _r1, _1, _a, _2, _val)] > '{' > + *((kw["elsif"] > bool_expr_eval(_r1)[px::bind(&MyContext::block_enter, _r1, _1 && _a)] > '}' > + text_block(_r1))[px::bind(&MyContext::block_exit, _r1, _1, _a, _2, _val)] > '{') > + -(kw["else"] > eps[px::bind(&MyContext::block_enter, _r1, _a)] > lit('}') > + text_block(_r1)[px::bind(&MyContext::block_exit, _r1, _a, _a, _1, _val)] > '{') > kw["endif"]; if_else_output.name("if_else_output"); // A switch expression enclosed in {} (the outmost {} are already parsed by the caller). @@ -1811,8 +1999,11 @@ namespace client identifier.name("identifier"); 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(_r1) [_val = _1] + >> -('?' > eps[px::bind(&expr::evaluate_boolean, _val, _a)] > + eps[px::bind(&MyContext::block_enter, _r1, _a)] > conditional_expression(_r1)[px::bind(&MyContext::block_exit_ternary>, _r1, _a, _1, _val)] + > ':' > + eps[px::bind(&MyContext::block_enter, _r1, ! _a)] > conditional_expression(_r1)[px::bind(&MyContext::block_exit_ternary>, _r1, ! _a, _1, _val)]); conditional_expression.name("conditional_expression"); logical_or_expression = @@ -1901,36 +2092,12 @@ namespace client ) ); - struct FactorActions { - static void set_start_pos(Iterator &start_pos, expr &out) - { out.it_range = boost::iterator_range(start_pos, start_pos); } - static void int_(int &value, Iterator &end_pos, expr &out) - { out = expr(value, out.it_range.begin(), end_pos); } - static void double_(double &value, Iterator &end_pos, expr &out) - { out = expr(value, out.it_range.begin(), end_pos); } - static void bool_(bool &value, Iterator &end_pos, expr &out) - { out = expr(value, out.it_range.begin(), end_pos); } - static void string_(boost::iterator_range &it_range, expr &out) - { out = expr(std::string(it_range.begin() + 1, it_range.end() - 1), it_range.begin(), it_range.end()); } - static void expr_(expr &value, Iterator &end_pos, expr &out) - { auto begin_pos = out.it_range.begin(); out = expr(std::move(value), begin_pos, end_pos); } - static void minus_(expr &value, expr &out) - { out = value.unary_minus(out.it_range.begin()); } - static void not_(expr &value, expr &out) - { out = value.unary_not(out.it_range.begin()); } - static void to_int(expr &value, expr &out) - { out = value.unary_integer(out.it_range.begin()); } - static void round(expr &value, expr &out) - { out = value.round(out.it_range.begin()); } - // For indicating "no optional parameter". - static void noexpr(expr &out) { out.reset(); } - }; - unary_expression = iter_pos[px::bind(&FactorActions::set_start_pos, _1, _val)] >> ( + unary_expression = iter_pos[px::bind(&FactorActions::set_start_pos, _1, _val)] >> ( variable_reference(_r1) [px::bind(&MyContext::variable_value, _r1, _1, _val)] - | (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) ] + | (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) ] | (kw["min"] > '(' > conditional_expression(_r1) [_val = _1] > ',' > conditional_expression(_r1) > ')') [ px::bind(&expr::min, _val, _2) ] | (kw["max"] > '(' > conditional_expression(_r1) [_val = _1] > ',' > conditional_expression(_r1) > ')') @@ -1941,18 +2108,18 @@ namespace client [ px::bind(&expr::template digits, _val, _2, _3) ] | (kw["zdigits"] > '(' > conditional_expression(_r1) [_val = _1] > ',' > conditional_expression(_r1) > optional_parameter(_r1)) [ px::bind(&expr::template digits, _val, _2, _3) ] - | (kw["int"] > '(' > conditional_expression(_r1) > ')') [ px::bind(&FactorActions::to_int, _1, _val) ] - | (kw["round"] > '(' > conditional_expression(_r1) > ')') [ px::bind(&FactorActions::round, _1, _val) ] + | (kw["int"] > '(' > conditional_expression(_r1) > ')') [ px::bind(&FactorActions::to_int, _1, _val) ] + | (kw["round"] > '(' > conditional_expression(_r1) > ')') [ px::bind(&FactorActions::round, _1, _val) ] | (kw["is_nil"] > '(' > variable_reference(_r1) > ')') [px::bind(&MyContext::is_nil_test, _r1, _1, _val)] | (kw["one_of"] > '(' > one_of(_r1) > ')') [ _val = _1 ] | (kw["empty"] > '(' > variable_reference(_r1) > ')') [px::bind(&MyContext::is_vector_empty, _r1, _1, _val)] | (kw["size"] > '(' > variable_reference(_r1) > ')') [px::bind(&MyContext::vector_size, _r1, _1, _val)] | (kw["interpolate_table"] > '(' > interpolate_table(_r1) > ')') [ _val = _1 ] - | (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) ] + | (strict_double > iter_pos) [ px::bind(&FactorActions::double_, _r1, _1, _2, _val) ] + | (int_ > iter_pos) [ px::bind(&FactorActions::int_, _r1, _1, _2, _val) ] + | (kw[bool_] > iter_pos) [ px::bind(&FactorActions::bool_, _r1, _1, _2, _val) ] | raw[lexeme['"' > *((utf8char - char_('\\') - char_('"')) | ('\\' > char_)) > '"']] - [ px::bind(&FactorActions::string_, _1, _val) ] + [ px::bind(&FactorActions::string_, _r1, _1, _val) ] ); unary_expression.name("unary_expression"); @@ -1980,8 +2147,8 @@ namespace client [px::bind(&InterpolateTableContext::add_pair, _1, _2, _val)] >> -lit(',')) ); interpolate_table.name("interpolate_table_list"); - optional_parameter = iter_pos[px::bind(&FactorActions::set_start_pos, _1, _val)] >> ( - lit(')') [ px::bind(&FactorActions::noexpr, _val) ] + optional_parameter = iter_pos[px::bind(&FactorActions::set_start_pos, _1, _val)] >> ( + lit(')') [ px::bind(&FactorActions::noexpr, _val) ] | (lit(',') > conditional_expression(_r1) > ')') [ _val = _1 ] ); optional_parameter.name("optional_parameter"); @@ -2074,7 +2241,7 @@ namespace client // Parsed identifier name. qi::rule(), spirit_encoding::space_type> identifier; // Ternary operator (?:) over logical_or_expression. - RuleExpression conditional_expression; + qi::rule(const MyContext*), qi::locals, spirit_encoding::space_type> conditional_expression; // Logical or over logical_and_expressions. RuleExpression logical_or_expression; // Logical and over relational_expressions. @@ -2108,7 +2275,7 @@ namespace client qi::rule(const MyContext*), qi::locals>, spirit_encoding::space_type> interpolate_table; qi::rule(const MyContext*, const expr ¶m), spirit_encoding::space_type> interpolate_table_list; - qi::rule, spirit_encoding::space_type> if_else_output; + qi::rule, spirit_encoding::space_type> if_else_output; qi::rule>, spirit_encoding::space_type> assignment_statement; // Allocating new local or global variables. qi::rule>, spirit_encoding::space_type> new_variable_statement; diff --git a/tests/libslic3r/test_placeholder_parser.cpp b/tests/libslic3r/test_placeholder_parser.cpp index b29ca0f8e..0e3521a23 100644 --- a/tests/libslic3r/test_placeholder_parser.cpp +++ b/tests/libslic3r/test_placeholder_parser.cpp @@ -75,6 +75,11 @@ SCENARIO("Placeholder parser scripting", "[PlaceholderParser]") { SECTION("math: zdigits(5., 15, 8)") { REQUIRE(parser.process("{zdigits(5, 15, 8)}") == "000005.00000000"); } SECTION("math: digits(13.84375892476, 15, 8)") { REQUIRE(parser.process("{digits(13.84375892476, 15, 8)}") == " 13.84375892"); } SECTION("math: zdigits(13.84375892476, 15, 8)") { REQUIRE(parser.process("{zdigits(13.84375892476, 15, 8)}") == "000013.84375892"); } + SECTION("math: ternary1") { REQUIRE(parser.process("{12 == 12 ? 1 - 3 : 2 * 2 * unknown_symbol}") == "-2"); } + SECTION("math: ternary2") { REQUIRE(parser.process("{12 == 21/2 ? 1 - 1 - unknown_symbol : 2 * 2}") == "4"); } + SECTION("math: ternary3") { REQUIRE(parser.process("{12 == 13 ? 1 - 1 * unknown_symbol : 2 * 2}") == "4"); } + SECTION("math: ternary4") { REQUIRE(parser.process("{12 == 2 * 6 ? 1 - 1 : 2 * unknown_symbol}") == "0"); } + SECTION("math: ternary nested") { REQUIRE(parser.process("{12 == 2 * 6 ? 3 - 1 != 2 ? does_not_exist : 0 * 0 - 0 / 1 + 12345 : bull ? 3 - cokoo : 2 * unknown_symbol}") == "12345"); } SECTION("math: interpolate_table(13.84375892476, (0, 0), (20, 20))") { REQUIRE(std::stod(parser.process("{interpolate_table(13.84375892476, (0, 0), (20, 20))}")) == Approx(13.84375892476)); } SECTION("math: interpolate_table(13, (0, 0), (20, 20), (30, 20))") { REQUIRE(std::stod(parser.process("{interpolate_table(13, (0, 0), (20, 20), (30, 20))}")) == Approx(13.)); } SECTION("math: interpolate_table(25, (0, 0), (20, 20), (30, 20))") { REQUIRE(std::stod(parser.process("{interpolate_table(25, (0, 0), (20, 20), (30, 20))}")) == Approx(20.)); } @@ -106,10 +111,10 @@ SCENARIO("Placeholder parser scripting", "[PlaceholderParser]") { SECTION("boolean expression parser: (12 == 12) or (13 == 14)") { REQUIRE(boolean_expression("(12 == 12) or (13 == 14)")); } SECTION("boolean expression parser: (12 == 12) || (13 == 14)") { REQUIRE(boolean_expression("(12 == 12) || (13 == 14)")); } SECTION("boolean expression parser: (12 == 12) and not (13 == 14)") { REQUIRE(boolean_expression("(12 == 12) and not (13 == 14)")); } - SECTION("boolean expression parser: ternary true") { REQUIRE(boolean_expression("(12 == 12) ? (1 - 1 == 0) : (2 * 2 == 3)")); } - SECTION("boolean expression parser: ternary false") { REQUIRE(! boolean_expression("(12 == 21/2) ? (1 - 1 == 0) : (2 * 2 == 3)")); } - SECTION("boolean expression parser: ternary false 2") { REQUIRE(boolean_expression("(12 == 13) ? (1 - 1 == 3) : (2 * 2 == 4)")); } - SECTION("boolean expression parser: ternary true 2") { REQUIRE(! boolean_expression("(12 == 2 * 6) ? (1 - 1 == 3) : (2 * 2 == 4)")); } + SECTION("boolean expression parser: ternary true") { REQUIRE(boolean_expression("(12 == 12) ? (1 - 1 == 0) : (2 * 2 == 3 * unknown_symbol)")); } + SECTION("boolean expression parser: ternary false") { REQUIRE(! boolean_expression("(12 == 21/2) ? (1 - 1 == 0 - unknown_symbol) : (2 * 2 == 3)")); } + SECTION("boolean expression parser: ternary false 2") { REQUIRE(boolean_expression("(12 == 13) ? (1 - 1 == 3 * unknown_symbol) : (2 * 2 == 4)")); } + SECTION("boolean expression parser: ternary true 2") { REQUIRE(! boolean_expression("(12 == 2 * 6) ? (1 - 1 == 3) : (2 * 2 == 4 * unknown_symbol)")); } SECTION("boolean expression parser: lower than - false") { REQUIRE(! boolean_expression("12 < 3")); } SECTION("boolean expression parser: lower than - true") { REQUIRE(boolean_expression("12 < 22")); } SECTION("boolean expression parser: greater than - true") { REQUIRE(boolean_expression("12 > 3")); } @@ -227,4 +232,23 @@ SCENARIO("Placeholder parser variables", "[PlaceholderParser]") { SECTION("size() of a an empty vector returns the right size") { REQUIRE(parser.process("{local myint = (0);myint=();size(myint)}", 0, nullptr, nullptr, nullptr) == "0"); } SECTION("empty() of a non-empty vector returns false") { REQUIRE(parser.process("{local myint = (0, 1, 2, 3)}{empty(myint)}", 0, nullptr, nullptr, nullptr) == "false"); } SECTION("empty() of a an empty vector returns true") { REQUIRE(parser.process("{local myint = (0);myint=();empty(myint)}", 0, nullptr, nullptr, nullptr) == "true"); } + + SECTION("nested if with new variables") { + std::string script = + "{if 1 == 1}{local myints = (5, 4, 3, 2, 1)}{else}{local myfloats = (1., 2., 3., 4., 5., 6., 7.)}{endif}" + "{myints[1]},{size(myints)}"; + REQUIRE(parser.process(script, 0, nullptr, nullptr, nullptr) == "4,5"); + } + SECTION("nested if with new variables 2") { + std::string script = + "{if 1 == 0}{local myints = (5, 4, 3, 2, 1)}{else}{local myfloats = (1., 2., 3., 4., 5., 6., 7.)}{endif}" + "{size(myfloats)}"; + REQUIRE(parser.process(script, 0, nullptr, nullptr, nullptr) == "7"); + } + SECTION("nested if with new variables, two level") { + std::string script = + "{if 1 == 1}{if 2 == 3}{nejaka / haluz}{else}{local myints = (6, 5, 4, 3, 2, 1)}{endif}{else}{if zase * haluz}{else}{local myfloats = (1., 2., 3., 4., 5., 6., 7.)}{endif}{endif}" + "{size(myints)}"; + REQUIRE(parser.process(script, 0, nullptr, nullptr, nullptr) == "6"); + } }