From b523d93377d82994df32676b390562989410acfb Mon Sep 17 00:00:00 2001 From: Anton Alkin Date: Mon, 18 Aug 2025 13:56:57 +0200 Subject: [PATCH 1/2] DPL Analysis: support configurables in string expressions --- .../Core/include/Framework/Expressions.h | 15 ++++ Framework/Core/src/Expressions.cxx | 89 ++++++++++++++++++- Framework/Core/test/test_Expressions.cxx | 15 ++++ .../TestWorkflows/src/o2TestHistograms.cxx | 6 +- 4 files changed, 120 insertions(+), 5 deletions(-) diff --git a/Framework/Core/include/Framework/Expressions.h b/Framework/Core/include/Framework/Expressions.h index ed8d4ef24f402..2bcb73f4873f9 100644 --- a/Framework/Core/include/Framework/Expressions.h +++ b/Framework/Core/include/Framework/Expressions.h @@ -165,6 +165,14 @@ struct PlaceholderNode : LiteralNode { retrieve = [](InitContext& context, char const* name) { return LiteralNode::var_t{static_cast(context.options().get(name))}; }; } + template + PlaceholderNode(T defaultValue, std::string&& path) + : LiteralNode{defaultValue}, + name{path} + { + retrieve = [](InitContext& context, char const* name){ return LiteralNode::var_t{context.options().get(name)}; }; + } + void reset(InitContext& context) { value = retrieve(context, name.data()); @@ -596,6 +604,13 @@ inline Node protect0(Node&& expr) return ifnode(nabs(Node{copy}) < o2::constants::math::Almost0, o2::constants::math::Almost0, Node{copy}); } +/// context-independent configurable +template +inline Node ncfg(T defaultValue, std::string path) +{ + return PlaceholderNode(defaultValue, path); +} + /// A struct, containing the root of the expression tree struct Filter { Filter() = default; diff --git a/Framework/Core/src/Expressions.cxx b/Framework/Core/src/Expressions.cxx index 1d4dec734ff21..9bf86a7acf4e1 100644 --- a/Framework/Core/src/Expressions.cxx +++ b/Framework/Core/src/Expressions.cxx @@ -64,6 +64,17 @@ constexpr std::array mapping{ "nbitwise_not", "ifnode"}; +constexpr std::array cfgtypes{ + "uint16_t", // 0 + "int16_t", // 1 + "uint32_t", // 2 + "int32_t", // 3 + "uint64_t", // 4 + "int64_t", // 5 + "float", // 6 + "double" // 7 +}; + /// math constants to recognize in string expressions constexpr std::array mathConstants{ "Almost0", @@ -813,7 +824,8 @@ Tokenizer::Tokenizer(std::string const& input) { LastChar = ' '; if (!source.empty()) { - source.erase(std::remove_if(source.begin(), source.end(), ::isspace), source.end()); + source.erase(std::remove_if(source.begin(), source.end(), ::isspace), source.end()); // strip whitespaces + source.erase(std::remove(source.begin(), source.end(), '\"'), source.end()); // strip quotes } current = source.begin(); } @@ -827,7 +839,8 @@ void Tokenizer::reset(std::string const& input) FloatValue = 0.f; source = input; if (!source.empty()) { - source.erase(std::remove_if(source.begin(), source.end(), ::isspace), source.end()); + source.erase(std::remove_if(source.begin(), source.end(), ::isspace), source.end()); // strip whitespaces + source.erase(std::remove(source.begin(), source.end(), '\"'), source.end()); // strip quotes } current = source.begin(); currentToken = Token::Unexpected; @@ -1202,6 +1215,78 @@ std::unique_ptr Parser::parseBase(Tokenizer& tk) } tk.nextToken(); return node; + } else if (id == "ncfg") { // configurable placeholder, 3 args none of them can be expressions + int args = 0; + std::string type; + std::string value; + std::string path; + while (tk.currentToken != ')') { + do { + tk.nextToken(); + if (args == 0) { // type + type = tk.TokenStr; + tk.nextToken(); + } else if (args == 1) { // value + value = tk.TokenStr; + tk.nextToken(); + } else if (args == 2) { // path + path = tk.TokenStr; + tk.nextToken(); + } else { + throw runtime_error_f("Extra argument in configurable: %s", tk.TokenStr.c_str()); + } + ++args; + } while (tk.currentToken == ','); + } + tk.nextToken(); + auto locate = std::find(cfgtypes.begin(), cfgtypes.end(), type); + if (locate == cfgtypes.end()) { + throw runtime_error_f("Unsupported type in configurable: %s", type.c_str()); + } + switch (std::distance(cfgtypes.begin(), locate)) { + case 0: + return std::make_unique( + PlaceholderNode( + static_cast(std::stoi(value)), + std::move(path))); + case 1: + return std::make_unique( + PlaceholderNode( + static_cast(std::stoi(value)), + std::move(path))); + case 2: + return std::make_unique( + PlaceholderNode( + static_cast(std::stoi(value)), + std::move(path))); + case 3: + return std::make_unique( + PlaceholderNode( + static_cast(std::stoi(value)), + std::move(path))); + case 4: + return std::make_unique( + PlaceholderNode( + static_cast(std::stoll(value)), + std::move(path))); + case 5: + return std::make_unique( + PlaceholderNode( + static_cast(std::stol(value)), + std::move(path))); + case 6: + return std::make_unique( + PlaceholderNode( + std::stof(value), + std::move(path))); + case 7: + return std::make_unique( + PlaceholderNode( + std::stod(value), + std::move(path))); + default: + throw runtime_error_f("Unsupported type in configurable: %s", type.c_str()); + } } else { // normal function auto node = std::make_unique(opFromToken(id), LiteralNode{-1}, LiteralNode{-1}); int args = 0; diff --git a/Framework/Core/test/test_Expressions.cxx b/Framework/Core/test/test_Expressions.cxx index eef0375f46086..4c6fc51795ca8 100644 --- a/Framework/Core/test/test_Expressions.cxx +++ b/Framework/Core/test/test_Expressions.cxx @@ -375,4 +375,19 @@ TEST_CASE("TestStringExpressionsParsing") auto treef2 = createExpressionTree(tf2, schema); REQUIRE(treef1->ToString() == treef2->ToString()); + + Configurable pTCut{"pTCut", 0.5f, "Lower pT limit"}; + Filter pcfg1 = o2::aod::track::pt > pTCut; + Filter pcfg2 = Parser::parse("o2::aod::track::pt > ncfg(float, 0.5, \"pTCut\")"); + auto pcfg1specs = createOperations(pcfg1); + auto pcfg2specs = createOperations(pcfg2); + + REQUIRE(pcfg2.node->right->self.index() == 3); + REQUIRE(pcfg2specs[0].right == (DatumSpec{LiteralNode::var_t{0.5f}, atype::FLOAT})); + + schema = std::make_shared(std::vector{o2::aod::track::Pt::asArrowField()}); + auto tree1c = createExpressionTree(pcfg1specs, schema); + auto tree2c = createExpressionTree(pcfg2specs, schema); + + REQUIRE(tree1c->ToString() == tree2c->ToString()); } diff --git a/Framework/TestWorkflows/src/o2TestHistograms.cxx b/Framework/TestWorkflows/src/o2TestHistograms.cxx index 326170dc56eff..38cfc00b6df7c 100644 --- a/Framework/TestWorkflows/src/o2TestHistograms.cxx +++ b/Framework/TestWorkflows/src/o2TestHistograms.cxx @@ -45,7 +45,7 @@ struct EtaAndClsHistogramsSimple { void init(InitContext&) { if (!trackFilterString->empty()) { - trackFilter = trackFilterString; + trackFilter = Parser::parse((std::string)trackFilterString); } } @@ -68,7 +68,7 @@ struct EtaAndClsHistogramsIUSimple { void init(InitContext&) { if (!trackFilterString->empty()) { - trackFilter = trackFilterString; + trackFilter = Parser::parse((std::string)trackFilterString); } } @@ -90,7 +90,7 @@ struct EtaAndClsHistogramsFull { void init(InitContext&) { if (!trackFilterString->empty()) { - trackFilter = trackFilterString; + trackFilter = Parser::parse((std::string)trackFilterString); } } From a8cea17feb6756d31c0e0a8d693cff3dbd245fa9 Mon Sep 17 00:00:00 2001 From: ALICE Action Bot Date: Mon, 18 Aug 2025 12:02:39 +0000 Subject: [PATCH 2/2] Please consider the following formatting changes --- .../Core/include/Framework/Expressions.h | 2 +- Framework/Core/src/Expressions.cxx | 20 +++++++++---------- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/Framework/Core/include/Framework/Expressions.h b/Framework/Core/include/Framework/Expressions.h index 2bcb73f4873f9..e2fdd0493d033 100644 --- a/Framework/Core/include/Framework/Expressions.h +++ b/Framework/Core/include/Framework/Expressions.h @@ -170,7 +170,7 @@ struct PlaceholderNode : LiteralNode { : LiteralNode{defaultValue}, name{path} { - retrieve = [](InitContext& context, char const* name){ return LiteralNode::var_t{context.options().get(name)}; }; + retrieve = [](InitContext& context, char const* name) { return LiteralNode::var_t{context.options().get(name)}; }; } void reset(InitContext& context) diff --git a/Framework/Core/src/Expressions.cxx b/Framework/Core/src/Expressions.cxx index 9bf86a7acf4e1..05a3462d6e4da 100644 --- a/Framework/Core/src/Expressions.cxx +++ b/Framework/Core/src/Expressions.cxx @@ -65,14 +65,14 @@ constexpr std::array mapping{ "ifnode"}; constexpr std::array cfgtypes{ - "uint16_t", // 0 - "int16_t", // 1 - "uint32_t", // 2 - "int32_t", // 3 - "uint64_t", // 4 - "int64_t", // 5 - "float", // 6 - "double" // 7 + "uint16_t", // 0 + "int16_t", // 1 + "uint32_t", // 2 + "int32_t", // 3 + "uint64_t", // 4 + "int64_t", // 5 + "float", // 6 + "double" // 7 }; /// math constants to recognize in string expressions @@ -825,7 +825,7 @@ Tokenizer::Tokenizer(std::string const& input) LastChar = ' '; if (!source.empty()) { source.erase(std::remove_if(source.begin(), source.end(), ::isspace), source.end()); // strip whitespaces - source.erase(std::remove(source.begin(), source.end(), '\"'), source.end()); // strip quotes + source.erase(std::remove(source.begin(), source.end(), '\"'), source.end()); // strip quotes } current = source.begin(); } @@ -840,7 +840,7 @@ void Tokenizer::reset(std::string const& input) source = input; if (!source.empty()) { source.erase(std::remove_if(source.begin(), source.end(), ::isspace), source.end()); // strip whitespaces - source.erase(std::remove(source.begin(), source.end(), '\"'), source.end()); // strip quotes + source.erase(std::remove(source.begin(), source.end(), '\"'), source.end()); // strip quotes } current = source.begin(); currentToken = Token::Unexpected;