From 0aac0478e769af0e016cad2f9c1f9a0df93720f1 Mon Sep 17 00:00:00 2001 From: Noisyfox Date: Tue, 23 Dec 2025 23:44:43 +0800 Subject: [PATCH] GCode Macro: Fix placeholder parser bugs and enable tests (#11485) * Support creating vector variable * Add tests for placeholder parser variables * Fix placeholder line width substitution & tests * Enable PlaceholderParser tests * fix build errors --------- Co-authored-by: SoftFever --- scripts/run_unit_tests.sh | 2 +- src/libslic3r/PlaceholderParser.cpp | 13 +- tests/libslic3r/test_placeholder_parser.cpp | 176 +++++++++++++++++--- 3 files changed, 157 insertions(+), 34 deletions(-) diff --git a/scripts/run_unit_tests.sh b/scripts/run_unit_tests.sh index 081467b300..292c6c9d3f 100755 --- a/scripts/run_unit_tests.sh +++ b/scripts/run_unit_tests.sh @@ -9,4 +9,4 @@ ROOT_DIR="$(dirname "$0")/.." cd "${ROOT_DIR}" || exit 1 -ctest --test-dir build/tests --output-junit "$(pwd)/ctest_results.xml" --output-on-failure -j +ctest --test-dir build/tests -L "Http|PlaceholderParser" --output-junit "$(pwd)/ctest_results.xml" --output-on-failure -j diff --git a/src/libslic3r/PlaceholderParser.cpp b/src/libslic3r/PlaceholderParser.cpp index 92e4210f3c..f03a5cee38 100644 --- a/src/libslic3r/PlaceholderParser.cpp +++ b/src/libslic3r/PlaceholderParser.cpp @@ -1045,8 +1045,8 @@ namespace client 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. + if (boost::ends_with(opt_key, "line_width")) { + // Line 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. @@ -1060,8 +1060,8 @@ namespace client 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. + if (boost::ends_with(opt_def->ratio_over, "line_width")) { + // Line 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; @@ -2197,9 +2197,8 @@ namespace client initializer_list(_r1)[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. - // Orca todo: following code cause strange build errors with MSVC C++17 - // | eps(px::bind(&MyContext::could_be_vector_variable_reference, _b)) >> - // variable_reference(_r1)[px::val(qi::_pass) = px::bind(&MyContext::vector_variable_new_from_copy, _r1, _a, _b, _1)] + | eps(px::bind(&MyContext::could_be_vector_variable_reference, _b)) >> + variable_reference(_r1)[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::scalar_variable_new_from_scalar_expression, _r1, _a, _b, _1)] diff --git a/tests/libslic3r/test_placeholder_parser.cpp b/tests/libslic3r/test_placeholder_parser.cpp index 2394aad4bf..93c848b3a2 100644 --- a/tests/libslic3r/test_placeholder_parser.cpp +++ b/tests/libslic3r/test_placeholder_parser.cpp @@ -14,40 +14,22 @@ SCENARIO("Placeholder parser scripting", "[PlaceholderParser]") { { "nozzle_diameter", "0.6;0.6;0.6;0.6" }, { "nozzle_temperature", "357;359;363;378" } }); - // To let the PlaceholderParser throw when referencing initial_layer_line_width if it is set to percent, as the PlaceholderParser does not know + // To test the "min_width_top_surface" over "inner_wall_line_width". + config.option("inner_wall_line_width")->value = 150.; + config.option("inner_wall_line_width")->percent = true; + // To let the PlaceholderParser throw when referencing scarf_joint_speed if it is set to percent, as the PlaceholderParser does not know // a percent to what. - config.option("initial_layer_line_width")->value = 50.; - config.option("initial_layer_line_width")->percent = true; + config.option("scarf_joint_speed")->value = 50.; + config.option("scarf_joint_speed")->percent = true; parser.apply_config(config); parser.set("foo", 0); parser.set("bar", 2); parser.set("num_extruders", 4); - SECTION("nested config options (legacy syntax)") { REQUIRE(parser.process("[nozzle_temperature_[foo]]") == "357"); } + SECTION("nested config options (legacy syntax)") { REQUIRE(parser.process("[nozzle_temperature[foo]]") == "357"); } SECTION("array reference") { REQUIRE(parser.process("{nozzle_temperature[foo]}") == "357"); } - SECTION("whitespaces and newlines are maintained") { REQUIRE(parser.process("test [ nozzle_temperature_ [foo] ] \n hu") == "test 357 \n hu"); } - - // Test the "coFloatOrPercent" and "xxx_width" substitutions. - - // FIXME: Don't know what exactly this referred to in Prusaslicer or - // whether it should apply to Orca or not. - // {outer_wall_line_width} returns as its default value, 0. - // SECTION("outer_wall_line_width") { REQUIRE(std::stod(parser.process("{outer_wall_line_width}")) == Catch::Approx(0.67500001192092896)); } - SECTION("support_object_xy_distance") { REQUIRE(std::stod(parser.process("{support_object_xy_distance}")) == Catch::Approx(0.35)); } - // initial_layer_line_width ratio over nozzle_diameter. - // FIXME: either something else which correctly calculates a ratio should be here, - // or something else should be found for for the REQUIRE_THROWS - // SECTION("initial_layer_line_width") { REQUIRE(std::stod(parser.process("{initial_layer_line_width}")) == Catch::Approx(0.9)); } - // small_perimeter_speed ratio over outer_wall_speed - SECTION("small_perimeter_speed") { REQUIRE(std::stod(parser.process("{small_perimeter_speed}")) == Catch::Approx(30.)); } - // infill_wall_overlap over inner_wall_line_width - // FIXME: Shouldn't this return the calculated value and not the percentage 15? - // SECTION("infill_wall_overlap") { REQUIRE(std::stod(parser.process("{infill_wall_overlap}")) == Catch::Approx(0.16875)); } - - // If initial_layer_line_width is set to percent, then it is applied over respective extrusion types by overriding their respective speeds. - // The PlaceholderParser has no way to know which extrusion type the caller has in mind, therefore it throws. - SECTION("initial_layer_line_width throws failed to resolve the ratio_over dependencies") { REQUIRE_THROWS(parser.process("{initial_layer_line_width}")); } + SECTION("whitespaces and newlines are maintained") { REQUIRE(parser.process("test [ nozzle_temperature [foo] ] \n hu") == "test 357 \n hu"); } // Test the math expressions. SECTION("math: 2*3") { REQUIRE(parser.process("{2*3}") == "6"); } @@ -84,6 +66,20 @@ SCENARIO("Placeholder parser scripting", "[PlaceholderParser]") { 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))}")) == Catch::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))}")) == Catch::Approx(20.)); } + // Test the "coFloatOrPercent" and "xxx_line_width" substitutions. + // min_width_top_surface ratio_over inner_wall_line_width. + SECTION("line_width") { REQUIRE(std::stod(parser.process("{line_width}")) == Catch::Approx(0.67500001192092896)); } + SECTION("min_width_top_surface") { REQUIRE(std::stod(parser.process("{min_width_top_surface}")) == Catch::Approx(2.7)); } + // Orca: this one is not coFloatOrPercent + //SECTION("support_object_xy_distance") { REQUIRE(std::stod(parser.process("{support_object_xy_distance}")) == Catch::Approx(0.3375)); } + // small_perimeter_speed over outer_wall_speed + SECTION("small_perimeter_speed") { REQUIRE(std::stod(parser.process("{small_perimeter_speed}")) == Catch::Approx(30.)); } + // infill_anchor over sparse_infill_line_width + SECTION("infill_anchor") { REQUIRE(std::stod(parser.process("{infill_anchor}")) == Catch::Approx(2.7)); } + // If scarf_joint_speed is set to percent, then it is applied over respective extrusion types by overriding their respective speeds. + // The PlaceholderParser has no way to know which extrusion type the caller has in mind, therefore it throws. + SECTION("scarf_joint_speed") { REQUIRE_THROWS(parser.process("{scarf_joint_speed}")); } + // Test the boolean expression parser. auto boolean_expression = [&parser](const std::string& templ) { return parser.evaluate_boolean_expression(templ, parser.config()); }; @@ -116,3 +112,131 @@ 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)")); } } + +SCENARIO("Placeholder parser variables", "[PlaceholderParser]") { + PlaceholderParser parser; + auto config = DynamicPrintConfig::full_print_config(); + + config.set_deserialize_strict({ + { "filament_notes", "testnotes" }, + { "enable_pressure_advance", "1" }, + { "nozzle_diameter", "0.6;0.6;0.6;0.6" }, + { "nozzle_temperature", "357;359;363;378" } + }); + + PlaceholderParser::ContextData context_with_global_dict; + context_with_global_dict.global_config = std::make_unique(); + + 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 repeat()") { REQUIRE(parser.process("{local myint = repeat(2*3, 4*6)}{myint[5]}", 0, nullptr, nullptr, nullptr) == "24"); } + SECTION("create a strings local variable with repeat()") { REQUIRE(parser.process("{local mystr = repeat(2*3, \"mine\" + \"only\" + \"mine\")}{mystr[5]}", 0, nullptr, nullptr, nullptr) == "mineonlymine"); } + SECTION("create a bools local variable with repeat()") { REQUIRE(parser.process("{local mybool = repeat(5, 1 + 1 == 2)}{mybool[4]}", 0, nullptr, nullptr, nullptr) == "true"); } + SECTION("create an ints global variable with repeat()") { REQUIRE(parser.process("{global myint = repeat(2*3, 4*6)}{myint[5]}", 0, nullptr, nullptr, &context_with_global_dict) == "24"); } + SECTION("create a strings global variable with repeat()") { REQUIRE(parser.process("{global mystr = repeat(2*3, \"mine\" + \"only\" + \"mine\")}{mystr[5]}", 0, nullptr, nullptr, &context_with_global_dict) == "mineonlymine"); } + SECTION("create a bools global variable with repeat()") { REQUIRE(parser.process("{global mybool = repeat(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 = nozzle_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_pressure_advance}{mybool[0]}", 0, &config, nullptr, nullptr) == "true"); } + SECTION("create an ints global variable by a copy") { REQUIRE(parser.process("{global myint = nozzle_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_pressure_advance}{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 = nozzle_temperature}{myint = repeat(2*3, 4*6)}{myint[5]}", 0, &config, nullptr, nullptr) == "24"); + REQUIRE(parser.process("{local myint = nozzle_temperature}{myint = (2*3, 4*6)}{myint[1]}", 0, &config, nullptr, nullptr) == "24"); + REQUIRE(parser.process("{local myint = nozzle_temperature}{myint = (1)}{myint = nozzle_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 = repeat(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_pressure_advance}{mybool = repeat(2*3, true)}{mybool[5]}", 0, &config, nullptr, nullptr) == "true"); + REQUIRE(parser.process("{local mybool = enable_pressure_advance}{mybool = (false, true)}{mybool[1]}", 0, &config, nullptr, nullptr) == "true"); + REQUIRE(parser.process("{local mybool = enable_pressure_advance}{mybool = (false, false)}{mybool = enable_pressure_advance}{mybool[0]}", 0, &config, nullptr, nullptr) == "true"); + } + + SECTION("size() of a non-empty vector returns the right size") { REQUIRE(parser.process("{local myint = (0, 1, 2, 3)}{size(myint)}", 0, nullptr, nullptr, nullptr) == "4"); } + SECTION("size() of a an empty vector returns the right size") { REQUIRE(parser.process("{local myint = (0);myint=();size(myint)}", 0, nullptr, nullptr, nullptr) == "0"); } + SECTION("empty() of a non-empty vector returns false") { REQUIRE(parser.process("{local myint = (0, 1, 2, 3)}{empty(myint)}", 0, nullptr, nullptr, nullptr) == "false"); } + SECTION("empty() of a an empty vector returns true") { REQUIRE(parser.process("{local myint = (0);myint=();empty(myint)}", 0, nullptr, nullptr, nullptr) == "true"); } + + SECTION("nested if with new variables") { + std::string script = + "{if 1 == 1}{local myints = (5, 4, 3, 2, 1)}{else}{local myfloats = (1., 2., 3., 4., 5., 6., 7.)}{endif}" + "{myints[1]},{size(myints)}"; + REQUIRE(parser.process(script, 0, nullptr, nullptr, nullptr) == "4,5"); + } + SECTION("nested if with new variables 2") { + std::string script = + "{if 1 == 0}{local myints = (5, 4, 3, 2, 1)}{else}{local myfloats = (1., 2., 3., 4., 5., 6., 7.)}{endif}" + "{size(myfloats)}"; + REQUIRE(parser.process(script, 0, nullptr, nullptr, nullptr) == "7"); + } + SECTION("nested if with new variables 2, mixing }{ with ;") { + std::string script = + "{if 1 == 0 then local myints = (5, 4, 3, 2, 1);else;local myfloats = (1., 2., 3., 4., 5., 6., 7.);endif}" + "{size(myfloats)}"; + REQUIRE(parser.process(script, 0, nullptr, nullptr, nullptr) == "7"); + } + SECTION("nested if with new variables, two level") { + std::string script = + "{if 1 == 1}{if 2 == 3}{nejaka / haluz}{else}{local myints = (6, 5, 4, 3, 2, 1)}{endif}{else}{if zase * haluz}{else}{local myfloats = (1., 2., 3., 4., 5., 6., 7.)}{endif}{endif}" + "{size(myints)}"; + REQUIRE(parser.process(script, 0, nullptr, nullptr, nullptr) == "6"); + } + SECTION("if with empty block and ;") { + std::string script = + "{if false then else;local myfloats = (1., 2., 3., 4., 5., 6., 7.);endif}" + "{size(myfloats)}"; + REQUIRE(parser.process(script, 0, nullptr, nullptr, nullptr) == "7"); + } + SECTION("nested if with new variables, two level, mixing }{ with ;") { + std::string script = + "{if 1 == 1 then if 2 == 3}nejaka / haluz{else local myints = (6, 5, 4, 3, 2, 1) endif else if zase * haluz then else local myfloats = (1., 2., 3., 4., 5., 6., 7.) endif endif}" + "{size(myints)}"; + REQUIRE(parser.process(script, 0, nullptr, nullptr, nullptr) == "6"); + } + SECTION("nested if with new variables, two level, mixing }{ with ; 2") { + std::string script = + "{if 1 == 1 then if 2 == 3 then nejaka / haluz else}{local myints = (6, 5, 4, 3, 2, 1)}{endif else if zase * haluz then else local myfloats = (1., 2., 3., 4., 5., 6., 7.) endif endif}" + "{size(myints)}"; + REQUIRE(parser.process(script, 0, nullptr, nullptr, nullptr) == "6"); + } + SECTION("nested if with new variables, two level, mixing }{ with ; 3") { + std::string script = + "{if 1 == 1 then if 2 == 3 then nejaka / haluz else}{local myints = (6, 5, 4, 3, 2, 1)}{endif else}{if zase * haluz}{else local myfloats = (1., 2., 3., 4., 5., 6., 7.) endif}{endif}" + "{size(myints)}"; + REQUIRE(parser.process(script, 0, nullptr, nullptr, nullptr) == "6"); + } + SECTION("if else completely empty") { REQUIRE(parser.process("{if false then elsif false then else endif}", 0, nullptr, nullptr, nullptr) == ""); } +}