WIP: PlaceholderParser support for writable output variables.

This commit is contained in:
Vojtech Bubnik 2023-03-20 07:48:26 +01:00
parent 39bca4420c
commit b9d8fe7118
3 changed files with 134 additions and 7 deletions

View File

@ -171,7 +171,8 @@ namespace client
struct OptWithPos {
OptWithPos() {}
OptWithPos(ConfigOptionConstPtr opt, boost::iterator_range<Iterator> it_range) : opt(opt), it_range(it_range) {}
ConfigOptionConstPtr opt = nullptr;
ConfigOptionConstPtr opt { nullptr };
bool writable { false };
boost::iterator_range<Iterator> 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 <typename Iterator>
static void legacy_variable_expansion(
@ -788,8 +791,12 @@ namespace client
OptWithPos<Iterator> &output)
{
const ConfigOption *opt = ctx->resolve_symbol(std::string(opt_key.begin(), opt_key.end()));
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<Iterator>(opt.it_range.begin(), it_end);
}
// Decoding a scalar variable symbol "opt", assigning it a value of "param".
template <typename Iterator>
static void scalar_variable_assign(
const MyContext *ctx,
OptWithPos<Iterator> &opt,
expr<Iterator> &param,
// 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<ConfigOption*>(opt.opt);
switch (wropt->type()) {
case coFloat:
if (param.type() != expr<Iterator>::TYPE_INT && param.type() != expr<Iterator>::TYPE_DOUBLE)
ctx->throw_exception("Right side is not a numeric expression", param.it_range);
static_cast<ConfigOptionFloat*>(wropt)->value = param.as_d();
break;
case coInt:
if (param.type() != expr<Iterator>::TYPE_INT && param.type() != expr<Iterator>::TYPE_DOUBLE)
ctx->throw_exception("Right side is not a numeric expression", param.it_range);
static_cast<ConfigOptionInt*>(wropt)->value = param.as_i();
break;
case coString:
static_cast<ConfigOptionString*>(wropt)->value = param.to_string();
break;
case coPercent:
if (param.type() != expr<Iterator>::TYPE_INT && param.type() != expr<Iterator>::TYPE_DOUBLE)
ctx->throw_exception("Right side is not a numeric expression", param.it_range);
static_cast<ConfigOptionPercent*>(wropt)->value = param.as_d();
break;
case coBool:
if (param.type() != expr<Iterator>::TYPE_BOOL)
ctx->throw_exception("Right side is not a boolean expression", param.it_range);
static_cast<ConfigOptionBool*>(wropt)->value = param.b();
break;
default:
ctx->throw_exception("Unsupported output scalar variable type", opt.it_range);
}
out.clear();
}
template <typename Iterator>
static void vector_variable_assign(
const MyContext *ctx,
OptWithPos<Iterator> &opt,
int &index,
expr<Iterator> &param,
// 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<ConfigOptionVectorBase*>(static_cast<const ConfigOptionVectorBase*>(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<Iterator>::TYPE_INT && param.type() != expr<Iterator>::TYPE_DOUBLE)
ctx->throw_exception("Right side is not a numeric expression", param.it_range);
static_cast<ConfigOptionFloats*>(vec)->values[index] = param.as_d();
break;
case coInts:
if (param.type() != expr<Iterator>::TYPE_INT && param.type() != expr<Iterator>::TYPE_DOUBLE)
ctx->throw_exception("Right side is not a numeric expression", param.it_range);
static_cast<ConfigOptionInts*>(vec)->values[index] = param.as_i();
break;
case coStrings:
static_cast<ConfigOptionStrings*>(vec)->values[index] = param.to_string();
break;
case coPercents:
if (param.type() != expr<Iterator>::TYPE_INT && param.type() != expr<Iterator>::TYPE_DOUBLE)
ctx->throw_exception("Right side is not a numeric expression", param.it_range);
static_cast<ConfigOptionPercents*>(vec)->values[index] = param.as_d();
break;
case coBools:
if (param.type() != expr<Iterator>::TYPE_BOOL)
ctx->throw_exception("Right side is not a boolean expression", param.it_range);
static_cast<ConfigOptionBools*>(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 <typename Iterator>
@ -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<Iterator>::to_string2, _1, _val) ];
| (assignment_statement(_r1) [_val = _1])
| (additive_expression(_r1) [ px::bind(&expr<Iterator>::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<Iterator>, _1, _b)] >> ']' >> '=' >> additive_expression(_r1))
[px::bind(&MyContext::vector_variable_assign<Iterator>, _r1, _a, _b, _2, _val)]
| ('=' >> additive_expression(_r1))
[px::bind(&MyContext::scalar_variable_assign<Iterator>, _r1, _a, _1, _val)]
);
struct FactorActions {
static void set_start_pos(Iterator &start_pos, expr<Iterator> &out)
{ out.it_range = boost::iterator_range<Iterator>(start_pos, start_pos); }
@ -1430,6 +1540,7 @@ namespace client
qi::rule<Iterator, expr<Iterator>(const MyContext*), qi::locals<OptWithPos<Iterator>, int>, spirit_encoding::space_type> is_nil_test;
qi::rule<Iterator, std::string(const MyContext*), qi::locals<bool, bool>, spirit_encoding::space_type> if_else_output;
qi::rule<Iterator, std::string(const MyContext*), qi::locals<OptWithPos<Iterator>, int>, spirit_encoding::space_type> assignment_statement;
// qi::rule<Iterator, std::string(const MyContext*), qi::locals<expr<Iterator>, bool, std::string>, spirit_encoding::space_type> switch_output;
qi::symbols<char> 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);

View File

@ -55,7 +55,9 @@ 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.

View File

@ -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.));
}
}