From 199487b5ba387653bbec36d6dcea85819c90994c Mon Sep 17 00:00:00 2001 From: Klaus Aehlig Date: Mon, 22 Apr 2024 14:33:31 +0200 Subject: expressions: add generic assertions --- doc/concepts/expressions.md | 9 ++++++++ .../build_engine/expression/evaluator.cpp | 21 +++++++++++++++++ .../build_engine/expression/expression.test.cpp | 27 ++++++++++++++++++++++ 3 files changed, 57 insertions(+) diff --git a/doc/concepts/expressions.md b/doc/concepts/expressions.md index 25da0fc8..80378191 100644 --- a/doc/concepts/expressions.md +++ b/doc/concepts/expressions.md @@ -391,3 +391,12 @@ that evaluation included in the error message presented to the user. two (or more) maps contain the same key, but map it to different values. It is also an error if the argument is a name-containing value. + + - `"assert"` Evaluate the argument (given by the parameter `"$1"`); + then evaluate the expression `"predicate"` with the variable given + at the key `"var"` (which has to be a string literal if given, + default value is `"_"`) bound to that value. If the predicate + evaluates to a true value, return the result of evaluating the + argument, otherwise fail; in evaluating the failure message + `"msg"`, also keep the variable specified by `"var"` bound to + the result of evaluating the argument. diff --git a/src/buildtool/build_engine/expression/evaluator.cpp b/src/buildtool/build_engine/expression/evaluator.cpp index b8102e95..f91e5b13 100644 --- a/src/buildtool/build_engine/expression/evaluator.cpp +++ b/src/buildtool/build_engine/expression/evaluator.cpp @@ -1027,6 +1027,26 @@ auto FailExpr(SubExprEvaluator&& eval, msg->ToString(), false, /* user error*/ true); } +auto AssertExpr(SubExprEvaluator&& eval, + ExpressionPtr const& expr, + Configuration const& env) -> ExpressionPtr { + auto val = eval(expr["$1"], env); + auto const& var = expr->Get("var", "_"s); + auto pred = eval(expr["predicate"], env.Update(var->String(), val)); + if (ValueIsTrue(pred)) { + return val; + } + auto msg_expr = expr->Get("msg", Expression::kNone); + std::string msg; + try { + auto msg_val = eval(msg_expr, env.Update(var->String(), val)); + msg = msg_val->ToString(); + } catch (std::exception const&) { + msg = "[non evaluating term] " + msg_expr->ToString(); + } + throw Evaluator::EvaluationError(msg, false, /* user error */ true); +} + auto AssertNonEmptyExpr(SubExprEvaluator&& eval, ExpressionPtr const& expr, Configuration const& env) -> ExpressionPtr { @@ -1057,6 +1077,7 @@ auto const kBuiltInFunctions = {"case", CaseExpr}, {"case*", SeqCaseExpr}, {"fail", FailExpr}, + {"assert", AssertExpr}, {"assert_non_empty", AssertNonEmptyExpr}, {"context", ContextExpr}, {"==", EqualExpr}, diff --git a/test/buildtool/build_engine/expression/expression.test.cpp b/test/buildtool/build_engine/expression/expression.test.cpp index 256eb392..18189baf 100644 --- a/test/buildtool/build_engine/expression/expression.test.cpp +++ b/test/buildtool/build_engine/expression/expression.test.cpp @@ -1600,6 +1600,33 @@ TEST_CASE("Expression Assertions", "[expression]") { [&](auto msg) { log_map << msg; })); CHECK(log_map.str().find("Found-Empty!!") != std::string::npos); } + + SECTION("assert") { + auto expr = Expression::FromJson(R"( + { "type": "assert" + , "predicate": {"type": "[]", "index": 0 + , "list": {"type": "var", "name": "_"}} + , "msg": ["First entry UNTRUE", {"type": "var", "name": "_"}] + , "$1": {"type": "++", "$1": [{"type": "var", "name": "x"} + , ["b", "c"]]} + })"_json); + REQUIRE(expr); + + CHECK(expr.Evaluate( + env.Update("x", Expression::FromJson(R"(["a"])"_json)), + fcts) == Expression::FromJson(R"(["a", "b", "c"])"_json)); + + std::stringstream log{}; + CHECK(not expr.Evaluate( + env.Update("x", Expression::FromJson(R"([false, "foo"])"_json)), + fcts, + [&](auto msg) { log << msg; })); + // log must contain the canoncial (minimal) repesentation of evaluating + // "msg" + CHECK( + log.str().find(R"(["First entry UNTRUE",[false,"foo","b","c"])"s) != + std::string::npos); + } } TEST_CASE("Expression hash computation", "[expression]") { -- cgit v1.2.3