diff --git a/src/libslic3r/PlaceholderParser.cpp b/src/libslic3r/PlaceholderParser.cpp index c26197d27..cb0760d4d 100644 --- a/src/libslic3r/PlaceholderParser.cpp +++ b/src/libslic3r/PlaceholderParser.cpp @@ -173,7 +173,11 @@ namespace client OptWithPos(ConfigOptionConstPtr opt, boost::iterator_range it_range) : opt(opt), it_range(it_range) {} ConfigOptionConstPtr opt { nullptr }; bool writable { false }; + // -1 means it is a scalar variable, or it is a vector variable and index was not assigned yet or the whole vector is considered. + int index { -1 }; boost::iterator_range it_range; + + bool has_index() const { return index != -1; } }; template @@ -802,128 +806,127 @@ namespace client } template - static void scalar_variable_reference( + static void store_variable_index( const MyContext *ctx, - OptWithPos &opt, - expr &output) + OptWithPos &opt, + int index, + Iterator it_end, + OptWithPos &output) { - if (opt.opt->is_vector()) - ctx->throw_exception("Referencing a vector variable when scalar is expected", opt.it_range); - switch (opt.opt->type()) { - case coFloat: output.set_d(opt.opt->getFloat()); break; - case coInt: output.set_i(opt.opt->getInt()); break; - case coString: output.set_s(static_cast(opt.opt)->value); break; - case coPercent: output.set_d(opt.opt->getFloat()); break; - case coEnum: - case coPoint: output.set_s(opt.opt->serialize()); break; - case coBool: output.set_b(opt.opt->getBool()); break; - case coFloatOrPercent: - { - std::string opt_key(opt.it_range.begin(), opt.it_range.end()); - if (boost::ends_with(opt_key, "extrusion_width")) { - // Extrusion width supports defaults and a complex graph of dependencies. - output.set_d(Flow::extrusion_width(opt_key, *ctx, static_cast(ctx->current_extruder_id))); - } else if (! static_cast(opt.opt)->percent) { - // Not a percent, just return the value. - output.set_d(opt.opt->getFloat()); - } else { - // Resolve dependencies using the "ratio_over" link to a parent value. - const ConfigOptionDef *opt_def = print_config_def.get(opt_key); - assert(opt_def != nullptr); - double v = opt.opt->getFloat() * 0.01; // percent to ratio - for (;;) { - const ConfigOption *opt_parent = opt_def->ratio_over.empty() ? nullptr : ctx->resolve_symbol(opt_def->ratio_over); - if (opt_parent == nullptr) - ctx->throw_exception("FloatOrPercent variable failed to resolve the \"ratio_over\" dependencies", opt.it_range); - if (boost::ends_with(opt_def->ratio_over, "extrusion_width")) { - // Extrusion width supports defaults and a complex graph of dependencies. - assert(opt_parent->type() == coFloatOrPercent); - v *= Flow::extrusion_width(opt_def->ratio_over, static_cast(opt_parent), *ctx, static_cast(ctx->current_extruder_id)); - break; - } - if (opt_parent->type() == coFloat || opt_parent->type() == coFloatOrPercent) { - v *= opt_parent->getFloat(); - if (opt_parent->type() == coFloat || ! static_cast(opt_parent)->percent) - break; - v *= 0.01; // percent to ratio - } - // Continue one level up in the "ratio_over" hierarchy. - opt_def = print_config_def.get(opt_def->ratio_over); - assert(opt_def != nullptr); - } - output.set_d(v); - } - break; - } - default: - ctx->throw_exception("Unknown scalar variable type", opt.it_range); - } - output.it_range = opt.it_range; + 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; + output.it_range.end() = it_end; } template - static void vector_variable_reference( + static void variable_value( const MyContext *ctx, OptWithPos &opt, - int &index, - Iterator it_end, expr &output) { - if (opt.opt->is_scalar()) - ctx->throw_exception("Referencing a scalar variable when vector is expected", opt.it_range); - const ConfigOptionVectorBase *vec = static_cast(opt.opt); - if (vec->empty()) - ctx->throw_exception("Indexing an empty vector variable", opt.it_range); - size_t idx = (index < 0) ? 0 : (index >= int(vec->size())) ? 0 : size_t(index); - switch (opt.opt->type()) { - case coFloats: output.set_d(static_cast(opt.opt)->values[idx]); break; - case coInts: output.set_i(static_cast(opt.opt)->values[idx]); break; - case coStrings: output.set_s(static_cast(opt.opt)->values[idx]); break; - case coPercents: output.set_d(static_cast(opt.opt)->values[idx]); break; - case coPoints: output.set_s(to_string(static_cast(opt.opt)->values[idx])); break; - case coBools: output.set_b(static_cast(opt.opt)->values[idx] != 0); break; - //case coEnums: output.set_s(opt.opt->vserialize()[idx]); break; - default: - ctx->throw_exception("Unknown vector variable type", opt.it_range); + 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); + if (vec->empty()) + ctx->throw_exception("Indexing an empty vector variable", opt.it_range); + size_t idx = (opt.index < 0) ? 0 : (opt.index >= int(vec->size())) ? 0 : size_t(opt.index); + switch (opt.opt->type()) { + case coFloats: output.set_d(static_cast(opt.opt)->values[idx]); break; + case coInts: output.set_i(static_cast(opt.opt)->values[idx]); break; + case coStrings: output.set_s(static_cast(opt.opt)->values[idx]); break; + case coPercents: output.set_d(static_cast(opt.opt)->values[idx]); break; + case coPoints: output.set_s(to_string(static_cast(opt.opt)->values[idx])); break; + case coBools: output.set_b(static_cast(opt.opt)->values[idx] != 0); break; + //case coEnums: output.set_s(opt.opt->vserialize()[idx]); break; + default: + ctx->throw_exception("Unknown vector variable type", opt.it_range); + } + } else { + assert(opt.opt->is_scalar()); + switch (opt.opt->type()) { + case coFloat: output.set_d(opt.opt->getFloat()); break; + case coInt: output.set_i(opt.opt->getInt()); break; + case coString: output.set_s(static_cast(opt.opt)->value); break; + case coPercent: output.set_d(opt.opt->getFloat()); break; + case coEnum: + case coPoint: output.set_s(opt.opt->serialize()); break; + case coBool: output.set_b(opt.opt->getBool()); break; + case coFloatOrPercent: + { + std::string opt_key(opt.it_range.begin(), opt.it_range.end()); + if (boost::ends_with(opt_key, "extrusion_width")) { + // Extrusion width supports defaults and a complex graph of dependencies. + output.set_d(Flow::extrusion_width(opt_key, *ctx, static_cast(ctx->current_extruder_id))); + } else if (! static_cast(opt.opt)->percent) { + // Not a percent, just return the value. + output.set_d(opt.opt->getFloat()); + } else { + // Resolve dependencies using the "ratio_over" link to a parent value. + const ConfigOptionDef *opt_def = print_config_def.get(opt_key); + assert(opt_def != nullptr); + double v = opt.opt->getFloat() * 0.01; // percent to ratio + for (;;) { + const ConfigOption *opt_parent = opt_def->ratio_over.empty() ? nullptr : ctx->resolve_symbol(opt_def->ratio_over); + if (opt_parent == nullptr) + ctx->throw_exception("FloatOrPercent variable failed to resolve the \"ratio_over\" dependencies", opt.it_range); + if (boost::ends_with(opt_def->ratio_over, "extrusion_width")) { + // Extrusion width supports defaults and a complex graph of dependencies. + assert(opt_parent->type() == coFloatOrPercent); + v *= Flow::extrusion_width(opt_def->ratio_over, static_cast(opt_parent), *ctx, static_cast(ctx->current_extruder_id)); + break; + } + if (opt_parent->type() == coFloat || opt_parent->type() == coFloatOrPercent) { + v *= opt_parent->getFloat(); + if (opt_parent->type() == coFloat || ! static_cast(opt_parent)->percent) + break; + v *= 0.01; // percent to ratio + } + // Continue one level up in the "ratio_over" hierarchy. + opt_def = print_config_def.get(opt_def->ratio_over); + assert(opt_def != nullptr); + } + output.set_d(v); + } + break; + } + default: + ctx->throw_exception("Unknown scalar variable type", opt.it_range); + } } - output.it_range = boost::iterator_range(opt.it_range.begin(), it_end); + + output.it_range = opt.it_range; } // Return a boolean value, true if the scalar variable referenced by "opt" is nullable and it has a nil value. - template - static void is_nil_test_scalar( - const MyContext *ctx, - OptWithPos &opt, - expr &output) - { - if (opt.opt->is_vector()) - ctx->throw_exception("Referencing a vector variable when scalar is expected", opt.it_range); - output.set_b(opt.opt->is_nil()); - output.it_range = opt.it_range; - } - // Return a boolean value, true if an element of a vector variable referenced by "opt[index]" is nullable and it has a nil value. template - static void is_nil_test_vector( + static void is_nil_test( const MyContext *ctx, OptWithPos &opt, - int &index, - Iterator it_end, expr &output) { - if (opt.opt->is_scalar()) - ctx->throw_exception("Referencing a scalar variable when vector is expected", opt.it_range); - const ConfigOptionVectorBase *vec = static_cast(opt.opt); - if (vec->empty()) - ctx->throw_exception("Indexing an empty vector variable", opt.it_range); - size_t idx = (index < 0) ? 0 : (index >= int(vec->size())) ? 0 : size_t(index); - output.set_b(static_cast(opt.opt)->is_nil(idx)); - output.it_range = boost::iterator_range(opt.it_range.begin(), it_end); + 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); + if (vec->empty()) + ctx->throw_exception("Indexing an empty vector variable", opt.it_range); + output.set_b(static_cast(opt.opt)->is_nil(opt.index >= int(vec->size()) ? 0 : size_t(opt.index))); + } else { + assert(opt.opt->is_scalar()); + output.set_b(opt.opt->is_nil()); + } + output.it_range = opt.it_range; } // Decoding a scalar variable symbol "opt", assigning it a value of "param". template - static void scalar_variable_assign( + static void variable_assign( const MyContext *ctx, OptWithPos &opt, expr ¶m, @@ -932,83 +935,71 @@ namespace client { 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); + if (opt.opt->is_vector()) { + if (! opt.has_index()) + ctx->throw_exception("Referencing an output vector variable when scalar 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 (opt.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[opt.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[opt.index] = param.as_i(); + break; + case coStrings: + static_cast(vec)->values[opt.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[opt.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[opt.index] = param.b(); + break; + default: + ctx->throw_exception("Unsupported output vector variable type", opt.it_range); + } + } else { + assert(opt.opt->is_scalar()); + 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(); } @@ -1110,9 +1101,9 @@ namespace client { "multiplicative_expression", "Expecting an expression." }, { "unary_expression", "Expecting an expression." }, { "optional_parameter", "Expecting a closing brace or an optional parameter." }, - { "scalar_variable_reference", "Expecting a scalar variable reference."}, - { "is_nil_test", "Expecting a scalar variable reference."}, { "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."} }; @@ -1359,13 +1350,8 @@ 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)] - ); + (variable_reference(_r1) >> '=' > additive_expression(_r1)) + [px::bind(&MyContext::variable_assign, _r1, _1, _2, _val)]; struct FactorActions { static void set_start_pos(Iterator &start_pos, expr &out) @@ -1392,7 +1378,7 @@ namespace client static void noexpr(expr &out) { out.reset(); } }; unary_expression = iter_pos[px::bind(&FactorActions::set_start_pos, _1, _val)] >> ( - scalar_variable_reference(_r1) [ _val = _1 ] + 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) ] @@ -1424,27 +1410,20 @@ namespace client ); optional_parameter.name("optional_parameter"); - scalar_variable_reference = - variable_reference(_r1)[_a=_1] >> - ( - ('[' > additive_expression(_r1)[px::bind(&MyContext::evaluate_index, _1, _b)] > ']' > - iter_pos[px::bind(&MyContext::vector_variable_reference, _r1, _a, _b, _1, _val)]) - | eps[px::bind(&MyContext::scalar_variable_reference, _r1, _a, _val)] - ); - scalar_variable_reference.name("scalar variable reference"); + is_nil_test = variable_reference(_r1)[px::bind(&MyContext::is_nil_test, _r1, _1, _val)]; + is_nil_test.name("is_nil test"); - variable_reference = identifier - [ px::bind(&MyContext::resolve_variable, _r1, _1, _val) ]; + variable_reference = + variable(_r1)[_a=_1] >> + ( + ('[' > additive_expression(_r1)[px::bind(&MyContext::evaluate_index, _1, _b)] > ']' > iter_pos) + [px::bind(&MyContext::store_variable_index, _r1, _a, _b, _2, _val)] + | eps[_val=_a] + ); variable_reference.name("variable reference"); - is_nil_test = - variable_reference(_r1)[_a=_1] >> - ( - ('[' > additive_expression(_r1)[px::bind(&MyContext::evaluate_index, _1, _b)] > ']' > - iter_pos[px::bind(&MyContext::is_nil_test_vector, _r1, _a, _b, _1, _val)]) - | eps[px::bind(&MyContext::is_nil_test_scalar, _r1, _a, _val)] - ); - is_nil_test.name("is_nil test"); + variable = identifier[ px::bind(&MyContext::resolve_variable, _r1, _1, _val) ]; + variable.name("variable reference"); regular_expression = raw[lexeme['/' > *((utf8char - char_('\\') - char_('/')) | ('\\' > char_)) > '/']]; regular_expression.name("regular_expression"); @@ -1488,8 +1467,8 @@ namespace client debug(multiplicative_expression); debug(unary_expression); debug(optional_parameter); - debug(scalar_variable_reference); debug(variable_reference); + debug(variable); debug(is_nil_test); debug(regular_expression); } @@ -1533,11 +1512,11 @@ namespace client // Evaluate boolean expression into bool. qi::rule bool_expr_eval; // Reference of a scalar variable, or reference to a field of a vector variable. - qi::rule(const MyContext*), qi::locals, int>, spirit_encoding::space_type> scalar_variable_reference; + qi::rule(const MyContext*), qi::locals, int>, spirit_encoding::space_type> variable_reference; // Rule to translate an identifier to a ConfigOption, or to fail. - qi::rule(const MyContext*), spirit_encoding::space_type> variable_reference; + qi::rule(const MyContext*), spirit_encoding::space_type> variable; // Evaluating whether a nullable variable is nil. - qi::rule(const MyContext*), qi::locals, int>, spirit_encoding::space_type> is_nil_test; + qi::rule(const MyContext*), 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;