Merge branch 'master' into fs_svg

This commit is contained in:
Filip Sykala - NTB T15p 2023-03-22 18:14:16 +01:00
commit 916baa9ef8
4 changed files with 704 additions and 37 deletions

View File

@ -1086,6 +1086,8 @@ void GCode::_do_export(Print& print, GCodeOutputStream &file, ThumbnailsGenerato
m_placeholder_parser = print.placeholder_parser(); m_placeholder_parser = print.placeholder_parser();
m_placeholder_parser.update_timestamp(); m_placeholder_parser.update_timestamp();
m_placeholder_parser_context.rng = std::mt19937(std::chrono::high_resolution_clock::now().time_since_epoch().count()); m_placeholder_parser_context.rng = std::mt19937(std::chrono::high_resolution_clock::now().time_since_epoch().count());
// Enable passing global variables between PlaceholderParser invocations.
m_placeholder_parser_context.global_config = std::make_unique<DynamicConfig>();
print.update_object_placeholders(m_placeholder_parser.config_writable(), ".gcode"); print.update_object_placeholders(m_placeholder_parser.config_writable(), ".gcode");
// Get optimal tool ordering to minimize tool switches of a multi-exruder print. // Get optimal tool ordering to minimize tool switches of a multi-exruder print.

View File

@ -191,6 +191,10 @@ namespace client
struct expr struct expr
{ {
expr() {} expr() {}
expr(const expr &rhs) : m_type(rhs.type()), it_range(rhs.it_range)
{ if (rhs.type() == TYPE_STRING) m_data.s = new std::string(*rhs.m_data.s); else m_data.set(rhs.m_data); }
expr(expr &&rhs) : expr(std::move(rhs), rhs.it_range.begin(), rhs.it_range.end()) {}
explicit expr(bool b) : m_type(TYPE_BOOL) { m_data.b = b; } explicit expr(bool b) : m_type(TYPE_BOOL) { m_data.b = b; }
explicit expr(bool b, const Iterator &it_begin, const Iterator &it_end) : m_type(TYPE_BOOL), it_range(it_begin, it_end) { m_data.b = b; } explicit expr(bool b, const Iterator &it_begin, const Iterator &it_end) : m_type(TYPE_BOOL), it_range(it_begin, it_end) { m_data.b = b; }
explicit expr(int i) : m_type(TYPE_INT) { m_data.i = i; } explicit expr(int i) : m_type(TYPE_INT) { m_data.i = i; }
@ -201,11 +205,8 @@ namespace client
explicit expr(const std::string &s) : m_type(TYPE_STRING) { m_data.s = new std::string(s); } explicit expr(const std::string &s) : m_type(TYPE_STRING) { m_data.s = new std::string(s); }
explicit expr(const std::string &s, const Iterator &it_begin, const Iterator &it_end) : explicit expr(const std::string &s, const Iterator &it_begin, const Iterator &it_end) :
m_type(TYPE_STRING), it_range(it_begin, it_end) { m_data.s = new std::string(s); } m_type(TYPE_STRING), it_range(it_begin, it_end) { m_data.s = new std::string(s); }
expr(const expr &rhs) : m_type(rhs.type()), it_range(rhs.it_range)
{ if (rhs.type() == TYPE_STRING) m_data.s = new std::string(*rhs.m_data.s); else m_data.set(rhs.m_data); }
explicit expr(expr &&rhs) : expr(rhs, rhs.it_range.begin(), rhs.it_range.end()) {}
explicit expr(expr &&rhs, const Iterator &it_begin, const Iterator &it_end) : m_type(rhs.type()), it_range{ it_begin, it_end } explicit expr(expr &&rhs, const Iterator &it_begin, const Iterator &it_end) : m_type(rhs.type()), it_range{ it_begin, it_end }
{ {
m_data.set(rhs.m_data); m_data.set(rhs.m_data);
rhs.m_type = TYPE_EMPTY; rhs.m_type = TYPE_EMPTY;
} }
@ -249,6 +250,7 @@ namespace client
TYPE_STRING, TYPE_STRING,
}; };
Type type() const { return m_type; } Type type() const { return m_type; }
bool numeric_type() const { return m_type == TYPE_INT || m_type == TYPE_DOUBLE; }
bool& b() { return m_data.b; } bool& b() { return m_data.b; }
bool b() const { return m_data.b; } bool b() const { return m_data.b; }
@ -472,8 +474,7 @@ namespace client
static void compare_op(expr &lhs, expr &rhs, char op, bool invert) static void compare_op(expr &lhs, expr &rhs, char op, bool invert)
{ {
bool value = false; bool value = false;
if ((lhs.type() == TYPE_INT || lhs.type() == TYPE_DOUBLE) && if (lhs.numeric_type() && rhs.numeric_type()) {
(rhs.type() == TYPE_INT || rhs.type() == TYPE_DOUBLE)) {
// Both types are numeric. // Both types are numeric.
switch (op) { switch (op) {
case '=': case '=':
@ -681,7 +682,7 @@ namespace client
void throw_if_not_numeric(const char *message) const void throw_if_not_numeric(const char *message) const
{ {
if (this->type() != TYPE_INT && this->type() != TYPE_DOUBLE) if (! this->numeric_type())
this->throw_exception(message); this->throw_exception(message);
} }
@ -723,7 +724,10 @@ namespace client
const DynamicConfig *config = nullptr; const DynamicConfig *config = nullptr;
const DynamicConfig *config_override = nullptr; const DynamicConfig *config_override = nullptr;
mutable DynamicConfig *config_outputs = nullptr; mutable DynamicConfig *config_outputs = nullptr;
// Local variables, read / write
mutable DynamicConfig config_local;
size_t current_extruder_id = 0; size_t current_extruder_id = 0;
// Random number generator and optionally global variables.
PlaceholderParser::ContextData *context_data = nullptr; PlaceholderParser::ContextData *context_data = nullptr;
// If false, the macro_processor will evaluate a full macro. // If false, the macro_processor will evaluate a full macro.
// If true, the macro processor will evaluate just a boolean condition using the full expressive power of the macro processor. // If true, the macro processor will evaluate just a boolean condition using the full expressive power of the macro processor.
@ -748,7 +752,24 @@ namespace client
} }
const ConfigOption* resolve_symbol(const std::string &opt_key) const { return this->optptr(opt_key); } 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; } ConfigOption* resolve_output_symbol(const std::string &opt_key) const {
ConfigOption *out = nullptr;
if (this->config_outputs)
out = this->config_outputs->optptr(opt_key, false);
if (out == nullptr && this->context_data != nullptr && this->context_data->global_config)
out = this->context_data->global_config->optptr(opt_key);
if (out == nullptr)
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);
if (global_variable) {
assert(this->context_data != nullptr && this->context_data->global_config);
this->context_data->global_config->set_key_value(opt_key, opt);
} else
this->config_local.set_key_value(opt_key ,opt);
}
template <typename Iterator> template <typename Iterator>
static void legacy_variable_expansion( static void legacy_variable_expansion(
@ -953,17 +974,105 @@ namespace client
output.it_range = opt.it_range; output.it_range = opt.it_range;
} }
// Decoding a scalar variable symbol "opt", assigning it a value of "param". template<typename Iterator>
struct NewOldVariable {
std::string name;
boost::iterator_range<Iterator> it_range;
ConfigOption *opt{ nullptr };
};
template <typename Iterator> template <typename Iterator>
static void variable_assign( static void new_old_variable(
const MyContext *ctx, const MyContext *ctx,
OptWithPos<Iterator> &opt, bool global_variable,
expr<Iterator> &param, const boost::iterator_range<Iterator> &it_range,
// Not used, just clear it. NewOldVariable<Iterator> &out)
std::string &out)
{ {
t_config_option_key key(std::string(it_range.begin(), it_range.end()));
if (const ConfigOption* opt = ctx->resolve_symbol(key); opt)
ctx->throw_exception("Symbol is already defined in read-only system dictionary", it_range);
if (ctx->config_outputs && ctx->config_outputs->optptr(key))
ctx->throw_exception("Symbol is already defined as system output variable", it_range);
bool has_global_dictionary = ctx->context_data != nullptr && ctx->context_data->global_config;
if (global_variable) {
if (! has_global_dictionary)
ctx->throw_exception("Global variables are not available in this context", it_range);
if (ctx->config_local.optptr(key))
ctx->throw_exception("Variable name already defined in local scope", it_range);
out.opt = ctx->context_data->global_config->optptr(key);
} else {
if (has_global_dictionary && ctx->context_data->global_config->optptr(key))
ctx->throw_exception("Variable name already defined in global scope", it_range);
out.opt = ctx->config_local.optptr(key);
}
out.name = std::move(key);
out.it_range = it_range;
}
template <typename Iterator>
static void new_scalar_variable(
const MyContext *ctx,
bool global_variable,
NewOldVariable<Iterator> &output_variable,
const expr<Iterator> &param)
{
auto check_numeric = [](const expr<Iterator> &param) {
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<ConfigOptionFloat*>(output_variable.opt)->value = param.as_d();
break;
case coInt:
check_numeric(param);
static_cast<ConfigOptionInt*>(output_variable.opt)->value = param.as_i();
break;
case coString:
static_cast<ConfigOptionString*>(output_variable.opt)->value = param.to_string();
break;
case coBool:
if (param.type() != expr<Iterator>::TYPE_BOOL)
param.throw_exception("Right side is not a boolean expression");
static_cast<ConfigOptionBool*>(output_variable.opt)->value = param.b();
break;
default: assert(false);
}
} else {
switch (param.type()) {
case expr<Iterator>::TYPE_BOOL: output_variable.opt = new ConfigOptionBool(param.b()); break;
case expr<Iterator>::TYPE_INT: output_variable.opt = new ConfigOptionInt(param.i()); break;
case expr<Iterator>::TYPE_DOUBLE: output_variable.opt = new ConfigOptionFloat(param.d()); break;
case expr<Iterator>::TYPE_STRING: output_variable.opt = new ConfigOptionString(param.s()); break;
default: assert(false);
}
const_cast<MyContext*>(ctx)->store_new_variable(output_variable.name, output_variable.opt, global_variable);
}
}
template <typename Iterator>
static void check_writable(const MyContext *ctx, OptWithPos<Iterator> &opt) {
if (! opt.writable) if (! opt.writable)
ctx->throw_exception("Cannot modify a read-only variable", opt.it_range); 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 <typename Iterator>
static void assign_scalar_variable(
const MyContext *ctx,
OptWithPos<Iterator> &opt,
expr<Iterator> &param)
{
check_writable(ctx, opt);
auto check_numeric = [](const expr<Iterator> &param) {
if (! param.numeric_type())
param.throw_exception("Right side is not a numeric expression");
};
if (opt.opt->is_vector()) { if (opt.opt->is_vector()) {
if (! opt.has_index()) if (! opt.has_index())
ctx->throw_exception("Referencing an output vector variable when scalar is expected", opt.it_range); ctx->throw_exception("Referencing an output vector variable when scalar is expected", opt.it_range);
@ -974,21 +1083,18 @@ namespace client
ctx->throw_exception("Index out of range", opt.it_range); ctx->throw_exception("Index out of range", opt.it_range);
switch (opt.opt->type()) { switch (opt.opt->type()) {
case coFloats: case coFloats:
if (param.type() != expr<Iterator>::TYPE_INT && param.type() != expr<Iterator>::TYPE_DOUBLE) check_numeric(param);
ctx->throw_exception("Right side is not a numeric expression", param.it_range);
static_cast<ConfigOptionFloats*>(vec)->values[opt.index] = param.as_d(); static_cast<ConfigOptionFloats*>(vec)->values[opt.index] = param.as_d();
break; break;
case coInts: case coInts:
if (param.type() != expr<Iterator>::TYPE_INT && param.type() != expr<Iterator>::TYPE_DOUBLE) check_numeric(param);
ctx->throw_exception("Right side is not a numeric expression", param.it_range);
static_cast<ConfigOptionInts*>(vec)->values[opt.index] = param.as_i(); static_cast<ConfigOptionInts*>(vec)->values[opt.index] = param.as_i();
break; break;
case coStrings: case coStrings:
static_cast<ConfigOptionStrings*>(vec)->values[opt.index] = param.to_string(); static_cast<ConfigOptionStrings*>(vec)->values[opt.index] = param.to_string();
break; break;
case coPercents: case coPercents:
if (param.type() != expr<Iterator>::TYPE_INT && param.type() != expr<Iterator>::TYPE_DOUBLE) check_numeric(param);
ctx->throw_exception("Right side is not a numeric expression", param.it_range);
static_cast<ConfigOptionPercents*>(vec)->values[opt.index] = param.as_d(); static_cast<ConfigOptionPercents*>(vec)->values[opt.index] = param.as_d();
break; break;
case coBools: case coBools:
@ -1004,21 +1110,18 @@ namespace client
ConfigOption *wropt = const_cast<ConfigOption*>(opt.opt); ConfigOption *wropt = const_cast<ConfigOption*>(opt.opt);
switch (wropt->type()) { switch (wropt->type()) {
case coFloat: case coFloat:
if (param.type() != expr<Iterator>::TYPE_INT && param.type() != expr<Iterator>::TYPE_DOUBLE) check_numeric(param);
ctx->throw_exception("Right side is not a numeric expression", param.it_range);
static_cast<ConfigOptionFloat*>(wropt)->value = param.as_d(); static_cast<ConfigOptionFloat*>(wropt)->value = param.as_d();
break; break;
case coInt: case coInt:
if (param.type() != expr<Iterator>::TYPE_INT && param.type() != expr<Iterator>::TYPE_DOUBLE) check_numeric(param);
ctx->throw_exception("Right side is not a numeric expression", param.it_range);
static_cast<ConfigOptionInt*>(wropt)->value = param.as_i(); static_cast<ConfigOptionInt*>(wropt)->value = param.as_i();
break; break;
case coString: case coString:
static_cast<ConfigOptionString*>(wropt)->value = param.to_string(); static_cast<ConfigOptionString*>(wropt)->value = param.to_string();
break; break;
case coPercent: case coPercent:
if (param.type() != expr<Iterator>::TYPE_INT && param.type() != expr<Iterator>::TYPE_DOUBLE) check_numeric(param);
ctx->throw_exception("Right side is not a numeric expression", param.it_range);
static_cast<ConfigOptionPercent*>(wropt)->value = param.as_d(); static_cast<ConfigOptionPercent*>(wropt)->value = param.as_d();
break; break;
case coBool: case coBool:
@ -1030,7 +1133,363 @@ namespace client
ctx->throw_exception("Unsupported output scalar variable type", opt.it_range); ctx->throw_exception("Unsupported output scalar variable type", opt.it_range);
} }
} }
out.clear(); }
template <typename Iterator>
static void new_vector_variable_array(
const MyContext *ctx,
bool global_variable,
NewOldVariable<Iterator> &output_variable,
const expr<Iterator> &expr_count,
const expr<Iterator> &expr_value)
{
auto check_numeric = [](const expr<Iterator> &param) {
if (! param.numeric_type())
param.throw_exception("Right side is not a numeric expression");
};
auto evaluate_count = [](const expr<Iterator> &expr_count) -> size_t {
if (expr_count.type() != expr<Iterator>::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<ConfigOptionFloats*>(output_variable.opt)->values.assign(count, expr_value.as_d());
break;
case coInts:
check_numeric(expr_value);
static_cast<ConfigOptionInts*>(output_variable.opt)->values.assign(count, expr_value.as_i());
break;
case coStrings:
static_cast<ConfigOptionStrings*>(output_variable.opt)->values.assign(count, expr_value.to_string());
break;
case coBools:
if (expr_value.type() != expr<Iterator>::TYPE_BOOL)
expr_value.throw_exception("Right side is not a boolean expression");
static_cast<ConfigOptionBools*>(output_variable.opt)->values.assign(count, expr_value.b());
break;
default: assert(false);
}
} else {
size_t count = evaluate_count(expr_count);
switch (expr_value.type()) {
case expr<Iterator>::TYPE_BOOL: output_variable.opt = new ConfigOptionBools(count, expr_value.b()); break;
case expr<Iterator>::TYPE_INT: output_variable.opt = new ConfigOptionInts(count, expr_value.i()); break;
case expr<Iterator>::TYPE_DOUBLE: output_variable.opt = new ConfigOptionFloats(count, expr_value.d()); break;
case expr<Iterator>::TYPE_STRING: output_variable.opt = new ConfigOptionStrings(count, expr_value.s()); break;
default: assert(false);
}
const_cast<MyContext*>(ctx)->store_new_variable(output_variable.name, output_variable.opt, global_variable);
}
}
template <typename Iterator>
static void assign_vector_variable_array(
const MyContext *ctx,
OptWithPos<Iterator> &lhs,
const expr<Iterator> &expr_count,
const expr<Iterator> &expr_value)
{
check_writable(ctx, lhs);
auto check_numeric = [](const expr<Iterator> &param) {
if (! param.numeric_type())
param.throw_exception("Right side is not a numeric expression");
};
auto evaluate_count = [](const expr<Iterator> &expr_count) -> size_t {
if (expr_count.type() != expr<Iterator>::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<ConfigOption*>(lhs.opt);
size_t count = evaluate_count(expr_count);
switch (lhs.opt->type()) {
case coFloats:
check_numeric(expr_value);
static_cast<ConfigOptionFloats*>(opt)->values.assign(count, expr_value.as_d());
break;
case coInts:
check_numeric(expr_value);
static_cast<ConfigOptionInts*>(opt)->values.assign(count, expr_value.as_i());
break;
case coStrings:
static_cast<ConfigOptionStrings*>(opt)->values.assign(count, expr_value.to_string());
break;
case coBools:
if (expr_value.type() != expr<Iterator>::TYPE_BOOL)
expr_value.throw_exception("Right side is not a boolean expression");
static_cast<ConfigOptionBools*>(opt)->values.assign(count, expr_value.b());
break;
default: assert(false);
}
}
template <typename Iterator>
static void new_vector_variable_initializer_list(
const MyContext *ctx,
bool global_variable,
NewOldVariable<Iterator> &output_variable,
const std::vector<expr<Iterator>> &il)
{
if (! output_variable.opt) {
// First guesstimate type of the output vector.
size_t num_bool = 0;
size_t num_int = 0;
size_t num_double = 0;
size_t num_string = 0;
for (auto &i : il)
switch (i.type()) {
case expr<Iterator>::TYPE_BOOL: ++ num_bool; break;
case expr<Iterator>::TYPE_INT: ++ num_int; break;
case expr<Iterator>::TYPE_DOUBLE: ++ num_double; break;
case expr<Iterator>::TYPE_STRING: ++ num_string; break;
default: assert(false);
}
if (num_string > 0)
// Convert everything to strings.
output_variable.opt = new ConfigOptionStrings();
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<Iterator>{ il.front().it_range.begin(), il.back().it_range.end() });
output_variable.opt = new ConfigOptionBools();
} else
// Output is numeric.
output_variable.opt = num_double == 0 ? static_cast<ConfigOption*>(new ConfigOptionInts()) : static_cast<ConfigOption*>(new ConfigOptionFloats());
const_cast<MyContext*>(ctx)->store_new_variable(output_variable.name, output_variable.opt, global_variable);
}
auto check_numeric = [](const std::vector<expr<Iterator>> &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<ConfigOptionFloats*>(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<ConfigOptionInts*>(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<ConfigOptionStrings*>(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<ConfigOptionBools*>(output_variable.opt)->values;
out.clear();
out.reserve(il.size());
for (auto &i : il)
if (i.type() == expr<Iterator>::TYPE_BOOL)
out.emplace_back(i.b());
else
i.throw_exception("Right side is not a boolean expression");
break;
}
default:
assert(false);
}
}
template <typename Iterator>
static void assign_vector_variable_initializer_list(
const MyContext *ctx,
OptWithPos<Iterator> &lhs,
const std::vector<expr<Iterator>> &il)
{
check_writable(ctx, lhs);
auto check_numeric = [](const std::vector<expr<Iterator>> &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<ConfigOption*>(lhs.opt);
switch (lhs.opt->type()) {
case coFloats:
{
check_numeric(il);
auto &out = static_cast<ConfigOptionFloats*>(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<ConfigOptionInts*>(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<ConfigOptionStrings*>(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<ConfigOptionBools*>(opt)->values;
out.clear();
out.reserve(il.size());
for (auto &i : il)
if (i.type() == expr<Iterator>::TYPE_BOOL)
out.emplace_back(i.b());
else
i.throw_exception("Right side is not a boolean expression");
break;
}
default:
assert(false);
}
}
template <typename Iterator>
static bool new_vector_variable_copy(
const MyContext *ctx,
bool global_variable,
NewOldVariable<Iterator> &output_variable,
const OptWithPos<Iterator> &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<const ConfigOptionPercents*>(src_variable.opt)->values);
else
ctx->throw_exception("Duplicating this vector variable is not supported", src_variable.it_range);
const_cast<MyContext*>(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<Iterator>{ output_variable.it_range.begin(), src_variable.it_range.end() });
static_cast<ConfigOptionFloats*>(output_variable.opt)->values = static_cast<const ConfigOptionFloats*>(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<Iterator>{ output_variable.it_range.begin(), src_variable.it_range.end() });
static_cast<ConfigOptionInts*>(output_variable.opt)->values = static_cast<const ConfigOptionInts*>(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<Iterator>{ output_variable.it_range.begin(), src_variable.it_range.end() });
static_cast<ConfigOptionStrings*>(output_variable.opt)->values = static_cast<const ConfigOptionStrings*>(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<Iterator>{ output_variable.it_range.begin(), src_variable.it_range.end() });
static_cast<ConfigOptionBools*>(output_variable.opt)->values = static_cast<const ConfigOptionBools*>(src_variable.opt)->values;
break;
default:
assert(false);
}
// Continue parsing.
return true;
}
template <typename Iterator>
static bool is_vector_variable_reference(const OptWithPos<Iterator> &var) {
return ! var.has_index() && var.opt->is_vector();
}
template <typename Iterator>
static bool assign_vector_variable_copy(
const MyContext *ctx,
OptWithPos<Iterator> &lhs,
const OptWithPos<Iterator> &src_variable)
{
if (! is_vector_variable_reference(src_variable))
// Skip parsing this branch, bactrack.
return false;
check_writable(ctx, lhs);
auto *opt = const_cast<ConfigOption*>(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<ConfigOptionFloats*>(opt)->values = static_cast<const ConfigOptionFloats*>(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<ConfigOptionInts*>(opt)->values = static_cast<const ConfigOptionInts*>(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<ConfigOptionStrings*>(opt)->values = static_cast<const ConfigOptionStrings*>(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<ConfigOptionBools*>(opt)->values = static_cast<const ConfigOptionBools*>(src_variable.opt)->values;
break;
default:
assert(false);
}
// Continue parsing.
return true;
}
template <typename Iterator>
static void new_vector_variable_initializer_list_append(std::vector<expr<Iterator>> &list, expr<Iterator> &expr)
{
list.emplace_back(std::move(expr));
} }
// Verify that the expression returns an integer, which may be used // Verify that the expression returns an integer, which may be used
@ -1109,8 +1568,75 @@ namespace client
} }
}; };
template<typename Iterator>
struct InterpolateTableContext {
template<typename Iterator>
struct Item {
double x;
boost::iterator_range<Iterator> it_range_x;
double y;
};
std::vector<Item<Iterator>> table;
static void init(const expr<Iterator> &x) {
if (!x.numeric_type())
x.throw_exception("Interpolation value must be a number.");
}
static void add_pair(const expr<Iterator> &x, const expr<Iterator> &y, InterpolateTableContext &table) {
if (! x.numeric_type())
x.throw_exception("X value of a table point must be a number.");
if (! y.numeric_type())
y.throw_exception("Y value of a table point must be a number.");
table.table.push_back({ x.as_d(), x.it_range, y.as_d() });
}
static void evaluate(const expr<Iterator> &expr_x, const InterpolateTableContext &table, expr<Iterator> &out) {
// Check whether the table X values are sorted.
double x = expr_x.as_d();
bool evaluated = false;
for (size_t i = 1; i < table.table.size(); ++i) {
double x0 = table.table[i - 1].x;
double x1 = table.table[i].x;
if (x0 > x1)
boost::throw_exception(qi::expectation_failure<Iterator>(
table.table[i - 1].it_range_x.begin(), table.table[i].it_range_x.end(), spirit::info("X coordinates of the table must be increasing")));
if (! evaluated && x >= x0 && x <= x1) {
double y0 = table.table[i - 1].y;
double y1 = table.table[i].y;
if (x == x0)
out.set_d(y0);
else if (x == x1)
out.set_d(y1);
else if (is_approx(x0, x1))
out.set_d(0.5 * (y0 + y1));
else
out.set_d(Slic3r::lerp(y0, y1, (x - x0) / (x1 - x0)));
evaluated = true;
}
}
if (! evaluated) {
// Clamp x into the table range with EPSILON.
if (x > table.table.front().x - EPSILON)
out.set_d(table.table.front().y);
else if (x < table.table.back().x + EPSILON)
out.set_d(table.table.back().y);
else
// The value is really outside the table range.
expr_x.throw_exception("Interpolation value is outside the table range");
}
}
};
template<typename Iterator>
std::ostream& operator<<(std::ostream &os, const InterpolateTableContext<Iterator> &table_context)
{
for (const auto &item : table_context.table)
os << "(" << item.x << "," << item.y << ")";
return os;
}
// Table to translate symbol tag to a human readable error message. // Table to translate symbol tag to a human readable error message.
std::map<std::string, std::string> MyContext::tag_to_error_message = { std::map<std::string, std::string> MyContext::tag_to_error_message = {
{ "array", "Unknown syntax error" },
{ "eoi", "Unknown syntax error" }, { "eoi", "Unknown syntax error" },
{ "start", "Unknown syntax error" }, { "start", "Unknown syntax error" },
{ "text", "Invalid text." }, { "text", "Invalid text." },
@ -1271,7 +1797,7 @@ namespace client
// Allow back tracking after '{' in case of a text_block embedded inside a condition. // Allow back tracking after '{' in case of a text_block embedded inside a condition.
// In that case the inner-most {else} wins and the {if}/{elsif}/{else} shall be paired. // In that case the inner-most {else} wins and the {if}/{elsif}/{else} shall be paired.
// {elsif}/{else} without an {if} will be allowed to back track from the embedded text_block. // {elsif}/{else} without an {if} will be allowed to back track from the embedded text_block.
| (lit('{') >> macro(_r1) [_val+=_1] > '}') | (lit('{') >> macro(_r1)[_val+=_1] > *(+lit(';') >> macro(_r1)[_val+=_1]) > *lit(';') > '}')
| (lit('[') > legacy_variable_expansion(_r1) [_val+=_1] > ']') | (lit('[') > legacy_variable_expansion(_r1) [_val+=_1] > ']')
); );
text_block.name("text_block"); text_block.name("text_block");
@ -1287,6 +1813,7 @@ namespace client
(kw["if"] > if_else_output(_r1) [_val = _1]) (kw["if"] > if_else_output(_r1) [_val = _1])
// | (kw["switch"] > switch_output(_r1) [_val = _1]) // | (kw["switch"] > switch_output(_r1) [_val = _1])
| (assignment_statement(_r1) [_val = _1]) | (assignment_statement(_r1) [_val = _1])
| (new_variable_statement(_r1) [_val = _1])
| (additive_expression(_r1) [ px::bind(&expr<Iterator>::to_string2, _1, _val) ]) | (additive_expression(_r1) [ px::bind(&expr<Iterator>::to_string2, _1, _val) ])
; ;
macro.name("macro"); macro.name("macro");
@ -1381,8 +1908,39 @@ namespace client
multiplicative_expression.name("multiplicative_expression"); multiplicative_expression.name("multiplicative_expression");
assignment_statement = assignment_statement =
(variable_reference(_r1) >> '=' > additive_expression(_r1)) variable_reference(_r1)[_a = _1] >> '=' >
[px::bind(&MyContext::variable_assign<Iterator>, _r1, _1, _2, _val)]; ( // 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<Iterator>, _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<Iterator>, _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<Iterator>, _r1, _a, _1)]
| (kw["array"] > "(" > additive_expression(_r1) > "," > conditional_expression(_r1) > ")")
[px::bind(&MyContext::assign_vector_variable_array<Iterator>, _r1, _a, _1, _2)]
);
new_variable_statement =
(kw["local"][_a = false] | kw["global"][_a = true]) > identifier[px::bind(&MyContext::new_old_variable<Iterator>, _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<Iterator>, _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<Iterator>, _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<Iterator>, _r1, _a, _b, _1)]
| (kw["array"] > "(" > additive_expression(_r1) > "," > conditional_expression(_r1) > ")")
[px::bind(&MyContext::new_vector_variable_array<Iterator>, _r1, _a, _b, _1, _2)]
);
new_variable_initializer_list =
conditional_expression(_r1)[px::bind(&MyContext::new_vector_variable_initializer_list_append<Iterator>, _val, _1)] >>
*(lit(',') > conditional_expression(_r1)[px::bind(&MyContext::new_vector_variable_initializer_list_append<Iterator>, _val, _1)]);
struct FactorActions { struct FactorActions {
static void set_start_pos(Iterator &start_pos, expr<Iterator> &out) static void set_start_pos(Iterator &start_pos, expr<Iterator> &out)
@ -1428,6 +1986,7 @@ namespace client
| (kw["round"] > '(' > conditional_expression(_r1) > ')') [ px::bind(&FactorActions::round, _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"] > '(' > is_nil_test(_r1) > ')') [ _val = _1 ]
| (kw["one_of"] > '(' > one_of(_r1) > ')') [ _val = _1 ] | (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) ] | (strict_double > iter_pos) [ px::bind(&FactorActions::double_, _1, _2, _val) ]
| (int_ > iter_pos) [ px::bind(&FactorActions::int_, _1, _2, _val) ] | (int_ > iter_pos) [ px::bind(&FactorActions::int_, _1, _2, _val) ]
| (kw[bool_] > iter_pos) [ px::bind(&FactorActions::bool_, _1, _2, _val) ] | (kw[bool_] > iter_pos) [ px::bind(&FactorActions::bool_, _1, _2, _val) ]
@ -1440,16 +1999,26 @@ namespace client
one_of.name("one_of"); one_of.name("one_of");
one_of_list = one_of_list =
eps[px::bind(&expr<Iterator>::one_of_test_init, _val)] > eps[px::bind(&expr<Iterator>::one_of_test_init, _val)] >
( ',' > *( ( ( ',' > *(
( (
unary_expression(_r1)[px::bind(&expr<Iterator>::template one_of_test<false>, _r2, _1, _val)] unary_expression(_r1)[px::bind(&expr<Iterator>::template one_of_test<false>, _r2, _1, _val)]
| (lit('~') > unary_expression(_r1))[px::bind(&expr<Iterator>::template one_of_test<true>, _r2, _1, _val)] | (lit('~') > unary_expression(_r1))[px::bind(&expr<Iterator>::template one_of_test<true>, _r2, _1, _val)]
| regular_expression[px::bind(&expr<Iterator>::one_of_test_regex, _r2, _1, _val)] | regular_expression[px::bind(&expr<Iterator>::one_of_test_regex, _r2, _1, _val)]
) >> -lit(',')) ) >> -lit(','))
| eps )
| eps
); );
one_of_list.name("one_of_list"); one_of_list.name("one_of_list");
interpolate_table = (unary_expression(_r1)[_a = _1] > ',' > interpolate_table_list(_r1, _a))
[px::bind(&InterpolateTableContext<Iterator>::evaluate, _a, _2, _val)];
interpolate_table.name("interpolate_table");
interpolate_table_list =
eps[px::bind(&InterpolateTableContext<Iterator>::init, _r2)] >
( *(( lit('(') > unary_expression(_r1) > ',' > unary_expression(_r1) > ')' )
[px::bind(&InterpolateTableContext<Iterator>::add_pair, _1, _2, _val)] >> -lit(',')) );
interpolate_table.name("interpolate_table_list");
optional_parameter = iter_pos[px::bind(&FactorActions::set_start_pos, _1, _val)] >> ( optional_parameter = iter_pos[px::bind(&FactorActions::set_start_pos, _1, _val)] >> (
lit(')') [ px::bind(&FactorActions::noexpr, _val) ] lit(')') [ px::bind(&FactorActions::noexpr, _val) ]
| (lit(',') > conditional_expression(_r1) > ')') [ _val = _1 ] | (lit(',') > conditional_expression(_r1) > ')') [ _val = _1 ]
@ -1469,23 +2038,27 @@ namespace client
variable_reference.name("variable reference"); variable_reference.name("variable reference");
variable = identifier[ px::bind(&MyContext::resolve_variable<Iterator>, _r1, _1, _val) ]; variable = identifier[ px::bind(&MyContext::resolve_variable<Iterator>, _r1, _1, _val) ];
variable.name("variable reference"); variable.name("variable name");
regular_expression = raw[lexeme['/' > *((utf8char - char_('\\') - char_('/')) | ('\\' > char_)) > '/']]; regular_expression = raw[lexeme['/' > *((utf8char - char_('\\') - char_('/')) | ('\\' > char_)) > '/']];
regular_expression.name("regular_expression"); regular_expression.name("regular_expression");
keywords.add keywords.add
("and") ("and")
("array")
("digits") ("digits")
("zdigits") ("zdigits")
("if") ("if")
("int") ("int")
("is_nil") ("is_nil")
("local")
//("inf") //("inf")
("else") ("else")
("elsif") ("elsif")
("endif") ("endif")
("false") ("false")
("global")
("interpolate_table")
("min") ("min")
("max") ("max")
("random") ("random")
@ -1501,9 +2074,12 @@ namespace client
debug(text_block); debug(text_block);
debug(macro); debug(macro);
debug(if_else_output); debug(if_else_output);
debug(interpolate_table);
// debug(switch_output); // debug(switch_output);
debug(legacy_variable_expansion); debug(legacy_variable_expansion);
debug(identifier); debug(identifier);
debug(interpolate_table);
debug(interpolate_table_list);
debug(conditional_expression); debug(conditional_expression);
debug(logical_or_expression); debug(logical_or_expression);
debug(logical_and_expression); debug(logical_and_expression);
@ -1569,11 +2145,17 @@ namespace client
// Evaluating "one of" list of patterns. // Evaluating "one of" list of patterns.
qi::rule<Iterator, expr<Iterator>(const MyContext*), qi::locals<expr<Iterator>>, spirit_encoding::space_type> one_of; qi::rule<Iterator, expr<Iterator>(const MyContext*), qi::locals<expr<Iterator>>, spirit_encoding::space_type> one_of;
qi::rule<Iterator, expr<Iterator>(const MyContext*, const expr<Iterator> &param), spirit_encoding::space_type> one_of_list; qi::rule<Iterator, expr<Iterator>(const MyContext*, const expr<Iterator> &param), spirit_encoding::space_type> one_of_list;
// Evaluating the "interpolate_table" expression.
qi::rule<Iterator, expr<Iterator>(const MyContext*), qi::locals<expr<Iterator>>, spirit_encoding::space_type> interpolate_table;
qi::rule<Iterator, InterpolateTableContext<Iterator>(const MyContext*, const expr<Iterator> &param), spirit_encoding::space_type> interpolate_table_list;
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<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<OptWithPos<Iterator>>, 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; // Allocating new local or global variables.
qi::rule<Iterator, std::string(const MyContext*), qi::locals<bool, MyContext::NewOldVariable<Iterator>>, spirit_encoding::space_type> new_variable_statement;
qi::rule<Iterator, std::vector<expr<Iterator>>(const MyContext*), spirit_encoding::space_type> new_variable_initializer_list;
// qi::rule<Iterator, std::string(const MyContext*), qi::locals<expr<Iterator>, bool, std::string>, spirit_encoding::space_type> switch_output;
qi::symbols<char> keywords; qi::symbols<char> keywords;
}; };
} }

View File

@ -20,7 +20,10 @@ public:
// In the future, the context may hold variables created and modified by the PlaceholderParser // In the future, the context may hold variables created and modified by the PlaceholderParser
// and shared between the PlaceholderParser::process() invocations. // and shared between the PlaceholderParser::process() invocations.
struct ContextData { struct ContextData {
std::mt19937 rng; std::mt19937 rng;
// If defined, then this dictionary is used by the scripts to define user variables and persist them
// between PlaceholderParser evaluations.
std::unique_ptr<DynamicConfig> global_config;
}; };
PlaceholderParser(const DynamicConfig *external_config = nullptr); PlaceholderParser(const DynamicConfig *external_config = nullptr);

View File

@ -39,6 +39,10 @@ SCENARIO("Placeholder parser scripting", "[PlaceholderParser]") {
SECTION("nullable is not null") { REQUIRE(parser.process("{is_nil(filament_retract_length[0])}") == "false"); } SECTION("nullable is not null") { REQUIRE(parser.process("{is_nil(filament_retract_length[0])}") == "false"); }
SECTION("nullable is null") { REQUIRE(parser.process("{is_nil(filament_retract_length[1])}") == "true"); } SECTION("nullable is null") { REQUIRE(parser.process("{is_nil(filament_retract_length[1])}") == "true"); }
SECTION("nullable is not null 2") { REQUIRE(parser.process("{is_nil(filament_retract_length[2])}") == "false"); } SECTION("nullable is not null 2") { REQUIRE(parser.process("{is_nil(filament_retract_length[2])}") == "false"); }
SECTION("multiple expressions") { REQUIRE(parser.process("{temperature[foo];temperature[foo]}") == "357357"); }
SECTION("multiple expressions with semicolons") { REQUIRE(parser.process("{temperature[foo];;;temperature[foo];}") == "357357"); }
SECTION("multiple expressions with semicolons 2") { REQUIRE(parser.process("{temperature[foo];;temperature[foo];}") == "357357"); }
SECTION("multiple expressions with semicolons 3") { REQUIRE(parser.process("{temperature[foo];;;temperature[foo];;}") == "357357"); }
// Test the math expressions. // Test the math expressions.
SECTION("math: 2*3") { REQUIRE(parser.process("{2*3}") == "6"); } SECTION("math: 2*3") { REQUIRE(parser.process("{2*3}") == "6"); }
@ -71,6 +75,9 @@ SCENARIO("Placeholder parser scripting", "[PlaceholderParser]") {
SECTION("math: zdigits(5., 15, 8)") { REQUIRE(parser.process("{zdigits(5, 15, 8)}") == "000005.00000000"); } SECTION("math: zdigits(5., 15, 8)") { REQUIRE(parser.process("{zdigits(5, 15, 8)}") == "000005.00000000"); }
SECTION("math: digits(13.84375892476, 15, 8)") { REQUIRE(parser.process("{digits(13.84375892476, 15, 8)}") == " 13.84375892"); } SECTION("math: digits(13.84375892476, 15, 8)") { REQUIRE(parser.process("{digits(13.84375892476, 15, 8)}") == " 13.84375892"); }
SECTION("math: zdigits(13.84375892476, 15, 8)") { REQUIRE(parser.process("{zdigits(13.84375892476, 15, 8)}") == "000013.84375892"); } SECTION("math: zdigits(13.84375892476, 15, 8)") { REQUIRE(parser.process("{zdigits(13.84375892476, 15, 8)}") == "000013.84375892"); }
SECTION("math: interpolate_table(13.84375892476, (0, 0), (20, 20))") { REQUIRE(std::stod(parser.process("{interpolate_table(13.84375892476, (0, 0), (20, 20))}")) == Approx(13.84375892476)); }
SECTION("math: interpolate_table(13, (0, 0), (20, 20), (30, 20))") { REQUIRE(std::stod(parser.process("{interpolate_table(13, (0, 0), (20, 20), (30, 20))}")) == Approx(13.)); }
SECTION("math: interpolate_table(25, (0, 0), (20, 20), (30, 20))") { REQUIRE(std::stod(parser.process("{interpolate_table(25, (0, 0), (20, 20), (30, 20))}")) == Approx(20.)); }
// Test the "coFloatOrPercent" and "xxx_extrusion_width" substitutions. // Test the "coFloatOrPercent" and "xxx_extrusion_width" substitutions.
// first_layer_extrusion_width ratio_over first_layer_heigth. // first_layer_extrusion_width ratio_over first_layer_heigth.
@ -143,3 +150,76 @@ SCENARIO("Placeholder parser scripting", "[PlaceholderParser]") {
REQUIRE(config_outputs.opt_float("writable_floats", 1) == Approx(33.)); REQUIRE(config_outputs.opt_float("writable_floats", 1) == Approx(33.));
} }
} }
SCENARIO("Placeholder parser variables", "[PlaceholderParser]") {
PlaceholderParser parser;
auto config = DynamicPrintConfig::full_print_config();
config.set_deserialize_strict({
{ "filament_notes", "testnotes" },
{ "enable_dynamic_fan_speeds", "1" },
{ "nozzle_diameter", "0.6;0.6;0.6;0.6" },
{ "temperature", "357;359;363;378" }
});
PlaceholderParser::ContextData context_with_global_dict;
context_with_global_dict.global_config = std::make_unique<DynamicConfig>();
SECTION("create an int local variable") { REQUIRE(parser.process("{local myint = 33+2}{myint}", 0, nullptr, nullptr, nullptr) == "35"); }
SECTION("create a string local variable") { REQUIRE(parser.process("{local mystr = \"mine\" + \"only\" + \"mine\"}{mystr}", 0, nullptr, nullptr, nullptr) == "mineonlymine"); }
SECTION("create a bool local variable") { REQUIRE(parser.process("{local mybool = 1 + 1 == 2}{mybool}", 0, nullptr, nullptr, nullptr) == "true"); }
SECTION("create an int global variable") { REQUIRE(parser.process("{global myint = 33+2}{myint}", 0, nullptr, nullptr, &context_with_global_dict) == "35"); }
SECTION("create a string global variable") { REQUIRE(parser.process("{global mystr = \"mine\" + \"only\" + \"mine\"}{mystr}", 0, nullptr, nullptr, &context_with_global_dict) == "mineonlymine"); }
SECTION("create a bool global variable") { REQUIRE(parser.process("{global mybool = 1 + 1 == 2}{mybool}", 0, nullptr, nullptr, &context_with_global_dict) == "true"); }
SECTION("create an int local variable and overwrite it") { REQUIRE(parser.process("{local myint = 33+2}{myint = 12}{myint}", 0, nullptr, nullptr, nullptr) == "12"); }
SECTION("create a string local variable and overwrite it") { REQUIRE(parser.process("{local mystr = \"mine\" + \"only\" + \"mine\"}{mystr = \"yours\"}{mystr}", 0, nullptr, nullptr, nullptr) == "yours"); }
SECTION("create a bool local variable and overwrite it") { REQUIRE(parser.process("{local mybool = 1 + 1 == 2}{mybool = false}{mybool}", 0, nullptr, nullptr, nullptr) == "false"); }
SECTION("create an int global variable and overwrite it") { REQUIRE(parser.process("{global myint = 33+2}{myint = 12}{myint}", 0, nullptr, nullptr, &context_with_global_dict) == "12"); }
SECTION("create a string global variable and overwrite it") { REQUIRE(parser.process("{global mystr = \"mine\" + \"only\" + \"mine\"}{mystr = \"yours\"}{mystr}", 0, nullptr, nullptr, &context_with_global_dict) == "yours"); }
SECTION("create a bool global variable and overwrite it") { REQUIRE(parser.process("{global mybool = 1 + 1 == 2}{mybool = false}{mybool}", 0, nullptr, nullptr, &context_with_global_dict) == "false"); }
SECTION("create an int local variable and redefine it") { REQUIRE(parser.process("{local myint = 33+2}{local myint = 12}{myint}", 0, nullptr, nullptr, nullptr) == "12"); }
SECTION("create a string local variable and redefine it") { REQUIRE(parser.process("{local mystr = \"mine\" + \"only\" + \"mine\"}{local mystr = \"yours\"}{mystr}", 0, nullptr, nullptr, nullptr) == "yours"); }
SECTION("create a bool local variable and redefine it") { REQUIRE(parser.process("{local mybool = 1 + 1 == 2}{local mybool = false}{mybool}", 0, nullptr, nullptr, nullptr) == "false"); }
SECTION("create an int global variable and redefine it") { REQUIRE(parser.process("{global myint = 33+2}{global myint = 12}{myint}", 0, nullptr, nullptr, &context_with_global_dict) == "12"); }
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 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"); }
SECTION("create a bools local variable with initializer list") { REQUIRE(parser.process("{local mybool = (3*3 == 8, 1 + 1 == 2)}{mybool[1]}", 0, nullptr, nullptr, nullptr) == "true"); }
SECTION("create an ints global variable with initializer list") { REQUIRE(parser.process("{global myint = (2*3, 4*6, 5*5)}{myint[1]}", 0, nullptr, nullptr, &context_with_global_dict) == "24"); }
SECTION("create a strings global variable with initializer list") { REQUIRE(parser.process("{global mystr = (2*3, \"mine\" + \"only\" + \"mine\", 8)}{mystr[1]}", 0, nullptr, nullptr, &context_with_global_dict) == "mineonlymine"); }
SECTION("create a bools global variable with initializer list") { REQUIRE(parser.process("{global mybool = (2*3 == 8, 1 + 1 == 2, 5*5 != 33)}{mybool[1]}", 0, nullptr, nullptr, &context_with_global_dict) == "true"); }
SECTION("create an ints local variable by a copy") { REQUIRE(parser.process("{local myint = temperature}{myint[0]}", 0, &config, nullptr, nullptr) == "357"); }
SECTION("create a strings local variable by a copy") { REQUIRE(parser.process("{local mystr = filament_notes}{mystr[0]}", 0, &config, nullptr, nullptr) == "testnotes"); }
SECTION("create a bools local variable by a copy") { REQUIRE(parser.process("{local mybool = enable_dynamic_fan_speeds}{mybool[0]}", 0, &config, nullptr, nullptr) == "true"); }
SECTION("create an ints global variable by a copy") { REQUIRE(parser.process("{global myint = temperature}{myint[0]}", 0, &config, nullptr, &context_with_global_dict) == "357"); }
SECTION("create a strings global variable by a copy") { REQUIRE(parser.process("{global mystr = filament_notes}{mystr[0]}", 0, &config, nullptr, &context_with_global_dict) == "testnotes"); }
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 = (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 = (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 = (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");
}
}