diff options
author | Klaus Aehlig <klaus.aehlig@huawei.com> | 2024-04-16 16:07:21 +0200 |
---|---|---|
committer | Klaus Aehlig <klaus.aehlig@huawei.com> | 2024-04-16 18:10:54 +0200 |
commit | 4dc2cafb17c2dbdb58ada3a200e843a894bfc113 (patch) | |
tree | 88b45f8caaf22b3ed0bec8727b7f7b164b2dc24a | |
parent | 2c362dc379217da3c213b4698543ddfe4b7bd9cf (diff) | |
download | justbuild-4dc2cafb17c2dbdb58ada3a200e843a894bfc113.tar.gz |
expression language: add array access by index
-rw-r--r-- | doc/concepts/expressions.md | 10 | ||||
-rw-r--r-- | src/buildtool/build_engine/expression/evaluator.cpp | 27 | ||||
-rw-r--r-- | test/buildtool/build_engine/expression/expression.test.cpp | 68 |
3 files changed, 105 insertions, 0 deletions
diff --git a/doc/concepts/expressions.md b/doc/concepts/expressions.md index 192952f4..25da0fc8 100644 --- a/doc/concepts/expressions.md +++ b/doc/concepts/expressions.md @@ -354,6 +354,16 @@ those) argument(s) to obtain the final result. `"default"` argument (with default `null`) is evaluated and returned. + - `"[]"` This function takes two keyword arguments, `"index"` and + `"list"`. The `"list"` argument has to evaluate to a list. The + `"index"` argument has to evaluate to either a number (which + is then rounded to the nearest integer) or a string (which + is interpreted as an integer). If the index obtained in this + way is valid for the obtained list, the entry at that index + is returned; negative indices count from the end of the list. + Otherwise the `"default"` argument (with default `null`) is + evaluated and returned. + #### Constructs related to reporting of user errors Normally, if an error occurs during the evaluation the error is diff --git a/src/buildtool/build_engine/expression/evaluator.cpp b/src/buildtool/build_engine/expression/evaluator.cpp index 93e9836a..b8102e95 100644 --- a/src/buildtool/build_engine/expression/evaluator.cpp +++ b/src/buildtool/build_engine/expression/evaluator.cpp @@ -664,6 +664,32 @@ auto LookupExpr(SubExprEvaluator&& eval, return lookup; } +auto ArrayAccessExpr(SubExprEvaluator&& eval, + ExpressionPtr const& expr, + Configuration const& env) -> ExpressionPtr { + auto k = eval(expr["index"], env); + auto d = eval(expr["list"], env); + if (not d->IsList()) { + throw Evaluator::EvaluationError{fmt::format( + "List expected to be list, but found {}.", d->ToString())}; + } + auto len = static_cast<int64_t>(d->List().size()); + int64_t index = 0; + if (k->IsNumber()) { + index = static_cast<int64_t>(std::lround(k->Number())); + } + if (k->IsString()) { + index = std::atol(k->String().c_str()); + } + if (0 <= index and index < len) { + return d->List()[static_cast<std::size_t>(index)]; + } + if (index < 0 and len + index >= 0) { + return d->List()[static_cast<std::size_t>(len + index)]; + } + return eval(expr->Get("default", Expression::none_t()), env); +} + auto EmptyMapExpr(SubExprEvaluator&& /*eval*/, ExpressionPtr const& /*expr*/, Configuration const& @@ -1053,6 +1079,7 @@ auto const kBuiltInFunctions = {"reverse", UnaryExpr(Reverse)}, {"values", UnaryExpr(Values)}, {"lookup", LookupExpr}, + {"[]", ArrayAccessExpr}, {"empty_map", EmptyMapExpr}, {"singleton_map", SingletonMapExpr}, {"disjoint_map_union", DisjointUnionExpr}, diff --git a/test/buildtool/build_engine/expression/expression.test.cpp b/test/buildtool/build_engine/expression/expression.test.cpp index 83cd48ce..f85c41d2 100644 --- a/test/buildtool/build_engine/expression/expression.test.cpp +++ b/test/buildtool/build_engine/expression/expression.test.cpp @@ -1113,6 +1113,74 @@ TEST_CASE("Expression Evaluation", "[expression]") { // NOLINT CHECK(result_missing == Expression::FromJson(R"("axb")"_json)); } + SECTION("array index") { + auto expr = Expression::FromJson(R"( + { "type": "[]" + , "list": ["a", 101, "c", null, "e"] + , "index": "PLACEHOLDER" + , "default": "here be dragons" + })"_json); + REQUIRE(expr); + + // Index a number + expr = Replace(expr, "index", Expression::FromJson("2"_json)); + REQUIRE(expr); + auto num_result = expr.Evaluate(env, fcts); + REQUIRE(num_result); + CHECK(num_result == Expression::FromJson(R"("c")"_json)); + + // Index a string + expr = Replace(expr, "index", Expression::FromJson(R"("2")"_json)); + REQUIRE(expr); + auto string_result = expr.Evaluate(env, fcts); + REQUIRE(string_result); + CHECK(string_result == Expression::FromJson(R"("c")"_json)); + + // Index pointing to a null value + expr = Replace(expr, "index", Expression::FromJson("3"_json)); + REQUIRE(expr); + auto null_result = expr.Evaluate(env, fcts); + REQUIRE(null_result); + CHECK(null_result == Expression::FromJson("null"_json)); + + // Index out of range + expr = Replace(expr, "index", Expression::FromJson("5"_json)); + REQUIRE(expr); + auto default_result = expr.Evaluate(env, fcts); + REQUIRE(default_result); + CHECK(default_result == + Expression::FromJson(R"("here be dragons")"_json)); + + // Negative index, number + expr = Replace(expr, "index", Expression::FromJson("-3"_json)); + REQUIRE(expr); + auto neg_index_number = expr.Evaluate(env, fcts); + REQUIRE(neg_index_number); + CHECK(neg_index_number == Expression::FromJson(R"("c")"_json)); + + // Negative index, extreme number + expr = Replace(expr, "index", Expression::FromJson("-5"_json)); + REQUIRE(expr); + auto neg_index_number_extreme = expr.Evaluate(env, fcts); + REQUIRE(neg_index_number_extreme); + CHECK(neg_index_number_extreme == Expression::FromJson(R"("a")"_json)); + + // Negative index, string + expr = Replace(expr, "index", Expression::FromJson(R"("-3")"_json)); + REQUIRE(expr); + auto neg_index_string = expr.Evaluate(env, fcts); + REQUIRE(neg_index_string); + CHECK(neg_index_string == Expression::FromJson(R"("c")"_json)); + + // Index out of range, other direction + expr = Replace(expr, "index", Expression::FromJson("-6"_json)); + REQUIRE(expr); + auto other_default_result = expr.Evaluate(env, fcts); + REQUIRE(other_default_result); + CHECK(other_default_result == + Expression::FromJson(R"("here be dragons")"_json)); + } + SECTION("empty_map expression") { auto expr = Expression::FromJson(R"({"type": "empty_map"})"_json); REQUIRE(expr); |