diff --git a/src/libslic3r/PlaceholderParser.cpp b/src/libslic3r/PlaceholderParser.cpp index 5235fd72e..c26197d27 100644 --- a/src/libslic3r/PlaceholderParser.cpp +++ b/src/libslic3r/PlaceholderParser.cpp @@ -171,7 +171,8 @@ namespace client struct OptWithPos { OptWithPos() {} OptWithPos(ConfigOptionConstPtr opt, boost::iterator_range it_range) : opt(opt), it_range(it_range) {} - ConfigOptionConstPtr opt = nullptr; + ConfigOptionConstPtr opt { nullptr }; + bool writable { false }; boost::iterator_range it_range; }; @@ -688,6 +689,7 @@ namespace client const DynamicConfig *external_config = nullptr; const DynamicConfig *config = nullptr; const DynamicConfig *config_override = nullptr; + mutable DynamicConfig *config_outputs = nullptr; size_t current_extruder_id = 0; PlaceholderParser::ContextData *context_data = nullptr; // If false, the macro_processor will evaluate a full macro. @@ -713,6 +715,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 { return this->config_outputs ? this->config_outputs->optptr(opt_key, false) : nullptr; } template static void legacy_variable_expansion( @@ -788,8 +791,12 @@ namespace client OptWithPos &output) { const ConfigOption *opt = ctx->resolve_symbol(std::string(opt_key.begin(), opt_key.end())); - if (opt == nullptr) - ctx->throw_exception("Not a variable name", opt_key); + if (opt == nullptr) { + opt = ctx->resolve_output_symbol(std::string(opt_key.begin(), opt_key.end())); + if (opt == nullptr) + ctx->throw_exception("Not a variable name", opt_key); + output.writable = true; + } output.opt = opt; output.it_range = opt_key; } @@ -914,6 +921,98 @@ namespace client output.it_range = boost::iterator_range(opt.it_range.begin(), it_end); } + // Decoding a scalar variable symbol "opt", assigning it a value of "param". + template + static void scalar_variable_assign( + const MyContext *ctx, + OptWithPos &opt, + expr ¶m, + // Not used, just clear it. + std::string &out) + { + if (! opt.writable) + ctx->throw_exception("Cannot modify a read-only variable", opt.it_range); + if (opt.opt->is_vector()) + ctx->throw_exception("Referencing an output vector variable when scalar is expected", opt.it_range); + ConfigOption *wropt = const_cast(opt.opt); + switch (wropt->type()) { + case coFloat: + if (param.type() != expr::TYPE_INT && param.type() != expr::TYPE_DOUBLE) + ctx->throw_exception("Right side is not a numeric expression", param.it_range); + static_cast(wropt)->value = param.as_d(); + break; + case coInt: + if (param.type() != expr::TYPE_INT && param.type() != expr::TYPE_DOUBLE) + ctx->throw_exception("Right side is not a numeric expression", param.it_range); + static_cast(wropt)->value = param.as_i(); + break; + case coString: + static_cast(wropt)->value = param.to_string(); + break; + case coPercent: + if (param.type() != expr::TYPE_INT && param.type() != expr::TYPE_DOUBLE) + ctx->throw_exception("Right side is not a numeric expression", param.it_range); + static_cast(wropt)->value = param.as_d(); + break; + case coBool: + if (param.type() != expr::TYPE_BOOL) + ctx->throw_exception("Right side is not a boolean expression", param.it_range); + static_cast(wropt)->value = param.b(); + break; + default: + ctx->throw_exception("Unsupported output scalar variable type", opt.it_range); + } + out.clear(); + } + + template + static void vector_variable_assign( + const MyContext *ctx, + OptWithPos &opt, + int &index, + expr ¶m, + // Not used, just clear it. + std::string &out) + { + if (! opt.writable) + ctx->throw_exception("Cannot modify a read-only variable", opt.it_range); + if (opt.opt->is_scalar()) + ctx->throw_exception("Referencing an output scalar variable when vector is expected", opt.it_range); + ConfigOptionVectorBase *vec = const_cast(static_cast(opt.opt)); + if (vec->empty()) + ctx->throw_exception("Indexing an empty vector variable", opt.it_range); + if (index < 0 || index >= int(vec->size())) + ctx->throw_exception("Index out of range", opt.it_range); + switch (opt.opt->type()) { + case coFloats: + if (param.type() != expr::TYPE_INT && param.type() != expr::TYPE_DOUBLE) + ctx->throw_exception("Right side is not a numeric expression", param.it_range); + static_cast(vec)->values[index] = param.as_d(); + break; + case coInts: + if (param.type() != expr::TYPE_INT && param.type() != expr::TYPE_DOUBLE) + ctx->throw_exception("Right side is not a numeric expression", param.it_range); + static_cast(vec)->values[index] = param.as_i(); + break; + case coStrings: + static_cast(vec)->values[index] = param.to_string(); + break; + case coPercents: + if (param.type() != expr::TYPE_INT && param.type() != expr::TYPE_DOUBLE) + ctx->throw_exception("Right side is not a numeric expression", param.it_range); + static_cast(vec)->values[index] = param.as_d(); + break; + case coBools: + if (param.type() != expr::TYPE_BOOL) + ctx->throw_exception("Right side is not a boolean expression", param.it_range); + static_cast(vec)->values[index] = param.b(); + break; + default: + ctx->throw_exception("Unsupported output vector variable type", opt.it_range); + } + out.clear(); + } + // Verify that the expression returns an integer, which may be used // to address a vector. template @@ -1165,7 +1264,9 @@ namespace client macro = (kw["if"] > if_else_output(_r1) [_val = _1]) // | (kw["switch"] > switch_output(_r1) [_val = _1]) - | additive_expression(_r1) [ px::bind(&expr::to_string2, _1, _val) ]; + | (assignment_statement(_r1) [_val = _1]) + | (additive_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). @@ -1257,6 +1358,15 @@ namespace client ); multiplicative_expression.name("multiplicative_expression"); + assignment_statement = + variable_reference(_r1)[_a = _1] >> + ( + ('[' >> additive_expression(_r1)[px::bind(&MyContext::evaluate_index, _1, _b)] >> ']' >> '=' >> additive_expression(_r1)) + [px::bind(&MyContext::vector_variable_assign, _r1, _a, _b, _2, _val)] + | ('=' >> additive_expression(_r1)) + [px::bind(&MyContext::scalar_variable_assign, _r1, _a, _1, _val)] + ); + struct FactorActions { static void set_start_pos(Iterator &start_pos, expr &out) { out.it_range = boost::iterator_range(start_pos, start_pos); } @@ -1430,6 +1540,7 @@ namespace client qi::rule(const MyContext*), qi::locals, int>, spirit_encoding::space_type> is_nil_test; qi::rule, spirit_encoding::space_type> if_else_output; + qi::rule, int>, spirit_encoding::space_type> assignment_statement; // qi::rule, bool, std::string>, spirit_encoding::space_type> switch_output; qi::symbols keywords; @@ -1461,12 +1572,13 @@ static std::string process_macro(const std::string &templ, client::MyContext &co return output; } -std::string PlaceholderParser::process(const std::string &templ, unsigned int current_extruder_id, const DynamicConfig *config_override, ContextData *context_data) const +std::string PlaceholderParser::process(const std::string &templ, unsigned int current_extruder_id, const DynamicConfig *config_override, DynamicConfig *config_outputs, ContextData *context_data) const { client::MyContext context; context.external_config = this->external_config(); context.config = &this->config(); context.config_override = config_override; + context.config_outputs = config_outputs; context.current_extruder_id = current_extruder_id; context.context_data = context_data; return process_macro(templ, context); diff --git a/src/libslic3r/PlaceholderParser.hpp b/src/libslic3r/PlaceholderParser.hpp index fc184be77..a3f051558 100644 --- a/src/libslic3r/PlaceholderParser.hpp +++ b/src/libslic3r/PlaceholderParser.hpp @@ -55,8 +55,10 @@ public: // Fill in the template using a macro processing language. // Throws Slic3r::PlaceholderParserError on syntax or runtime error. - std::string process(const std::string &templ, unsigned int current_extruder_id = 0, const DynamicConfig *config_override = nullptr, ContextData *context = nullptr) const; - + std::string process(const std::string &templ, unsigned int current_extruder_id, const DynamicConfig *config_override, DynamicConfig *config_outputs, ContextData *context) const; + std::string process(const std::string &templ, unsigned int current_extruder_id = 0, const DynamicConfig *config_override = nullptr, ContextData *context = nullptr) const + { return this->process(templ, current_extruder_id, config_override, nullptr /* config_outputs */, context); } + // Evaluate a boolean expression using the full expressive power of the PlaceholderParser boolean expression syntax. // Throws Slic3r::PlaceholderParserError on syntax or runtime error. static bool evaluate_boolean_expression(const std::string &templ, const DynamicConfig &config, const DynamicConfig *config_override = nullptr); diff --git a/tests/libslic3r/test_placeholder_parser.cpp b/tests/libslic3r/test_placeholder_parser.cpp index 8ad6b243f..5248e089a 100644 --- a/tests/libslic3r/test_placeholder_parser.cpp +++ b/tests/libslic3r/test_placeholder_parser.cpp @@ -117,4 +117,17 @@ SCENARIO("Placeholder parser scripting", "[PlaceholderParser]") { SECTION("complex expression2") { REQUIRE(boolean_expression("printer_notes=~/.*PRINTER_VEwerfNDOR_PRUSA3D.*/ or printer_notes=~/.*PRINTertER_MODEL_MK2.*/ or (nozzle_diameter[0]==0.6 and num_extruders>1)")); } SECTION("complex expression3") { REQUIRE(! boolean_expression("printer_notes=~/.*PRINTER_VEwerfNDOR_PRUSA3D.*/ or printer_notes=~/.*PRINTertER_MODEL_MK2.*/ or (nozzle_diameter[0]==0.3 and num_extruders>1)")); } SECTION("enum expression") { REQUIRE(boolean_expression("gcode_flavor == \"marlin\"")); } + + SECTION("write to a scalar variable") { + DynamicConfig config_outputs; + config_outputs.set_key_value("writable_string", new ConfigOptionString()); + parser.process("{writable_string = \"Written\"}", 0, nullptr, &config_outputs, nullptr); + REQUIRE(parser.process("{writable_string}", 0, nullptr, &config_outputs, nullptr) == "Written"); + } + SECTION("write to a vector variable") { + DynamicConfig config_outputs; + config_outputs.set_key_value("writable_floats", new ConfigOptionFloats({ 0., 0., 0. })); + parser.process("{writable_floats[1] = 33}", 0, nullptr, &config_outputs, nullptr); + REQUIRE(config_outputs.opt_float("writable_floats", 1) == Approx(33.)); + } }