PlaceholderParser: implemented round(), digits() and zdigits() macros.

round() rounds to an integer.
This is a popular request, for example #3472

digits(value, num_digits, num_decimals) rounds to num_digits and
	num_decimals, left filled with spaces.
digits(value, num_digits) the same as digits(value, num_digits, 0)
    Neither decimal separator nor any decimals after decimal separator
    are emitted.
zdigits(...) is the same as digits(...) only left filled with zeros.
If the result does not fit num_digits, the result is never trimmed.
This commit is contained in:
Vojtech Bubnik 2021-10-29 13:36:26 +02:00
parent 0f145920d9
commit 3216448bbc
2 changed files with 79 additions and 3 deletions

View File

@ -238,6 +238,7 @@ namespace client
int i() const { return data.i; }
void set_i(int v) { this->reset(); this->data.i = v; this->type = TYPE_INT; }
int as_i() const { return (this->type == TYPE_INT) ? this->i() : int(this->d()); }
int as_i_rounded() const { return (this->type == TYPE_INT) ? this->i() : int(std::round(this->d())); }
double& d() { return data.d; }
double d() const { return data.d; }
void set_d(double v) { this->reset(); this->data.d = v; this->type = TYPE_DOUBLE; }
@ -319,7 +320,7 @@ namespace client
expr unary_integer(const Iterator start_pos) const
{
switch (this->type) {
case TYPE_INT :
case TYPE_INT:
return expr<Iterator>(this->i(), start_pos, this->it_range.end());
case TYPE_DOUBLE:
return expr<Iterator>(static_cast<int>(this->d()), start_pos, this->it_range.end());
@ -331,10 +332,25 @@ namespace client
return expr();
}
expr round(const Iterator start_pos) const
{
switch (this->type) {
case TYPE_INT:
return expr<Iterator>(this->i(), start_pos, this->it_range.end());
case TYPE_DOUBLE:
return expr<Iterator>(static_cast<int>(std::round(this->d())), start_pos, this->it_range.end());
default:
this->throw_exception("Cannot round a non-numeric value.");
}
assert(false);
// Suppress compiler warnings.
return expr();
}
expr unary_not(const Iterator start_pos) const
{
switch (this->type) {
case TYPE_BOOL :
case TYPE_BOOL:
return expr<Iterator>(! this->b(), start_pos, this->it_range.end());
default:
this->throw_exception("Cannot apply a not operator.");
@ -549,6 +565,30 @@ namespace client
}
}
// Store the result into param1.
// param3 is optional
template<bool leading_zeros>
static void digits(expr &param1, expr &param2, expr &param3)
{
throw_if_not_numeric(param1);
if (param2.type != TYPE_INT)
param2.throw_exception("digits: second parameter must be integer");
bool has_decimals = param3.type != TYPE_EMPTY;
if (has_decimals && param3.type != TYPE_INT)
param3.throw_exception("digits: third parameter must be integer");
char buf[256];
int ndigits = std::clamp(param2.as_i(), 0, 64);
if (has_decimals) {
// Format as double.
int decimals = std::clamp(param3.as_i(), 0, 64);
sprintf(buf, leading_zeros ? "%0*.*lf" : "%*.*lf", ndigits, decimals, param1.as_d());
} else
// Format as int.
sprintf(buf, leading_zeros ? "%0*d" : "%*d", ndigits, param1.as_i_rounded());
param1.set_s(buf);
}
static void regex_op(expr &lhs, boost::iterator_range<Iterator> &rhs, char op)
{
const std::string *subject = nullptr;
@ -930,6 +970,7 @@ namespace client
{ "additive_expression", "Expecting an expression." },
{ "multiplicative_expression", "Expecting an expression." },
{ "unary_expression", "Expecting an expression." },
{ "optional_parameter", "Expecting a closing brace or an optional parameter." },
{ "scalar_variable_reference", "Expecting a scalar variable reference."},
{ "variable_reference", "Expecting a variable reference."},
{ "regular_expression", "Expecting a regular expression."}
@ -1194,6 +1235,10 @@ namespace client
{ out = value.unary_not(out.it_range.begin()); }
static void to_int(expr<Iterator> &value, expr<Iterator> &out)
{ out = value.unary_integer(out.it_range.begin()); }
static void round(expr<Iterator> &value, expr<Iterator> &out)
{ out = value.round(out.it_range.begin()); }
// For indicating "no optional parameter".
static void noexpr(expr<Iterator> &out) { out.reset(); }
};
unary_expression = iter_pos[px::bind(&FactorActions::set_start_pos, _1, _val)] >> (
scalar_variable_reference(_r1) [ _val = _1 ]
@ -1207,7 +1252,12 @@ namespace client
[ px::bind(&expr<Iterator>::max, _val, _2) ]
| (kw["random"] > '(' > conditional_expression(_r1) [_val = _1] > ',' > conditional_expression(_r1) > ')')
[ px::bind(&MyContext::random<Iterator>, _r1, _val, _2) ]
| (kw["int"] > '(' > conditional_expression(_r1) > ')') [ px::bind(&FactorActions::to_int, _1, _val) ]
| (kw["digits"] > '(' > conditional_expression(_r1) [_val = _1] > ',' > conditional_expression(_r1) > optional_parameter(_r1))
[ px::bind(&expr<Iterator>::digits<false>, _val, _2, _3) ]
| (kw["zdigits"] > '(' > conditional_expression(_r1) [_val = _1] > ',' > conditional_expression(_r1) > optional_parameter(_r1))
[ px::bind(&expr<Iterator>::digits<true>, _val, _2, _3) ]
| (kw["int"] > '(' > conditional_expression(_r1) > ')') [ px::bind(&FactorActions::to_int, _1, _val) ]
| (kw["round"] > '(' > conditional_expression(_r1) > ')') [ px::bind(&FactorActions::round, _1, _val) ]
| (strict_double > iter_pos) [ px::bind(&FactorActions::double_, _1, _2, _val) ]
| (int_ > iter_pos) [ px::bind(&FactorActions::int_, _1, _2, _val) ]
| (kw[bool_] > iter_pos) [ px::bind(&FactorActions::bool_, _1, _2, _val) ]
@ -1216,6 +1266,12 @@ namespace client
);
unary_expression.name("unary_expression");
optional_parameter = iter_pos[px::bind(&FactorActions::set_start_pos, _1, _val)] >> (
lit(')') [ px::bind(&FactorActions::noexpr, _val) ]
| (lit(',') > conditional_expression(_r1) > ')') [ _val = _1 ]
);
optional_parameter.name("optional_parameter");
scalar_variable_reference =
variable_reference(_r1)[_a=_1] >>
(
@ -1234,6 +1290,8 @@ namespace client
keywords.add
("and")
("digits")
("zdigits")
("if")
("int")
//("inf")
@ -1244,6 +1302,7 @@ namespace client
("min")
("max")
("random")
("round")
("not")
("or")
("true");
@ -1266,6 +1325,7 @@ namespace client
debug(additive_expression);
debug(multiplicative_expression);
debug(unary_expression);
debug(optional_parameter);
debug(scalar_variable_reference);
debug(variable_reference);
debug(regular_expression);
@ -1303,6 +1363,8 @@ namespace client
RuleExpression multiplicative_expression;
// Number literals, functions, braced expressions, variable references, variable indexing references.
RuleExpression unary_expression;
// Accepting an optional parameter.
RuleExpression optional_parameter;
// Rule to capture a regular expression enclosed in //.
qi::rule<Iterator, boost::iterator_range<Iterator>(), spirit_encoding::space_type> regular_expression;
// Evaluate boolean expression into bool.

View File

@ -51,6 +51,20 @@ SCENARIO("Placeholder parser scripting", "[PlaceholderParser]") {
SECTION("math: max(13.4, -1238.1)") { REQUIRE(std::stod(parser.process("{max(13.4, -1238.1)}")) == Approx(13.4)); }
SECTION("math: int(13.4)") { REQUIRE(parser.process("{int(13.4)}") == "13"); }
SECTION("math: int(-13.4)") { REQUIRE(parser.process("{int(-13.4)}") == "-13"); }
SECTION("math: round(13.4)") { REQUIRE(parser.process("{round(13.4)}") == "13"); }
SECTION("math: round(-13.4)") { REQUIRE(parser.process("{round(-13.4)}") == "-13"); }
SECTION("math: round(13.6)") { REQUIRE(parser.process("{round(13.6)}") == "14"); }
SECTION("math: round(-13.6)") { REQUIRE(parser.process("{round(-13.6)}") == "-14"); }
SECTION("math: digits(5, 15)") { REQUIRE(parser.process("{digits(5, 15)}") == " 5"); }
SECTION("math: digits(5., 15)") { REQUIRE(parser.process("{digits(5., 15)}") == " 5"); }
SECTION("math: zdigits(5, 15)") { REQUIRE(parser.process("{zdigits(5, 15)}") == "000000000000005"); }
SECTION("math: zdigits(5., 15)") { REQUIRE(parser.process("{zdigits(5., 15)}") == "000000000000005"); }
SECTION("math: digits(5, 15, 8)") { REQUIRE(parser.process("{digits(5, 15, 8)}") == " 5.00000000"); }
SECTION("math: digits(5., 15, 8)") { REQUIRE(parser.process("{digits(5, 15, 8)}") == " 5.00000000"); }
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: zdigits(13.84375892476, 15, 8)") { REQUIRE(parser.process("{zdigits(13.84375892476, 15, 8)}") == "000013.84375892"); }
// Test the "coFloatOrPercent" and "xxx_extrusion_width" substitutions.
// first_layer_extrusion_width ratio_over first_layer_heigth.