diff --git a/src/libslic3r/PlaceholderParser.cpp b/src/libslic3r/PlaceholderParser.cpp index 55e7235eb..a26841082 100644 --- a/src/libslic3r/PlaceholderParser.cpp +++ b/src/libslic3r/PlaceholderParser.cpp @@ -170,7 +170,7 @@ namespace client template struct OptWithPos { OptWithPos() {} - OptWithPos(ConfigOptionConstPtr opt, boost::iterator_range it_range) : opt(opt), it_range(it_range) {} + OptWithPos(ConfigOptionConstPtr opt, boost::iterator_range it_range, bool writable = false) : opt(opt), it_range(it_range), writable(writable) {} 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. @@ -720,9 +720,14 @@ namespace client } struct MyContext : public ConfigOptionResolver { + // Config provided as a parameter to PlaceholderParser invocation, overriding PlaceholderParser stored config. const DynamicConfig *external_config = nullptr; + // Config stored inside PlaceholderParser. const DynamicConfig *config = nullptr; + // Config provided as a parameter to PlaceholderParser invocation, evaluated after the two configs above. const DynamicConfig *config_override = nullptr; + // Config provided as a parameter to PlaceholderParser invocation, containing variables that will be read out + // and processed by the PlaceholderParser callee. mutable DynamicConfig *config_outputs = nullptr; // Local variables, read / write mutable DynamicConfig config_local; @@ -737,6 +742,7 @@ namespace client // Table to translate symbol tag to a human readable error message. static std::map tag_to_error_message; + // 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; } const ConfigOption* optptr(const t_config_option_key &opt_key) const override @@ -762,13 +768,13 @@ namespace client out = this->config_local.optptr(opt_key); return out; } - void store_new_variable(const std::string &opt_key, ConfigOption *opt, bool global_variable) { - assert(opt != nullptr); + void store_new_variable(const std::string &opt_key, std::unique_ptr &&opt, bool global_variable) { + assert(opt); if (global_variable) { assert(this->context_data != nullptr && this->context_data->global_config); - this->context_data->global_config->set_key_value(opt_key, opt); + this->context_data->global_config->set_key_value(opt_key, opt.release()); } else - this->config_local.set_key_value(opt_key ,opt); + this->config_local.set_key_value(opt_key, opt.release()); } template @@ -844,9 +850,10 @@ namespace client boost::iterator_range &opt_key, OptWithPos &output) { - const ConfigOption *opt = ctx->resolve_symbol(std::string(opt_key.begin(), opt_key.end())); + 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(std::string(opt_key.begin(), opt_key.end())); + opt = ctx->resolve_output_symbol(key); if (opt == nullptr) ctx->throw_exception("Not a variable name", opt_key); output.writable = true; @@ -872,83 +879,233 @@ namespace client output.it_range.end() = it_end; } + // Evaluating a scalar variable into expr, + // all possible ConfigOption types are supported. + template + static void scalar_variable_to_expr( + const MyContext *ctx, + OptWithPos &opt, + expr &output) + { + 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("Unsupported scalar variable type", opt.it_range); + } + } + + // Evaluating one element of a vector variable. + // all possible ConfigOption types are supported. + template + static void vector_element_to_expr( + const MyContext *ctx, + OptWithPos &opt, + expr &output) + { + assert(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("Unsupported vector variable type", opt.it_range); + } + } + + template + static void check_writable(const MyContext *ctx, OptWithPos &opt) { + if (! opt.writable) + ctx->throw_exception("Cannot modify a read-only variable", opt.it_range); + } + + template + static void check_numeric(const expr ¶m) { + if (! param.numeric_type()) + param.throw_exception("Right side is not a numeric expression"); + }; + + template + static size_t evaluate_count(const expr &expr_count) { + if (expr_count.type() != expr::TYPE_INT) + expr_count.throw_exception("Expected number of elements to fill a vector with."); + int count = expr_count.i(); + if (count < 0) + expr_count.throw_exception("Negative number of elements specified."); + return size_t(count); + }; + + template + static void scalar_variable_assign_scalar( + const MyContext *ctx, + OptWithPos &lhs, + const expr &rhs) + { + assert(lhs.opt->is_scalar()); + check_writable(ctx, lhs); + ConfigOption *wropt = const_cast(lhs.opt); + switch (wropt->type()) { + case coFloat: + check_numeric(rhs); + static_cast(wropt)->value = rhs.as_d(); + break; + case coInt: + check_numeric(rhs); + static_cast(wropt)->value = rhs.as_i(); + break; + case coString: + static_cast(wropt)->value = rhs.to_string(); + break; + case coPercent: + check_numeric(rhs); + static_cast(wropt)->value = rhs.as_d(); + break; + case coBool: + if (rhs.type() != expr::TYPE_BOOL) + ctx->throw_exception("Right side is not a boolean expression", rhs.it_range); + static_cast(wropt)->value = rhs.b(); + break; + default: + ctx->throw_exception("Unsupported output scalar variable type", lhs.it_range); + } + } + + template + static void vector_variable_element_assign_scalar( + const MyContext *ctx, + OptWithPos &lhs, + const expr &rhs) + { + assert(lhs.opt->is_vector()); + check_writable(ctx, lhs); + if (! lhs.has_index()) + ctx->throw_exception("Referencing an output vector variable when scalar is expected", lhs.it_range); + ConfigOptionVectorBase *vec = const_cast(static_cast(lhs.opt)); + if (vec->empty()) + ctx->throw_exception("Indexing an empty vector variable", lhs.it_range); + if (lhs.index >= int(vec->size())) + ctx->throw_exception("Index out of range", lhs.it_range); + switch (lhs.opt->type()) { + case coFloats: + check_numeric(rhs); + static_cast(vec)->values[lhs.index] = rhs.as_d(); + break; + case coInts: + check_numeric(rhs); + static_cast(vec)->values[lhs.index] = rhs.as_i(); + break; + case coStrings: + static_cast(vec)->values[lhs.index] = rhs.to_string(); + break; + case coPercents: + check_numeric(rhs); + static_cast(vec)->values[lhs.index] = rhs.as_d(); + break; + case coBools: + if (rhs.type() != expr::TYPE_BOOL) + ctx->throw_exception("Right side is not a boolean expression", rhs.it_range); + static_cast(vec)->values[lhs.index] = rhs.b(); + break; + default: + ctx->throw_exception("Unsupported output vector variable type", lhs.it_range); + } + } + + template + static void vector_variable_assign_expr_with_count( + const MyContext *ctx, + OptWithPos &lhs, + const expr &rhs_count, + const expr &rhs_value) + { + size_t count = evaluate_count(rhs_count); + auto *opt = const_cast(lhs.opt); + switch (lhs.opt->type()) { + case coFloats: + check_numeric(rhs_value); + static_cast(opt)->values.assign(count, rhs_value.as_d()); + break; + case coInts: + check_numeric(rhs_value); + static_cast(opt)->values.assign(count, rhs_value.as_i()); + break; + case coStrings: + static_cast(opt)->values.assign(count, rhs_value.to_string()); + break; + case coBools: + if (rhs_value.type() != expr::TYPE_BOOL) + rhs_value.throw_exception("Right side is not a boolean expression"); + static_cast(opt)->values.assign(count, rhs_value.b()); + break; + default: assert(false); + } + } + template static void variable_value( const MyContext *ctx, OptWithPos &opt, expr &output) { - 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); - } - } - + 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; } @@ -974,6 +1131,7 @@ namespace client output.it_range = opt.it_range; } + // Reference to an existing symbol, or a name of a new symbol. template struct NewOldVariable { std::string name; @@ -1010,240 +1168,148 @@ namespace client out.it_range = it_range; } - template - static void new_scalar_variable( - const MyContext *ctx, - bool global_variable, - NewOldVariable &output_variable, - const expr ¶m) - { - auto check_numeric = [](const expr ¶m) { - if (! param.numeric_type()) - param.throw_exception("Right side is not a numeric expression"); - }; - if (output_variable.opt) { - if (output_variable.opt->is_vector()) - param.throw_exception("Cannot assign a scalar value to a vector variable."); - switch (output_variable.opt->type()) { - case coFloat: - check_numeric(param); - static_cast(output_variable.opt)->value = param.as_d(); - break; - case coInt: - check_numeric(param); - static_cast(output_variable.opt)->value = param.as_i(); - break; - case coString: - static_cast(output_variable.opt)->value = param.to_string(); - break; - case coBool: - if (param.type() != expr::TYPE_BOOL) - param.throw_exception("Right side is not a boolean expression"); - static_cast(output_variable.opt)->value = param.b(); - break; - default: assert(false); - } - } else { - switch (param.type()) { - case expr::TYPE_BOOL: output_variable.opt = new ConfigOptionBool(param.b()); break; - case expr::TYPE_INT: output_variable.opt = new ConfigOptionInt(param.i()); break; - case expr::TYPE_DOUBLE: output_variable.opt = new ConfigOptionFloat(param.d()); break; - case expr::TYPE_STRING: output_variable.opt = new ConfigOptionString(param.s()); break; - default: assert(false); - } - const_cast(ctx)->store_new_variable(output_variable.name, output_variable.opt, global_variable); - } - } - - template - static void check_writable(const MyContext *ctx, OptWithPos &opt) { - if (! opt.writable) - ctx->throw_exception("Cannot modify a read-only variable", opt.it_range); - } - // Decoding a scalar variable symbol "opt", assigning it a value of "param". template - static void assign_scalar_variable( + static void scalar_variable_assign_scalar_expression( const MyContext *ctx, OptWithPos &opt, - expr ¶m) + const expr ¶m) { check_writable(ctx, opt); - auto check_numeric = [](const expr ¶m) { - if (! param.numeric_type()) - param.throw_exception("Right side is not a numeric expression"); - }; - 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: - check_numeric(param); - static_cast(vec)->values[opt.index] = param.as_d(); - break; - case coInts: - check_numeric(param); - static_cast(vec)->values[opt.index] = param.as_i(); - break; - case coStrings: - static_cast(vec)->values[opt.index] = param.to_string(); - break; - case coPercents: - check_numeric(param); - 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: - check_numeric(param); - static_cast(wropt)->value = param.as_d(); - break; - case coInt: - check_numeric(param); - static_cast(wropt)->value = param.as_i(); - break; - case coString: - static_cast(wropt)->value = param.to_string(); - break; - case coPercent: - check_numeric(param); - 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); - } - } + if (opt.opt->is_vector()) + vector_variable_element_assign_scalar(ctx, opt, param); + else + scalar_variable_assign_scalar(ctx, opt, param); } template - static void new_vector_variable_array( + static void scalar_variable_new_from_scalar_expression( const MyContext *ctx, bool global_variable, - NewOldVariable &output_variable, - const expr &expr_count, - const expr &expr_value) + NewOldVariable &lhs, + const expr &rhs) { - auto check_numeric = [](const expr ¶m) { - if (! param.numeric_type()) - param.throw_exception("Right side is not a numeric expression"); - }; - auto evaluate_count = [](const expr &expr_count) -> size_t { - if (expr_count.type() != expr::TYPE_INT) - expr_count.throw_exception("Expected number of elements to fill a vector with."); - int count = expr_count.i(); - if (count < 0) - expr_count.throw_exception("Negative number of elements specified."); - return size_t(count); - }; - if (output_variable.opt) { - if (output_variable.opt->is_scalar()) - expr_value.throw_exception("Cannot assign a vector value to a scalar variable."); - size_t count = evaluate_count(expr_count); - switch (output_variable.opt->type()) { - case coFloats: - check_numeric(expr_value); - static_cast(output_variable.opt)->values.assign(count, expr_value.as_d()); - break; - case coInts: - check_numeric(expr_value); - static_cast(output_variable.opt)->values.assign(count, expr_value.as_i()); - break; - case coStrings: - static_cast(output_variable.opt)->values.assign(count, expr_value.to_string()); - break; - case coBools: - if (expr_value.type() != expr::TYPE_BOOL) - expr_value.throw_exception("Right side is not a boolean expression"); - static_cast(output_variable.opt)->values.assign(count, expr_value.b()); - break; - default: assert(false); - } + 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 }; + scalar_variable_assign_scalar(ctx, lhs_opt, rhs); } else { - size_t count = evaluate_count(expr_count); - switch (expr_value.type()) { - case expr::TYPE_BOOL: output_variable.opt = new ConfigOptionBools(count, expr_value.b()); break; - case expr::TYPE_INT: output_variable.opt = new ConfigOptionInts(count, expr_value.i()); break; - case expr::TYPE_DOUBLE: output_variable.opt = new ConfigOptionFloats(count, expr_value.d()); break; - case expr::TYPE_STRING: output_variable.opt = new ConfigOptionStrings(count, expr_value.s()); break; + std::unique_ptr opt_new; + switch (rhs.type()) { + case expr::TYPE_BOOL: opt_new = std::make_unique(rhs.b()); break; + case expr::TYPE_INT: opt_new = std::make_unique(rhs.i()); break; + case expr::TYPE_DOUBLE: opt_new = std::make_unique(rhs.d()); break; + case expr::TYPE_STRING: opt_new = std::make_unique(rhs.s()); break; default: assert(false); } - const_cast(ctx)->store_new_variable(output_variable.name, output_variable.opt, global_variable); + const_cast(ctx)->store_new_variable(lhs.name, std::move(opt_new), global_variable); } } template - static void assign_vector_variable_array( + static void vector_variable_new_from_array( + const MyContext *ctx, + bool global_variable, + NewOldVariable &lhs, + const expr &rhs_count, + const expr &rhs_value) + { + 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 }; + vector_variable_assign_expr_with_count(ctx, lhs_opt, rhs_count, rhs_value); + } else { + size_t count = evaluate_count(rhs_count); + std::unique_ptr opt_new; + switch (rhs_value.type()) { + case expr::TYPE_BOOL: opt_new = std::make_unique(count, rhs_value.b()); break; + case expr::TYPE_INT: opt_new = std::make_unique(count, rhs_value.i()); break; + case expr::TYPE_DOUBLE: opt_new = std::make_unique(count, rhs_value.d()); break; + case expr::TYPE_STRING: opt_new = std::make_unique(count, rhs_value.s()); break; + default: assert(false); + } + const_cast(ctx)->store_new_variable(lhs.name, std::move(opt_new), global_variable); + } + } + + template + static void vector_variable_assign_array( const MyContext *ctx, OptWithPos &lhs, - const expr &expr_count, - const expr &expr_value) + const expr &rhs_count, + const expr &rhs_value) { check_writable(ctx, lhs); - auto check_numeric = [](const expr ¶m) { - if (! param.numeric_type()) - param.throw_exception("Right side is not a numeric expression"); - }; - auto evaluate_count = [](const expr &expr_count) -> size_t { - if (expr_count.type() != expr::TYPE_INT) - expr_count.throw_exception("Expected number of elements to fill a vector with."); - int count = expr_count.i(); - if (count < 0) - expr_count.throw_exception("Negative number of elements specified."); - return size_t(count); - }; if (lhs.opt->is_scalar()) - expr_value.throw_exception("Cannot assign a vector value to a scalar variable."); - auto *opt = const_cast(lhs.opt); - size_t count = evaluate_count(expr_count); + 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 + static void fill_vector_from_initializer_list(ConfigOption *opt, const std::vector> &il, RightValueEvaluate rv_eval) { + auto& out = static_cast(opt)->values; + out.clear(); + out.reserve(il.size()); + for (const expr& i : il) + out.emplace_back(rv_eval(i)); + } + + template + static void vector_variable_assign_initializer_list( + const MyContext *ctx, + OptWithPos &lhs, + const std::vector> &il) + { + check_writable(ctx, lhs); + 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: - check_numeric(expr_value); - static_cast(opt)->values.assign(count, expr_value.as_d()); + check_numeric_vector(il); + fill_vector_from_initializer_list(opt, il, [](auto &v){ return v.as_d(); }); break; case coInts: - check_numeric(expr_value); - static_cast(opt)->values.assign(count, expr_value.as_i()); + check_numeric_vector(il); + fill_vector_from_initializer_list(opt, il, [](auto &v){ return v.as_i(); }); break; case coStrings: - static_cast(opt)->values.assign(count, expr_value.to_string()); + fill_vector_from_initializer_list(opt, il, [](auto &v){ return v.to_string(); }); break; case coBools: - if (expr_value.type() != expr::TYPE_BOOL) - expr_value.throw_exception("Right side is not a boolean expression"); - static_cast(opt)->values.assign(count, expr_value.b()); + for (auto &i : il) + if (i.type() != expr::TYPE_BOOL) + i.throw_exception("Right side is not a boolean expression"); + fill_vector_from_initializer_list(opt, il, [](auto &v){ return v.b(); }); break; default: assert(false); } } template - static void new_vector_variable_initializer_list( + static void vector_variable_new_from_initializer_list( const MyContext *ctx, bool global_variable, - NewOldVariable &output_variable, + NewOldVariable &lhs, const std::vector> &il) { - if (! output_variable.opt) { + 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 { + // Allocate a new vector variable. // First guesstimate type of the output vector. size_t num_bool = 0; size_t num_int = 0; @@ -1257,186 +1323,53 @@ namespace client case expr::TYPE_STRING: ++ num_string; break; default: assert(false); } + std::unique_ptr opt_new; if (num_string > 0) // Convert everything to strings. - output_variable.opt = new ConfigOptionStrings(); + opt_new = std::make_unique(); else if (num_bool > 0) { if (num_double + num_int > 0) ctx->throw_exception("Right side is not valid: Mixing numeric and boolean types.", boost::iterator_range{ il.front().it_range.begin(), il.back().it_range.end() }); - output_variable.opt = new ConfigOptionBools(); - } else + opt_new = std::make_unique(); + } else { // Output is numeric. - output_variable.opt = num_double == 0 ? static_cast(new ConfigOptionInts()) : static_cast(new ConfigOptionFloats()); - const_cast(ctx)->store_new_variable(output_variable.name, output_variable.opt, global_variable); - } - - auto check_numeric = [](const std::vector> &il) { - for (auto& i : il) - if (!i.numeric_type()) - i.throw_exception("Right side is not a numeric expression"); - }; - - if (output_variable.opt->is_scalar()) - ctx->throw_exception("Cannot assign a vector value to a scalar variable.", output_variable.it_range); - - switch (output_variable.opt->type()) { - case coFloats: - { - check_numeric(il); - auto &out = static_cast(output_variable.opt)->values; - out.clear(); - out.reserve(il.size()); - for (auto &i : il) - out.emplace_back(i.as_d()); - break; - } - case coInts: - { - check_numeric(il); - auto &out = static_cast(output_variable.opt)->values; - out.clear(); - out.reserve(il.size()); - for (auto& i : il) - out.emplace_back(i.as_i()); - break; - } - case coStrings: - { - auto &out = static_cast(output_variable.opt)->values; - out.clear(); - out.reserve(il.size()); - for (auto &i : il) - out.emplace_back(i.to_string()); - break; - } - case coBools: - { - auto &out = static_cast(output_variable.opt)->values; - out.clear(); - out.reserve(il.size()); - for (auto &i : il) - if (i.type() == expr::TYPE_BOOL) - out.emplace_back(i.b()); - else - i.throw_exception("Right side is not a boolean expression"); - break; - } - default: - assert(false); + if (num_double == 0) + opt_new = std::make_unique(); + else + opt_new = std::make_unique(); + } + OptWithPos lhs_opt{ opt_new.get(), lhs.it_range, true }; + vector_variable_assign_initializer_list(ctx, lhs_opt, il); + const_cast(ctx)->store_new_variable(lhs.name, std::move(opt_new), global_variable); } } template - static void assign_vector_variable_initializer_list( - const MyContext *ctx, - OptWithPos &lhs, - const std::vector> &il) + static void copy_vector_variable_to_vector_variable( + const MyContext *ctx, + OptWithPos &lhs, + const OptWithPos &rhs) { check_writable(ctx, lhs); - auto check_numeric = [](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: - { - check_numeric(il); - auto &out = static_cast(opt)->values; - out.clear(); - out.reserve(il.size()); - for (auto &i : il) - out.emplace_back(i.as_d()); - break; + assert(rhs.opt->is_vector()); + if (! lhs.opt->is_vector()) + ctx->throw_exception("Cannot assign vector to a scalar", lhs.it_range); + if (lhs.opt->type() != rhs.opt->type()) { + // Vector types are not compatible. + switch (lhs.opt->type()) { + case coFloats: + ctx->throw_exception("Left hand side is a float vector, while the right hand side is not.", lhs.it_range); + case coInts: + ctx->throw_exception("Left hand side is an int vector, while the right hand side is not.", lhs.it_range); + case coStrings: + ctx->throw_exception("Left hand side is a string vector, while the right hand side is not.", lhs.it_range); + case coBools: + ctx->throw_exception("Left hand side is a bool vector, while the right hand side is not.", lhs.it_range); + default: + ctx->throw_exception("Left hand side / right hand side vectors are not compatible.", lhs.it_range); + } } - case coInts: - { - check_numeric(il); - auto &out = static_cast(opt)->values; - out.clear(); - out.reserve(il.size()); - for (auto& i : il) - out.emplace_back(i.as_i()); - break; - } - case coStrings: - { - auto &out = static_cast(opt)->values; - out.clear(); - out.reserve(il.size()); - for (auto &i : il) - out.emplace_back(i.to_string()); - break; - } - case coBools: - { - auto &out = static_cast(opt)->values; - out.clear(); - out.reserve(il.size()); - for (auto &i : il) - if (i.type() == expr::TYPE_BOOL) - out.emplace_back(i.b()); - else - i.throw_exception("Right side is not a boolean expression"); - break; - } - default: - assert(false); - } - } - - template - static bool new_vector_variable_copy( - const MyContext *ctx, - bool global_variable, - NewOldVariable &output_variable, - const OptWithPos &src_variable) - { - if (! is_vector_variable_reference(src_variable)) - // Skip parsing this branch, bactrack. - return false; - - if (! output_variable.opt) { - if (one_of(src_variable.opt->type(), { coFloats, coInts, coStrings, coBools })) - output_variable.opt = src_variable.opt->clone(); - else if (src_variable.opt->type() == coPercents) - output_variable.opt = new ConfigOptionFloats(static_cast(src_variable.opt)->values); - else - ctx->throw_exception("Duplicating this vector variable is not supported", src_variable.it_range); - const_cast(ctx)->store_new_variable(output_variable.name, output_variable.opt, global_variable); - } - - switch (output_variable.opt->type()) { - case coFloats: - if (output_variable.opt->type() != coFloats) - ctx->throw_exception("Left hand side is a float vector, while the right hand side is not.", boost::iterator_range{ output_variable.it_range.begin(), src_variable.it_range.end() }); - static_cast(output_variable.opt)->values = static_cast(src_variable.opt)->values; - break; - case coInts: - if (output_variable.opt->type() != coInts) - ctx->throw_exception("Left hand side is an int vector, while the right hand side is not.", boost::iterator_range{ output_variable.it_range.begin(), src_variable.it_range.end() }); - static_cast(output_variable.opt)->values = static_cast(src_variable.opt)->values; - break; - case coStrings: - if (output_variable.opt->type() != coStrings) - ctx->throw_exception("Left hand side is a string vector, while the right hand side is not.", boost::iterator_range{ output_variable.it_range.begin(), src_variable.it_range.end() }); - static_cast(output_variable.opt)->values = static_cast(src_variable.opt)->values; - break; - case coBools: - if (output_variable.opt->type() != coBools) - ctx->throw_exception("Left hand side is a bool vector, while the right hand side is not.", boost::iterator_range{ output_variable.it_range.begin(), src_variable.it_range.end() }); - static_cast(output_variable.opt)->values = static_cast(src_variable.opt)->values; - break; - default: - assert(false); - } - // Continue parsing. - return true; + const_cast(lhs.opt)->set(rhs.opt); } template @@ -1445,49 +1378,53 @@ namespace client } template - static bool assign_vector_variable_copy( + static bool vector_variable_new_from_copy( const MyContext *ctx, - OptWithPos &lhs, - const OptWithPos &src_variable) + bool global_variable, + NewOldVariable &lhs, + const OptWithPos &rhs) { - if (! is_vector_variable_reference(src_variable)) + 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; + } else { // Skip parsing this branch, bactrack. return false; - - check_writable(ctx, lhs); - - auto *opt = const_cast(lhs.opt); - switch (lhs.opt->type()) { - case coFloats: - if (lhs.opt->type() != coFloats) - ctx->throw_exception("Left hand side is a float vector, while the right hand side is not.", lhs.it_range); - static_cast(opt)->values = static_cast(src_variable.opt)->values; - break; - case coInts: - if (lhs.opt->type() != coInts) - ctx->throw_exception("Left hand side is an int vector, while the right hand side is not.", lhs.it_range); - static_cast(opt)->values = static_cast(src_variable.opt)->values; - break; - case coStrings: - if (lhs.opt->type() != coStrings) - ctx->throw_exception("Left hand side is a string vector, while the right hand side is not.", lhs.it_range); - static_cast(opt)->values = static_cast(src_variable.opt)->values; - break; - case coBools: - if (lhs.opt->type() != coBools) - ctx->throw_exception("Left hand side is a bool vector, while the right hand side is not.", lhs.it_range); - static_cast(opt)->values = static_cast(src_variable.opt)->values; - break; - default: - assert(false); } - - // Continue parsing. - return true; } template - static void new_vector_variable_initializer_list_append(std::vector> &list, expr &expr) + 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; + } + } + + template + static void initializer_list_append(std::vector> &list, expr &expr) { list.emplace_back(std::move(expr)); } @@ -1910,36 +1847,36 @@ namespace client 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) > ')') - [px::bind(&MyContext::assign_vector_variable_initializer_list, _r1, _a, _1)] + [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::assign_vector_variable_copy, _r1, _a, _1)] + [px::ref(qi::_pass) = px::bind(&MyContext::vector_variable_assign_copy, _r1, _a, _1)] // Would NOT consume '(' conditional_expression ')' because such value was consumed with the expression above. | conditional_expression(_r1) - [px::bind(&MyContext::assign_scalar_variable, _r1, _a, _1)] + [px::bind(&MyContext::scalar_variable_assign_scalar_expression, _r1, _a, _1)] | (kw["array"] > "(" > additive_expression(_r1) > "," > conditional_expression(_r1) > ")") - [px::bind(&MyContext::assign_vector_variable_array, _r1, _a, _1, _2)] + [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) > ')') - [px::bind(&MyContext::new_vector_variable_initializer_list, _r1, _a, _b, _1)] + [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::new_vector_variable_copy, _r1, _a, _b, _1)] + [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::new_scalar_variable, _r1, _a, _b, _1)] + [px::bind(&MyContext::scalar_variable_new_from_scalar_expression, _r1, _a, _b, _1)] | (kw["array"] > "(" > additive_expression(_r1) > "," > conditional_expression(_r1) > ")") - [px::bind(&MyContext::new_vector_variable_array, _r1, _a, _b, _1, _2)] + [px::bind(&MyContext::vector_variable_new_from_array, _r1, _a, _b, _1, _2)] ); new_variable_initializer_list = - conditional_expression(_r1)[px::bind(&MyContext::new_vector_variable_initializer_list_append, _val, _1)] >> - *(lit(',') > conditional_expression(_r1)[px::bind(&MyContext::new_vector_variable_initializer_list_append, _val, _1)]); + conditional_expression(_r1)[px::bind(&MyContext::initializer_list_append, _val, _1)] >> + *(lit(',') > conditional_expression(_r1)[px::bind(&MyContext::initializer_list_append, _val, _1)]); struct FactorActions { static void set_start_pos(Iterator &start_pos, expr &out)