summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorKlaus Aehlig <klaus.aehlig@huawei.com>2024-04-16 16:07:21 +0200
committerKlaus Aehlig <klaus.aehlig@huawei.com>2024-04-16 18:10:54 +0200
commit4dc2cafb17c2dbdb58ada3a200e843a894bfc113 (patch)
tree88b45f8caaf22b3ed0bec8727b7f7b164b2dc24a
parent2c362dc379217da3c213b4698543ddfe4b7bd9cf (diff)
downloadjustbuild-4dc2cafb17c2dbdb58ada3a200e843a894bfc113.tar.gz
expression language: add array access by index
-rw-r--r--doc/concepts/expressions.md10
-rw-r--r--src/buildtool/build_engine/expression/evaluator.cpp27
-rw-r--r--test/buildtool/build_engine/expression/expression.test.cpp68
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);