From 2eaffa5cfb8ad8e5d18ac1ed61b4ae6b976852ee Mon Sep 17 00:00:00 2001 From: Klaus Aehlig Date: Mon, 25 Mar 2024 11:57:42 +0100 Subject: Expression language: add float operations "*" and "+" Numerical values are used at some places in justbuild: as value for timeout scaling, as well as by the "range" expression that is used, e.g., to define repreated test runs. Therefore, improve support for numerical values by adding basic operations. --- doc/concepts/expressions.md | 8 ++++ .../build_engine/expression/evaluator.cpp | 38 +++++++++++++++++ .../build_engine/expression/expression.test.cpp | 48 ++++++++++++++++++++++ 3 files changed, 94 insertions(+) diff --git a/doc/concepts/expressions.md b/doc/concepts/expressions.md index dcd567af..192952f4 100644 --- a/doc/concepts/expressions.md +++ b/doc/concepts/expressions.md @@ -259,6 +259,14 @@ those) argument(s) to obtain the final result. - `"++"` The argument has to be a list of lists. The result is the concatenation of those lists. + - `"+"` The argument has to be a list of numbers. The result is + their sum (where the sum of the empty list is, of course, the + neutral element 0). + + - `"*"` The argument has to be a list of numbers. The result + is their product (where the producut of the empty list is, of + course, the neutral element 1). + - `"map_union"` The argument has to be a list of maps. The result is a map containing as keys the union of the keys of the maps in that list. For each key, the value is the value diff --git a/src/buildtool/build_engine/expression/evaluator.cpp b/src/buildtool/build_engine/expression/evaluator.cpp index d2cee809..4499552b 100644 --- a/src/buildtool/build_engine/expression/evaluator.cpp +++ b/src/buildtool/build_engine/expression/evaluator.cpp @@ -82,6 +82,42 @@ auto Flatten(ExpressionPtr const& expr) -> ExpressionPtr { return ExpressionPtr{result}; } +auto Addition(ExpressionPtr const& expr) -> ExpressionPtr { + if (not expr->IsList()) { + throw Evaluator::EvaluationError{fmt::format( + "Addition expects a list, but found: {}", expr->ToString())}; + } + Expression::number_t result = 0.0; + auto const& list = expr->List(); + std::for_each(list.begin(), list.end(), [&](auto const& f) { + if (not f->IsNumber()) { + throw Evaluator::EvaluationError{fmt::format( + "Non-number entry found for argument to addition: {}", + f->ToString())}; + } + result += f->Number(); + }); + return ExpressionPtr(result); +} + +auto Multiplication(ExpressionPtr const& expr) -> ExpressionPtr { + if (not expr->IsList()) { + throw Evaluator::EvaluationError{fmt::format( + "Multiplication expects a list, but found: {}", expr->ToString())}; + } + Expression::number_t result = 1.0; + auto const& list = expr->List(); + std::for_each(list.begin(), list.end(), [&](auto const& f) { + if (not f->IsNumber()) { + throw Evaluator::EvaluationError{fmt::format( + "Non-number entry found for argument to multiplication: {}", + f->ToString())}; + } + result *= f->Number(); + }); + return ExpressionPtr(result); +} + auto All(ExpressionPtr const& list) -> ExpressionPtr { for (auto const& c : list->List()) { if (not ValueIsTrue(c)) { @@ -954,6 +990,8 @@ auto const kBuiltInFunctions = {"and", AndExpr}, {"or", OrExpr}, {"++", UnaryExpr(Flatten)}, + {"+", UnaryExpr(Addition)}, + {"*", UnaryExpr(Multiplication)}, {"nub_right", UnaryExpr(NubRight)}, {"range", UnaryExpr(Range)}, {"change_ending", ChangeEndingExpr}, diff --git a/test/buildtool/build_engine/expression/expression.test.cpp b/test/buildtool/build_engine/expression/expression.test.cpp index 242c0f4a..83257bf8 100644 --- a/test/buildtool/build_engine/expression/expression.test.cpp +++ b/test/buildtool/build_engine/expression/expression.test.cpp @@ -744,6 +744,54 @@ TEST_CASE("Expression Evaluation", "[expression]") { // NOLINT CHECK(result == Expression::FromJson(R"(["foo", "bar", "baz"])"_json)); } + SECTION("+ expression") { + auto expr_empty = Expression::FromJson(R"( + { "type": "+" + , "$1": [] + })"_json); + REQUIRE(expr_empty); + + auto result_empty = expr_empty.Evaluate(env, fcts); + REQUIRE(result_empty); + REQUIRE(result_empty->IsNumber()); + CHECK(result_empty == Expression::FromJson(R"(0.0)"_json)); + + auto expr = Expression::FromJson(R"( + { "type": "+" + , "$1": [2, 3, 7, -1] + })"_json); + REQUIRE(expr); + + auto result = expr.Evaluate(env, fcts); + REQUIRE(result); + REQUIRE(result->IsNumber()); + CHECK(result == Expression::FromJson(R"(11.0)"_json)); + } + + SECTION("* expression") { + auto expr_empty = Expression::FromJson(R"( + { "type": "*" + , "$1": [] + })"_json); + REQUIRE(expr_empty); + + auto result_empty = expr_empty.Evaluate(env, fcts); + REQUIRE(result_empty); + REQUIRE(result_empty->IsNumber()); + CHECK(result_empty == Expression::FromJson(R"(1.0)"_json)); + + auto expr = Expression::FromJson(R"( + { "type": "*" + , "$1": [2, 3, 7, -1] + })"_json); + REQUIRE(expr); + + auto result = expr.Evaluate(env, fcts); + REQUIRE(result); + REQUIRE(result->IsNumber()); + CHECK(result == Expression::FromJson(R"(-42.0)"_json)); + } + SECTION("nub_right expression") { auto expr = Expression::FromJson(R"( {"type": "nub_right" -- cgit v1.2.3