diff --git a/src/libslic3r/PlaceholderParser.cpp b/src/libslic3r/PlaceholderParser.cpp index a26841082..29d35ac2f 100644 --- a/src/libslic3r/PlaceholderParser.cpp +++ b/src/libslic3r/PlaceholderParser.cpp @@ -1263,15 +1263,24 @@ namespace client const std::vector> &il) { check_writable(ctx, lhs); + + if (lhs.opt->is_scalar()) { + if (il.size() == 1) + // scalar_var = ( scalar ) + scalar_variable_assign_scalar_expression(ctx, lhs, il.front()); + else + // scalar_var = () + // or + // scalar_var = ( scalar, scalar, ... ) + ctx->throw_exception("Cannot assign a vector value to a scalar variable.", lhs.it_range); + } + auto check_numeric_vector = [](const std::vector> &il) { for (auto &i : il) if (! i.numeric_type()) i.throw_exception("Right side is not a numeric expression"); }; - if (lhs.opt->is_scalar()) - ctx->throw_exception("Cannot assign a vector value to a scalar variable.", lhs.it_range); - ConfigOption *opt = const_cast(lhs.opt); switch (lhs.opt->type()) { case coFloats: @@ -1304,11 +1313,11 @@ namespace client { if (lhs.opt) { // Assign to an existing vector variable. - if (lhs.opt->is_scalar()) - ctx->throw_exception("Cannot assign a vector value to a scalar variable.", lhs.it_range); OptWithPos lhs_opt{ lhs.opt, lhs.it_range, true }; vector_variable_assign_initializer_list(ctx, lhs_opt, il); } else { + if (il.empty()) + ctx->throw_exception("Cannot create vector variable from an empty initializer list, because its type cannot be deduced.", lhs.it_range); // Allocate a new vector variable. // First guesstimate type of the output vector. size_t num_bool = 0; @@ -1344,6 +1353,17 @@ namespace client } } + template + static bool is_vector_variable_reference(const OptWithPos &var) { + return ! var.has_index() && var.opt->is_vector(); + } + + // Called when checking whether the NewOldVariable could be assigned a vectir right hand side. + template + static bool could_be_vector_variable_reference(const NewOldVariable &var) { + return var.opt == nullptr || var.opt->is_vector(); + } + template static void copy_vector_variable_to_vector_variable( const MyContext *ctx, @@ -1351,9 +1371,9 @@ namespace client const OptWithPos &rhs) { check_writable(ctx, lhs); - assert(rhs.opt->is_vector()); - if (! lhs.opt->is_vector()) - ctx->throw_exception("Cannot assign vector to a scalar", lhs.it_range); + assert(lhs.opt->is_vector()); + if (rhs.has_index() || ! rhs.opt->is_vector()) + ctx->throw_exception("Cannot assign scalar to a vector", lhs.it_range); if (lhs.opt->type() != rhs.opt->type()) { // Vector types are not compatible. switch (lhs.opt->type()) { @@ -1372,11 +1392,6 @@ namespace client const_cast(lhs.opt)->set(rhs.opt); } - template - static bool is_vector_variable_reference(const OptWithPos &var) { - return ! var.has_index() && var.opt->is_vector(); - } - template static bool vector_variable_new_from_copy( const MyContext *ctx, @@ -1384,43 +1399,26 @@ namespace client NewOldVariable &lhs, const OptWithPos &rhs) { - if (is_vector_variable_reference(rhs)) { - if (lhs.opt) { - OptWithPos lhs_opt{ lhs.opt, lhs.it_range, true }; - copy_vector_variable_to_vector_variable(ctx, lhs_opt, rhs); - } else { - // Clone the vector variable. - std::unique_ptr opt_new; - if (one_of(rhs.opt->type(), { coFloats, coInts, coStrings, coBools })) - opt_new = std::unique_ptr(rhs.opt->clone()); - else if (rhs.opt->type() == coPercents) - opt_new = std::make_unique(static_cast(rhs.opt)->values); - else - ctx->throw_exception("Duplicating this type of vector variable is not supported", rhs.it_range); - const_cast(ctx)->store_new_variable(lhs.name, std::move(opt_new), global_variable); - } - // Continue parsing. - return true; + if (lhs.opt) { + assert(lhs.opt->is_vector()); + OptWithPos lhs_opt{ lhs.opt, lhs.it_range, true }; + copy_vector_variable_to_vector_variable(ctx, lhs_opt, rhs); } else { - // Skip parsing this branch, bactrack. - return false; - } - } - - template - static bool vector_variable_assign_copy( - const MyContext *ctx, - OptWithPos &lhs, - const OptWithPos &rhs) - { - if (is_vector_variable_reference(rhs)) { - copy_vector_variable_to_vector_variable(ctx, lhs, rhs); - // Continue parsing. - return true; - } else { - // Skip parsing this branch, bactrack. - return false; + if (rhs.has_index() || ! rhs.opt->is_vector()) + // Stop parsing, let the other rules resolve this case. + return false; + // Clone the vector variable. + std::unique_ptr opt_new; + if (one_of(rhs.opt->type(), { coFloats, coInts, coStrings, coBools })) + opt_new = std::unique_ptr(rhs.opt->clone()); + else if (rhs.opt->type() == coPercents) + opt_new = std::make_unique(static_cast(rhs.opt)->values); + else + ctx->throw_exception("Duplicating this type of vector variable is not supported", rhs.it_range); + const_cast(ctx)->store_new_variable(lhs.name, std::move(opt_new), global_variable); } + // Continue parsing. + return true; } template @@ -1572,12 +1570,12 @@ namespace client // Table to translate symbol tag to a human readable error message. std::map MyContext::tag_to_error_message = { - { "array", "Unknown syntax error" }, { "eoi", "Unknown syntax error" }, { "start", "Unknown syntax error" }, { "text", "Invalid text." }, { "text_block", "Invalid text block." }, { "macro", "Invalid macro." }, + { "repeat", "Unknown syntax error" }, { "if_else_output", "Not an {if}{else}{endif} macro." }, { "switch_output", "Not a {switch} macro." }, { "legacy_variable_expansion", "Expecting a legacy variable expansion format" }, @@ -1594,7 +1592,6 @@ namespace client { "optional_parameter", "Expecting a closing brace or an optional parameter." }, { "one_of_list", "Expecting a list of string patterns (simple text or rexep)" }, { "variable_reference", "Expecting a variable reference."}, - { "is_nil_test", "Expecting a scalar variable reference."}, { "variable", "Expecting a variable name."}, { "regular_expression", "Expecting a regular expression."} }; @@ -1846,37 +1843,35 @@ namespace client assignment_statement = variable_reference(_r1)[_a = _1] >> '=' > ( // Consumes also '(' conditional_expression ')', that means enclosing an expression into braces makes it a single value vector initializer. - (lit('(') > new_variable_initializer_list(_r1) > ')') + (lit('(') > initializer_list(_r1) > ')') [px::bind(&MyContext::vector_variable_assign_initializer_list, _r1, _a, _1)] // Process it before conditional_expression, as conditional_expression requires a vector reference to be augmented with an index. // Only process such variable references, which return a naked vector variable. - | variable_reference(_r1) - [px::ref(qi::_pass) = px::bind(&MyContext::vector_variable_assign_copy, _r1, _a, _1)] + | eps(px::bind(&MyContext::is_vector_variable_reference, _a)) >> + variable_reference(_r1)[px::bind(&MyContext::copy_vector_variable_to_vector_variable, _r1, _a, _1)] // Would NOT consume '(' conditional_expression ')' because such value was consumed with the expression above. | conditional_expression(_r1) [px::bind(&MyContext::scalar_variable_assign_scalar_expression, _r1, _a, _1)] - | (kw["array"] > "(" > additive_expression(_r1) > "," > conditional_expression(_r1) > ")") + | (kw["repeat"] > "(" > additive_expression(_r1) > "," > conditional_expression(_r1) > ")") [px::bind(&MyContext::vector_variable_assign_array, _r1, _a, _1, _2)] ); new_variable_statement = (kw["local"][_a = false] | kw["global"][_a = true]) > identifier[px::bind(&MyContext::new_old_variable, _r1, _a, _1, _b)] > lit('=') > ( // Consumes also '(' conditional_expression ')', that means enclosing an expression into braces makes it a single value vector initializer. - (lit('(') > new_variable_initializer_list(_r1) > ')') + (lit('(') > initializer_list(_r1) > ')') [px::bind(&MyContext::vector_variable_new_from_initializer_list, _r1, _a, _b, _1)] // Process it before conditional_expression, as conditional_expression requires a vector reference to be augmented with an index. // Only process such variable references, which return a naked vector variable. - | variable_reference(_r1) - [px::ref(qi::_pass) = px::bind(&MyContext::vector_variable_new_from_copy, _r1, _a, _b, _1)] + | eps(px::bind(&MyContext::could_be_vector_variable_reference, _b)) >> + variable_reference(_r1)[px::ref(qi::_pass) = px::bind(&MyContext::vector_variable_new_from_copy, _r1, _a, _b, _1)] // Would NOT consume '(' conditional_expression ')' because such value was consumed with the expression above. | conditional_expression(_r1) [px::bind(&MyContext::scalar_variable_new_from_scalar_expression, _r1, _a, _b, _1)] - | (kw["array"] > "(" > additive_expression(_r1) > "," > conditional_expression(_r1) > ")") + | (kw["repeat"] > "(" > additive_expression(_r1) > "," > conditional_expression(_r1) > ")") [px::bind(&MyContext::vector_variable_new_from_array, _r1, _a, _b, _1, _2)] ); - new_variable_initializer_list = - conditional_expression(_r1)[px::bind(&MyContext::initializer_list_append, _val, _1)] >> - *(lit(',') > conditional_expression(_r1)[px::bind(&MyContext::initializer_list_append, _val, _1)]); + initializer_list = *(lit(',') > conditional_expression(_r1)[px::bind(&MyContext::initializer_list_append, _val, _1)]); struct FactorActions { static void set_start_pos(Iterator &start_pos, expr &out) @@ -1920,7 +1915,7 @@ namespace client [ 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["is_nil"] > '(' > is_nil_test(_r1) > ')') [ _val = _1 ] + | (kw["is_nil"] > '(' > variable_reference(_r1) > ')') [px::bind(&MyContext::is_nil_test, _r1, _1, _val)] | (kw["one_of"] > '(' > one_of(_r1) > ')') [ _val = _1 ] | (kw["interpolate_table"] > '(' > interpolate_table(_r1) > ')') [ _val = _1 ] | (strict_double > iter_pos) [ px::bind(&FactorActions::double_, _1, _2, _val) ] @@ -1961,9 +1956,6 @@ namespace client ); optional_parameter.name("optional_parameter"); - is_nil_test = variable_reference(_r1)[px::bind(&MyContext::is_nil_test, _r1, _1, _val)]; - is_nil_test.name("is_nil test"); - variable_reference = variable(_r1)[_a=_1] >> ( @@ -1981,7 +1973,6 @@ namespace client keywords.add ("and") - ("array") ("digits") ("zdigits") ("if") @@ -1998,6 +1989,7 @@ namespace client ("min") ("max") ("random") + ("repeat") ("round") ("not") ("one_of") @@ -2030,7 +2022,6 @@ namespace client debug(optional_parameter); debug(variable_reference); debug(variable); - debug(is_nil_test); debug(regular_expression); } } @@ -2089,7 +2080,7 @@ namespace client qi::rule>, spirit_encoding::space_type> assignment_statement; // Allocating new local or global variables. qi::rule>, spirit_encoding::space_type> new_variable_statement; - qi::rule>(const MyContext*), spirit_encoding::space_type> new_variable_initializer_list; + qi::rule>(const MyContext*), spirit_encoding::space_type> initializer_list; // qi::rule, bool, std::string>, spirit_encoding::space_type> switch_output; qi::symbols keywords; diff --git a/tests/libslic3r/test_placeholder_parser.cpp b/tests/libslic3r/test_placeholder_parser.cpp index ee1461baf..48fe43117 100644 --- a/tests/libslic3r/test_placeholder_parser.cpp +++ b/tests/libslic3r/test_placeholder_parser.cpp @@ -186,12 +186,12 @@ SCENARIO("Placeholder parser variables", "[PlaceholderParser]") { SECTION("create a string global variable and redefine it") { REQUIRE(parser.process("{global mystr = \"mine\" + \"only\" + \"mine\"}{global mystr = \"yours\"}{mystr}", 0, nullptr, nullptr, &context_with_global_dict) == "yours"); } SECTION("create a bool global variable and redefine it") { REQUIRE(parser.process("{global mybool = 1 + 1 == 2}{global mybool = false}{mybool}", 0, nullptr, nullptr, &context_with_global_dict) == "false"); } - SECTION("create an ints local variable with array()") { REQUIRE(parser.process("{local myint = array(2*3, 4*6)}{myint[5]}", 0, nullptr, nullptr, nullptr) == "24"); } - SECTION("create a strings local variable array()") { REQUIRE(parser.process("{local mystr = array(2*3, \"mine\" + \"only\" + \"mine\")}{mystr[5]}", 0, nullptr, nullptr, nullptr) == "mineonlymine"); } - SECTION("create a bools local variable array()") { REQUIRE(parser.process("{local mybool = array(5, 1 + 1 == 2)}{mybool[4]}", 0, nullptr, nullptr, nullptr) == "true"); } - SECTION("create an ints global variable array()") { REQUIRE(parser.process("{global myint = array(2*3, 4*6)}{myint[5]}", 0, nullptr, nullptr, &context_with_global_dict) == "24"); } - SECTION("create a strings global variable array()") { REQUIRE(parser.process("{global mystr = array(2*3, \"mine\" + \"only\" + \"mine\")}{mystr[5]}", 0, nullptr, nullptr, &context_with_global_dict) == "mineonlymine"); } - SECTION("create a bools global variable array()") { REQUIRE(parser.process("{global mybool = array(5, 1 + 1 == 2)}{mybool[4]}", 0, nullptr, nullptr, &context_with_global_dict) == "true"); } + SECTION("create an ints local variable with repeat()") { REQUIRE(parser.process("{local myint = repeat(2*3, 4*6)}{myint[5]}", 0, nullptr, nullptr, nullptr) == "24"); } + SECTION("create a strings local variable with repeat()") { REQUIRE(parser.process("{local mystr = repeat(2*3, \"mine\" + \"only\" + \"mine\")}{mystr[5]}", 0, nullptr, nullptr, nullptr) == "mineonlymine"); } + SECTION("create a bools local variable with repeat()") { REQUIRE(parser.process("{local mybool = repeat(5, 1 + 1 == 2)}{mybool[4]}", 0, nullptr, nullptr, nullptr) == "true"); } + SECTION("create an ints global variable with repeat()") { REQUIRE(parser.process("{global myint = repeat(2*3, 4*6)}{myint[5]}", 0, nullptr, nullptr, &context_with_global_dict) == "24"); } + SECTION("create a strings global variable with repeat()") { REQUIRE(parser.process("{global mystr = repeat(2*3, \"mine\" + \"only\" + \"mine\")}{mystr[5]}", 0, nullptr, nullptr, &context_with_global_dict) == "mineonlymine"); } + SECTION("create a bools global variable with repeat()") { REQUIRE(parser.process("{global mybool = repeat(5, 1 + 1 == 2)}{mybool[4]}", 0, nullptr, nullptr, &context_with_global_dict) == "true"); } SECTION("create an ints local variable with initializer list") { REQUIRE(parser.process("{local myint = (2*3, 4*6, 5*5)}{myint[1]}", 0, nullptr, nullptr, nullptr) == "24"); } SECTION("create a strings local variable with initializer list") { REQUIRE(parser.process("{local mystr = (2*3, \"mine\" + \"only\" + \"mine\", 8)}{mystr[1]}", 0, nullptr, nullptr, nullptr) == "mineonlymine"); } @@ -208,17 +208,17 @@ SCENARIO("Placeholder parser variables", "[PlaceholderParser]") { SECTION("create a bools global variable by a copy") { REQUIRE(parser.process("{global mybool = enable_dynamic_fan_speeds}{mybool[0]}", 0, &config, nullptr, &context_with_global_dict) == "true"); } SECTION("create an ints local variable by a copy and overwrite it") { - REQUIRE(parser.process("{local myint = temperature}{myint = array(2*3, 4*6)}{myint[5]}", 0, &config, nullptr, nullptr) == "24"); + REQUIRE(parser.process("{local myint = temperature}{myint = repeat(2*3, 4*6)}{myint[5]}", 0, &config, nullptr, nullptr) == "24"); REQUIRE(parser.process("{local myint = temperature}{myint = (2*3, 4*6)}{myint[1]}", 0, &config, nullptr, nullptr) == "24"); REQUIRE(parser.process("{local myint = temperature}{myint = (1)}{myint = temperature}{myint[0]}", 0, &config, nullptr, nullptr) == "357"); } SECTION("create a strings local variable by a copy and overwrite it") { - REQUIRE(parser.process("{local mystr = filament_notes}{mystr = array(2*3, \"mine\" + \"only\" + \"mine\")}{mystr[5]}", 0, &config, nullptr, nullptr) == "mineonlymine"); + REQUIRE(parser.process("{local mystr = filament_notes}{mystr = repeat(2*3, \"mine\" + \"only\" + \"mine\")}{mystr[5]}", 0, &config, nullptr, nullptr) == "mineonlymine"); REQUIRE(parser.process("{local mystr = filament_notes}{mystr = (2*3, \"mine\" + \"only\" + \"mine\")}{mystr[1]}", 0, &config, nullptr, nullptr) == "mineonlymine"); REQUIRE(parser.process("{local mystr = filament_notes}{mystr = (2*3, \"mine\" + \"only\" + \"mine\")}{mystr = filament_notes}{mystr[0]}", 0, &config, nullptr, nullptr) == "testnotes"); } SECTION("create a bools local variable by a copy and overwrite it") { - REQUIRE(parser.process("{local mybool = enable_dynamic_fan_speeds}{mybool = array(2*3, true)}{mybool[5]}", 0, &config, nullptr, nullptr) == "true"); + REQUIRE(parser.process("{local mybool = enable_dynamic_fan_speeds}{mybool = repeat(2*3, true)}{mybool[5]}", 0, &config, nullptr, nullptr) == "true"); REQUIRE(parser.process("{local mybool = enable_dynamic_fan_speeds}{mybool = (false, true)}{mybool[1]}", 0, &config, nullptr, nullptr) == "true"); REQUIRE(parser.process("{local mybool = enable_dynamic_fan_speeds}{mybool = (false, false)}{mybool = enable_dynamic_fan_speeds}{mybool[0]}", 0, &config, nullptr, nullptr) == "true"); }