diff --git a/Framework/Core/include/Framework/ASoA.h b/Framework/Core/include/Framework/ASoA.h index ccf2cab5e6807..9703f8eb26b9d 100644 --- a/Framework/Core/include/Framework/ASoA.h +++ b/Framework/Core/include/Framework/ASoA.h @@ -34,7 +34,6 @@ #include #include // IWYU pragma: export #include -#include namespace o2::framework { @@ -2307,7 +2306,7 @@ consteval static std::string_view namespace_prefix() { constexpr auto name = o2::framework::type_name(); const auto pos = name.rfind(std::string_view{":"}); - return name.substr(0, pos - 1); + return name.substr(0, pos + 1); } } // namespace @@ -2326,7 +2325,7 @@ consteval static std::string_view namespace_prefix() #define DECLARE_SOA_COLUMN_FULL(_Name_, _Getter_, _Type_, _Label_) \ struct _Name_ : o2::soa::Column<_Type_, _Name_> { \ static constexpr const char* mLabel = _Label_; \ - static constexpr const uint32_t hash = crc32(namespace_prefix<_Name_>(), std::string_view{#_Getter_}); \ + static constexpr const uint32_t hash = compile_time_hash(namespace_prefix<_Name_>(), std::string_view{#_Getter_}); \ static_assert(!((*(mLabel + 1) == 'I' && *(mLabel + 2) == 'n' && *(mLabel + 3) == 'd' && *(mLabel + 4) == 'e' && *(mLabel + 5) == 'x')), "Index is not a valid column name"); \ using base = o2::soa::Column<_Type_, _Name_>; \ using type = _Type_; \ @@ -2362,7 +2361,7 @@ consteval static std::string_view namespace_prefix() #define DECLARE_SOA_BITMAP_COLUMN_FULL(_Name_, _Getter_, _Size_, _Label_) \ struct _Name_ : o2::soa::Column { \ static constexpr const char* mLabel = _Label_; \ - static constexpr const uint32_t hash = crc32(namespace_prefix<_Name_>(), std::string_view{#_Getter_}); \ + static constexpr const uint32_t hash = compile_time_hash(namespace_prefix<_Name_>(), std::string_view{#_Getter_}); \ static_assert(!((*(mLabel + 1) == 'I' && *(mLabel + 2) == 'n' && *(mLabel + 3) == 'd' && *(mLabel + 4) == 'e' && *(mLabel + 5) == 'x')), "Index is not a valid column name"); \ using base = o2::soa::Column; \ using type = MAKEINT(_Size_); \ @@ -2392,38 +2391,38 @@ consteval static std::string_view namespace_prefix() /// An 'expression' column. i.e. a column that can be calculated from other /// columns with gandiva based on static C++ expression. -#define DECLARE_SOA_EXPRESSION_COLUMN_FULL(_Name_, _Getter_, _Type_, _Label_, _Expression_) \ - struct _Name_ : o2::soa::Column<_Type_, _Name_> { \ - static constexpr const char* mLabel = _Label_; \ - static constexpr const uint32_t hash = crc32(namespace_prefix<_Name_>(), std::string_view{#_Getter_}); \ - using base = o2::soa::Column<_Type_, _Name_>; \ - using type = _Type_; \ - using column_t = _Name_; \ - using spawnable_t = std::true_type; \ - _Name_(arrow::ChunkedArray const* column) \ - : o2::soa::Column<_Type_, _Name_>(o2::soa::ColumnIterator(column)) \ - { \ - } \ - \ - _Name_() = default; \ - _Name_(_Name_ const& other) = default; \ - _Name_& operator=(_Name_ const& other) = default; \ - \ - decltype(auto) _Getter_() const \ - { \ - return *mColumnIterator; \ - } \ - \ - decltype(auto) get() const \ - { \ - return _Getter_(); \ - } \ - \ - static o2::framework::expressions::Projector Projector() \ - { \ - return _Expression_; \ - } \ - }; \ +#define DECLARE_SOA_EXPRESSION_COLUMN_FULL(_Name_, _Getter_, _Type_, _Label_, _Expression_) \ + struct _Name_ : o2::soa::Column<_Type_, _Name_> { \ + static constexpr const char* mLabel = _Label_; \ + static constexpr const uint32_t hash = compile_time_hash(namespace_prefix<_Name_>(), std::string_view{#_Getter_}); \ + using base = o2::soa::Column<_Type_, _Name_>; \ + using type = _Type_; \ + using column_t = _Name_; \ + using spawnable_t = std::true_type; \ + _Name_(arrow::ChunkedArray const* column) \ + : o2::soa::Column<_Type_, _Name_>(o2::soa::ColumnIterator(column)) \ + { \ + } \ + \ + _Name_() = default; \ + _Name_(_Name_ const& other) = default; \ + _Name_& operator=(_Name_ const& other) = default; \ + \ + decltype(auto) _Getter_() const \ + { \ + return *mColumnIterator; \ + } \ + \ + decltype(auto) get() const \ + { \ + return _Getter_(); \ + } \ + \ + static o2::framework::expressions::Projector Projector() \ + { \ + return _Expression_; \ + } \ + }; \ [[maybe_unused]] static constexpr o2::framework::expressions::BindingNode _Getter_ { _Label_, _Name_::hash, o2::framework::expressions::selectArrowType<_Type_>() } #define DECLARE_SOA_EXPRESSION_COLUMN(_Name_, _Getter_, _Type_, _Expression_) \ @@ -2431,34 +2430,34 @@ consteval static std::string_view namespace_prefix() /// A configurable 'expression' column. i.e. a column that can be calculated from other /// columns with gandiva based on dynamically supplied C++ expression or a string definition. -#define DECLARE_SOA_CONFIGURABLE_EXPRESSION_COLUMN(_Name_, _Getter_, _Type_, _Label_) \ - struct _Name_ : o2::soa::Column<_Type_, _Name_> { \ - static constexpr const char* mLabel = _Label_; \ - static constexpr const uint32_t hash = crc32(namespace_prefix<_Name_>(), std::string_view{#_Getter_}); \ - static constexpr const int32_t mHash = _Label_ ""_h; \ - using base = o2::soa::Column<_Type_, _Name_>; \ - using type = _Type_; \ - using column_t = _Name_; \ - using spawnable_t = std::true_type; \ - _Name_(arrow::ChunkedArray const* column) \ - : o2::soa::Column<_Type_, _Name_>(o2::soa::ColumnIterator(column)) \ - { \ - } \ - \ - _Name_() = default; \ - _Name_(_Name_ const& other) = default; \ - _Name_& operator=(_Name_ const& other) = default; \ - \ - decltype(auto) _Getter_() const \ - { \ - return *mColumnIterator; \ - } \ - \ - decltype(auto) get() const \ - { \ - return _Getter_(); \ - } \ - }; \ +#define DECLARE_SOA_CONFIGURABLE_EXPRESSION_COLUMN(_Name_, _Getter_, _Type_, _Label_) \ + struct _Name_ : o2::soa::Column<_Type_, _Name_> { \ + static constexpr const char* mLabel = _Label_; \ + static constexpr const uint32_t hash = compile_time_hash(namespace_prefix<_Name_>(), std::string_view{#_Getter_}); \ + static constexpr const int32_t mHash = _Label_ ""_h; \ + using base = o2::soa::Column<_Type_, _Name_>; \ + using type = _Type_; \ + using column_t = _Name_; \ + using spawnable_t = std::true_type; \ + _Name_(arrow::ChunkedArray const* column) \ + : o2::soa::Column<_Type_, _Name_>(o2::soa::ColumnIterator(column)) \ + { \ + } \ + \ + _Name_() = default; \ + _Name_(_Name_ const& other) = default; \ + _Name_& operator=(_Name_ const& other) = default; \ + \ + decltype(auto) _Getter_() const \ + { \ + return *mColumnIterator; \ + } \ + \ + decltype(auto) get() const \ + { \ + return _Getter_(); \ + } \ + }; \ [[maybe_unused]] static constexpr o2::framework::expressions::BindingNode _Getter_ { _Label_, _Name_::hash, o2::framework::expressions::selectArrowType<_Type_>() } /// An index column is a column of indices to elements / of another table named @@ -2731,81 +2730,81 @@ consteval auto getIndexTargets() #define DECLARE_SOA_ARRAY_INDEX_COLUMN_CUSTOM(_Name_, _Getter_, _Label_) DECLARE_SOA_ARRAY_INDEX_COLUMN_FULL_CUSTOM(_Name_, _Getter_, int32_t, _Name_##s, _Label_, "") /// NORMAL -#define DECLARE_SOA_INDEX_COLUMN_FULL_CUSTOM(_Name_, _Getter_, _Type_, _Table_, _Label_, _Suffix_) \ - struct _Name_##Id : o2::soa::Column<_Type_, _Name_##Id> { \ - static_assert(std::is_integral_v<_Type_>, "Index type must be integral"); \ - static_assert((*_Suffix_ == '\0') || (*_Suffix_ == '_'), "Suffix has to begin with _"); \ - static constexpr const char* mLabel = "fIndex" _Label_ _Suffix_; \ - static constexpr const uint32_t hash = crc32(namespace_prefix<_Name_##Id>(), std::string_view{#_Getter_ "Id"}); \ - using base = o2::soa::Column<_Type_, _Name_##Id>; \ - using type = _Type_; \ - using column_t = _Name_##Id; \ - using binding_t = _Table_; \ - static constexpr auto index_targets = getIndexTargets<_Table_>(); \ - _Name_##Id(arrow::ChunkedArray const* column) \ - : o2::soa::Column<_Type_, _Name_##Id>(o2::soa::ColumnIterator(column)) \ - { \ - } \ - \ - _Name_##Id() = default; \ - _Name_##Id(_Name_##Id const& other) = default; \ - _Name_##Id& operator=(_Name_##Id const& other) = default; \ - type inline getId() const \ - { \ - return _Getter_##Id(); \ - } \ - \ - type _Getter_##Id() const \ - { \ - return *mColumnIterator; \ - } \ - \ - bool has_##_Getter_() const \ - { \ - return *mColumnIterator >= 0; \ - } \ - \ - template \ - auto _Getter_##_as() const \ - { \ - if (O2_BUILTIN_UNLIKELY(mBinding.ptr == nullptr)) { \ - o2::soa::notBoundTable(#_Table_); \ - } \ - if (O2_BUILTIN_UNLIKELY(!has_##_Getter_())) { \ - o2::soa::accessingInvalidIndexFor(#_Getter_); \ - } \ - auto t = mBinding.get(); \ - if (O2_BUILTIN_UNLIKELY(t == nullptr)) { \ - o2::soa::dereferenceWithWrongType(#_Getter_, #_Table_); \ - } \ - return t->rawIteratorAt(*mColumnIterator); \ - } \ - \ - auto _Getter_() const \ - { \ - return _Getter_##_as(); \ - } \ - \ - template \ - bool setCurrent(T* current) \ - { \ - if constexpr (o2::soa::is_binding_compatible_v()) { \ - assert(current != nullptr); \ - this->mBinding.bind(current); \ - return true; \ - } \ - return false; \ - } \ - \ - bool setCurrentRaw(o2::soa::Binding current) \ - { \ - this->mBinding = current; \ - return true; \ - } \ - binding_t const* getCurrent() const { return mBinding.get(); } \ - o2::soa::Binding getCurrentRaw() const { return mBinding; } \ - o2::soa::Binding mBinding; \ - }; \ +#define DECLARE_SOA_INDEX_COLUMN_FULL_CUSTOM(_Name_, _Getter_, _Type_, _Table_, _Label_, _Suffix_) \ + struct _Name_##Id : o2::soa::Column<_Type_, _Name_##Id> { \ + static_assert(std::is_integral_v<_Type_>, "Index type must be integral"); \ + static_assert((*_Suffix_ == '\0') || (*_Suffix_ == '_'), "Suffix has to begin with _"); \ + static constexpr const char* mLabel = "fIndex" _Label_ _Suffix_; \ + static constexpr const uint32_t hash = compile_time_hash(namespace_prefix<_Name_##Id>(), std::string_view{#_Getter_ "Id"}); \ + using base = o2::soa::Column<_Type_, _Name_##Id>; \ + using type = _Type_; \ + using column_t = _Name_##Id; \ + using binding_t = _Table_; \ + static constexpr auto index_targets = getIndexTargets<_Table_>(); \ + _Name_##Id(arrow::ChunkedArray const* column) \ + : o2::soa::Column<_Type_, _Name_##Id>(o2::soa::ColumnIterator(column)) \ + { \ + } \ + \ + _Name_##Id() = default; \ + _Name_##Id(_Name_##Id const& other) = default; \ + _Name_##Id& operator=(_Name_##Id const& other) = default; \ + type inline getId() const \ + { \ + return _Getter_##Id(); \ + } \ + \ + type _Getter_##Id() const \ + { \ + return *mColumnIterator; \ + } \ + \ + bool has_##_Getter_() const \ + { \ + return *mColumnIterator >= 0; \ + } \ + \ + template \ + auto _Getter_##_as() const \ + { \ + if (O2_BUILTIN_UNLIKELY(mBinding.ptr == nullptr)) { \ + o2::soa::notBoundTable(#_Table_); \ + } \ + if (O2_BUILTIN_UNLIKELY(!has_##_Getter_())) { \ + o2::soa::accessingInvalidIndexFor(#_Getter_); \ + } \ + auto t = mBinding.get(); \ + if (O2_BUILTIN_UNLIKELY(t == nullptr)) { \ + o2::soa::dereferenceWithWrongType(#_Getter_, #_Table_); \ + } \ + return t->rawIteratorAt(*mColumnIterator); \ + } \ + \ + auto _Getter_() const \ + { \ + return _Getter_##_as(); \ + } \ + \ + template \ + bool setCurrent(T* current) \ + { \ + if constexpr (o2::soa::is_binding_compatible_v()) { \ + assert(current != nullptr); \ + this->mBinding.bind(current); \ + return true; \ + } \ + return false; \ + } \ + \ + bool setCurrentRaw(o2::soa::Binding current) \ + { \ + this->mBinding = current; \ + return true; \ + } \ + binding_t const* getCurrent() const { return mBinding.get(); } \ + o2::soa::Binding getCurrentRaw() const { return mBinding; } \ + o2::soa::Binding mBinding; \ + }; \ [[maybe_unused]] static constexpr o2::framework::expressions::BindingNode _Getter_##Id { "fIndex" #_Table_ _Suffix_, _Name_##Id::hash, o2::framework::expressions::selectArrowType<_Type_>() } #define DECLARE_SOA_INDEX_COLUMN_FULL(_Name_, _Getter_, _Type_, _Table_, _Suffix_) DECLARE_SOA_INDEX_COLUMN_FULL_CUSTOM(_Name_, _Getter_, _Type_, _Table_, #_Table_, _Suffix_) @@ -2813,60 +2812,60 @@ consteval auto getIndexTargets() #define DECLARE_SOA_INDEX_COLUMN_CUSTOM(_Name_, _Getter_, _Label_) DECLARE_SOA_INDEX_COLUMN_FULL_CUSTOM(_Name_, _Getter_, int32_t, _Name_##s, _Label_, "") /// SELF -#define DECLARE_SOA_SELF_INDEX_COLUMN_COMPLETE(_Name_, _Getter_, _Type_, _Label_, _IndexTarget_) \ - struct _Name_##Id : o2::soa::Column<_Type_, _Name_##Id> { \ - static_assert(std::is_integral_v<_Type_>, "Index type must be integral"); \ - static constexpr const char* mLabel = "fIndex" _Label_; \ - static constexpr const uint32_t hash = crc32(namespace_prefix<_Name_##Id>(), std::string_view{#_Getter_ "Id"}); \ - using base = o2::soa::Column<_Type_, _Name_##Id>; \ - using type = _Type_; \ - using column_t = _Name_##Id; \ - using self_index_t = std::true_type; \ - using compatible_signature = std::conditional, _IndexTarget_, void>; \ - _Name_##Id(arrow::ChunkedArray const* column) \ - : o2::soa::Column<_Type_, _Name_##Id>(o2::soa::ColumnIterator(column)) \ - { \ - } \ - \ - _Name_##Id() = default; \ - _Name_##Id(_Name_##Id const& other) = default; \ - _Name_##Id& operator=(_Name_##Id const& other) = default; \ - type inline getId() const \ - { \ - return _Getter_##Id(); \ - } \ - \ - type _Getter_##Id() const \ - { \ - return *mColumnIterator; \ - } \ - \ - bool has_##_Getter_() const \ - { \ - return *mColumnIterator >= 0; \ - } \ - \ - template \ - auto _Getter_##_as() const \ - { \ - if (O2_BUILTIN_UNLIKELY(!has_##_Getter_())) { \ - o2::soa::accessingInvalidIndexFor(#_Getter_); \ - } \ - auto t = mBinding.get(); \ - if (O2_BUILTIN_UNLIKELY(t == nullptr)) { \ - o2::soa::dereferenceWithWrongType(#_Getter_, "self"); \ - } \ - return t->rawIteratorAt(*mColumnIterator); \ - } \ - \ - bool setCurrentRaw(o2::soa::Binding current) \ - { \ - this->mBinding = current; \ - return true; \ - } \ - o2::soa::Binding getCurrentRaw() const { return mBinding; } \ - o2::soa::Binding mBinding; \ - }; \ +#define DECLARE_SOA_SELF_INDEX_COLUMN_COMPLETE(_Name_, _Getter_, _Type_, _Label_, _IndexTarget_) \ + struct _Name_##Id : o2::soa::Column<_Type_, _Name_##Id> { \ + static_assert(std::is_integral_v<_Type_>, "Index type must be integral"); \ + static constexpr const char* mLabel = "fIndex" _Label_; \ + static constexpr const uint32_t hash = compile_time_hash(namespace_prefix<_Name_##Id>(), std::string_view{#_Getter_ "Id"}); \ + using base = o2::soa::Column<_Type_, _Name_##Id>; \ + using type = _Type_; \ + using column_t = _Name_##Id; \ + using self_index_t = std::true_type; \ + using compatible_signature = std::conditional, _IndexTarget_, void>; \ + _Name_##Id(arrow::ChunkedArray const* column) \ + : o2::soa::Column<_Type_, _Name_##Id>(o2::soa::ColumnIterator(column)) \ + { \ + } \ + \ + _Name_##Id() = default; \ + _Name_##Id(_Name_##Id const& other) = default; \ + _Name_##Id& operator=(_Name_##Id const& other) = default; \ + type inline getId() const \ + { \ + return _Getter_##Id(); \ + } \ + \ + type _Getter_##Id() const \ + { \ + return *mColumnIterator; \ + } \ + \ + bool has_##_Getter_() const \ + { \ + return *mColumnIterator >= 0; \ + } \ + \ + template \ + auto _Getter_##_as() const \ + { \ + if (O2_BUILTIN_UNLIKELY(!has_##_Getter_())) { \ + o2::soa::accessingInvalidIndexFor(#_Getter_); \ + } \ + auto t = mBinding.get(); \ + if (O2_BUILTIN_UNLIKELY(t == nullptr)) { \ + o2::soa::dereferenceWithWrongType(#_Getter_, "self"); \ + } \ + return t->rawIteratorAt(*mColumnIterator); \ + } \ + \ + bool setCurrentRaw(o2::soa::Binding current) \ + { \ + this->mBinding = current; \ + return true; \ + } \ + o2::soa::Binding getCurrentRaw() const { return mBinding; } \ + o2::soa::Binding mBinding; \ + }; \ [[maybe_unused]] static constexpr o2::framework::expressions::BindingNode _Getter_##Id { "fIndex" _Label_, _Name_##Id::hash, o2::framework::expressions::selectArrowType<_Type_>() } #define DECLARE_SOA_SELF_INDEX_COLUMN_FULL(_Name_, _Getter_, _Type_, _Label_) DECLARE_SOA_SELF_INDEX_COLUMN_COMPLETE(_Name_, _Getter_, _Type_, _Label_, void) diff --git a/Framework/Core/include/Framework/AnalysisTask.h b/Framework/Core/include/Framework/AnalysisTask.h index 892948582b3cc..b3378543e6ebb 100644 --- a/Framework/Core/include/Framework/AnalysisTask.h +++ b/Framework/Core/include/Framework/AnalysisTask.h @@ -574,6 +574,11 @@ DataProcessorSpec adaptAnalysisTask(ConfigContext const& ctx, Args&&... args) callbacks.set(eoscb); + /// call the task's init() function first as it may manipulate the task's elements + if constexpr (requires { task->init(ic); }) { + task->init(ic); + } + /// update configurables in filters and partitions homogeneous_apply_refs( [&ic](auto& element) -> bool { return analysis_task_parsers::updatePlaceholders(ic, element); }, @@ -584,10 +589,6 @@ DataProcessorSpec adaptAnalysisTask(ConfigContext const& ctx, Args&&... args) }, *task.get()); - if constexpr (requires { task->init(ic); }) { - task->init(ic); - } - /// parse process functions to enable requested grouping caches - note that at this state process configurables have their final values if constexpr (requires { &T::process; }) { AnalysisDataProcessorBuilder::cacheFromArgs(&T::process, true, bindingsKeys, bindingsKeysUnsorted); diff --git a/Framework/Core/include/Framework/ExpressionHelpers.h b/Framework/Core/include/Framework/ExpressionHelpers.h index f881abf7b0e6c..cd2ccd743c5d6 100644 --- a/Framework/Core/include/Framework/ExpressionHelpers.h +++ b/Framework/Core/include/Framework/ExpressionHelpers.h @@ -25,17 +25,26 @@ struct DatumSpec { size_t hash = 0; atype::type type = atype::NA; - explicit DatumSpec(size_t index, atype::type type_) : datum{index}, type{type_} {} - explicit DatumSpec(LiteralNode::var_t literal, atype::type type_) : datum{literal}, type{type_} {} - explicit DatumSpec(std::string binding, size_t hash_, atype::type type_) : datum{binding}, hash{hash_}, type{type_} {} + explicit constexpr DatumSpec(size_t index, atype::type type_) : datum{index}, type{type_} {} + explicit constexpr DatumSpec(LiteralNode::var_t literal, atype::type type_) : datum{literal}, type{type_} {} + explicit constexpr DatumSpec(std::string binding, size_t hash_, atype::type type_) : datum{binding}, hash{hash_}, type{type_} {} DatumSpec() = default; DatumSpec(DatumSpec const&) = default; DatumSpec(DatumSpec&&) = default; DatumSpec& operator=(DatumSpec const&) = default; DatumSpec& operator=(DatumSpec&&) = default; -}; -bool operator==(DatumSpec const& lhs, DatumSpec const& rhs); + bool operator==(DatumSpec const& rhs) const + { + bool eqValue = this->datum == rhs.datum; + bool eqHash = true; + if (this->datum.index() == 3 && eqValue) { + eqHash = this->hash == rhs.hash; + } + bool eqType = this->type == rhs.type; + return eqValue && eqHash && eqType; + } +}; std::ostream& operator<<(std::ostream& os, DatumSpec const& spec); diff --git a/Framework/Core/include/Framework/Expressions.h b/Framework/Core/include/Framework/Expressions.h index 4163a73f83983..ed8d4ef24f402 100644 --- a/Framework/Core/include/Framework/Expressions.h +++ b/Framework/Core/include/Framework/Expressions.h @@ -110,13 +110,16 @@ std::string upcastTo(atype::type f); /// An expression tree node corresponding to a literal value struct LiteralNode { + LiteralNode() + : value{-1}, + type{atype::INT32} + { + } template LiteralNode(T v) : value{v}, type{selectArrowType()} { } - LiteralNode(LiteralNode const& other) = default; - using var_t = LiteralValue::stored_type; var_t value; atype::type type = atype::NA; @@ -124,9 +127,16 @@ struct LiteralNode { /// An expression tree node corresponding to a column binding struct BindingNode { + constexpr BindingNode() + : name{nullptr}, + hash{0}, + type{atype::FLOAT} + { + } BindingNode(BindingNode const&) = default; BindingNode(BindingNode&&) = delete; constexpr BindingNode(const char* name_, uint32_t hash_, atype::type type_) : name{name_}, hash{hash_}, type{type_} {} + constexpr BindingNode(uint32_t hash_, atype::type type_) : name{nullptr}, hash{hash_}, type{type_} {} const char* name; uint32_t hash; atype::type type; @@ -134,8 +144,8 @@ struct BindingNode { /// An expression tree node corresponding to binary or unary operation struct OpNode { + OpNode() : op{BasicOp::Abs} {} OpNode(BasicOp op_) : op{op_} {} - OpNode(OpNode const& other) = default; BasicOp op; }; @@ -155,8 +165,6 @@ struct PlaceholderNode : LiteralNode { retrieve = [](InitContext& context, char const* name) { return LiteralNode::var_t{static_cast(context.options().get(name))}; }; } - PlaceholderNode(PlaceholderNode const& other) = default; - void reset(InitContext& context) { value = retrieve(context, name.data()); @@ -180,8 +188,6 @@ struct ParameterNode : LiteralNode { { } - ParameterNode(ParameterNode const&) = default; - template void reset(T value_, int index_ = -1) { @@ -221,7 +227,7 @@ struct Node { { } - Node(Node&& n) : self{std::forward(n.self)}, left{std::forward>(n.left)}, right{std::forward>(n.right)}, condition{std::forward>(n.condition)} + Node(Node&& n) : self{std::forward(n.self)}, left{std::forward>(n.left)}, right{std::forward>(n.right)}, condition{std::forward>(n.condition)}, binding{std::forward(n.binding)} { } @@ -229,6 +235,11 @@ struct Node { { } + Node(BindingNode const& n, std::string binding_) : self{n}, left{nullptr}, right{nullptr}, condition{nullptr}, binding{binding_} + { + get(self).name = binding.c_str(); + } + Node(ParameterNode&& p) : self{std::forward(p)}, left{nullptr}, right{nullptr}, condition{nullptr} { } @@ -239,12 +250,24 @@ struct Node { right{std::make_unique(std::forward(else_))}, condition{std::make_unique(std::forward(condition_))} {} + Node(ConditionalNode op, Node&& then_, std::unique_ptr&& else_, Node&& condition_) + : self{op}, + left{std::make_unique(std::forward(then_))}, + right{std::forward>(else_)}, + condition{std::make_unique(std::forward(condition_))} {} + Node(OpNode op, Node&& l, Node&& r) : self{op}, left{std::make_unique(std::forward(l))}, right{std::make_unique(std::forward(r))}, condition{nullptr} {} + Node(OpNode op, std::unique_ptr&& l, Node&& r) + : self{op}, + left{std::forward>(l)}, + right{std::make_unique(std::forward(r))}, + condition{nullptr} {} + Node(OpNode op, Node&& l) : self{op}, left{std::make_unique(std::forward(l))}, @@ -264,6 +287,10 @@ struct Node { if (other.condition != nullptr) { condition = std::make_unique(*other.condition); } + binding = other.binding; + if (!binding.empty()) { + get(self).name = binding.c_str(); + } } /// variant with possible nodes @@ -274,6 +301,9 @@ struct Node { std::unique_ptr left = nullptr; std::unique_ptr right = nullptr; std::unique_ptr condition = nullptr; + + /// buffer for dynamic binding + std::string binding; }; /// helper struct used to parse trees @@ -315,54 +345,58 @@ void walk(Node* head, L&& pred) } } +/// helper concepts +template +concept arithmetic = std::is_arithmetic_v; + /// overloaded operators to build the tree from an expression -#define BINARY_OP_NODES(_operator_, _operation_) \ - inline Node operator _operator_(Node&& left, Node&& right) \ - { \ - return Node{OpNode{BasicOp::_operation_}, std::forward(left), std::forward(right)}; \ - } \ - template \ - inline Node operator _operator_(Node&& left, T right) requires(std::is_arithmetic_v>) \ - { \ - return Node{OpNode{BasicOp::_operation_}, std::forward(left), LiteralNode{right}}; \ - } \ - template \ - inline Node operator _operator_(T left, Node&& right) requires(std::is_arithmetic_v>) \ - { \ - return Node{OpNode{BasicOp::_operation_}, LiteralNode{left}, std::forward(right)}; \ - } \ - template \ - inline Node operator _operator_(Node&& left, Configurable const& right) \ - { \ - return Node{OpNode{BasicOp::_operation_}, std::forward(left), PlaceholderNode{right}}; \ - } \ - template \ - inline Node operator _operator_(Configurable const& left, Node&& right) \ - { \ - return Node{OpNode{BasicOp::_operation_}, PlaceholderNode{left}, std::forward(right)}; \ - } \ - inline Node operator _operator_(BindingNode const& left, BindingNode const& right) \ - { \ - return Node{OpNode{BasicOp::_operation_}, left, right}; \ - } \ - inline Node operator _operator_(BindingNode const& left, Node&& right) \ - { \ - return Node{OpNode{BasicOp::_operation_}, left, std::forward(right)}; \ - } \ - inline Node operator _operator_(Node&& left, BindingNode const& right) \ - { \ - return Node{OpNode{BasicOp::_operation_}, std::forward(left), right}; \ - } \ - template \ - inline Node operator _operator_(Configurable const& left, BindingNode const& right) \ - { \ - return Node{OpNode{BasicOp::_operation_}, PlaceholderNode{left}, right}; \ - } \ - template \ - inline Node operator _operator_(BindingNode const& left, Configurable const& right) \ - { \ - return Node{OpNode{BasicOp::_operation_}, left, PlaceholderNode{right}}; \ +#define BINARY_OP_NODES(_operator_, _operation_) \ + inline Node operator _operator_(Node&& left, Node&& right) \ + { \ + return Node{OpNode{BasicOp::_operation_}, std::forward(left), std::forward(right)}; \ + } \ + template \ + inline Node operator _operator_(Node&& left, T right) \ + { \ + return Node{OpNode{BasicOp::_operation_}, std::forward(left), LiteralNode{right}}; \ + } \ + template \ + inline Node operator _operator_(T left, Node&& right) \ + { \ + return Node{OpNode{BasicOp::_operation_}, LiteralNode{left}, std::forward(right)}; \ + } \ + template \ + inline Node operator _operator_(Node&& left, Configurable const& right) \ + { \ + return Node{OpNode{BasicOp::_operation_}, std::forward(left), PlaceholderNode{right}}; \ + } \ + template \ + inline Node operator _operator_(Configurable const& left, Node&& right) \ + { \ + return Node{OpNode{BasicOp::_operation_}, PlaceholderNode{left}, std::forward(right)}; \ + } \ + inline Node operator _operator_(BindingNode const& left, BindingNode const& right) \ + { \ + return Node{OpNode{BasicOp::_operation_}, left, right}; \ + } \ + inline Node operator _operator_(BindingNode const& left, Node&& right) \ + { \ + return Node{OpNode{BasicOp::_operation_}, left, std::forward(right)}; \ + } \ + inline Node operator _operator_(Node&& left, BindingNode const& right) \ + { \ + return Node{OpNode{BasicOp::_operation_}, std::forward(left), right}; \ + } \ + template \ + inline Node operator _operator_(Configurable const& left, BindingNode const& right) \ + { \ + return Node{OpNode{BasicOp::_operation_}, PlaceholderNode{left}, right}; \ + } \ + template \ + inline Node operator _operator_(BindingNode const& left, Configurable const& right) \ + { \ + return Node{OpNode{BasicOp::_operation_}, left, PlaceholderNode{right}}; \ } BINARY_OP_NODES(&, BitwiseAnd); @@ -382,61 +416,61 @@ BINARY_OP_NODES(&&, LogicalAnd); BINARY_OP_NODES(||, LogicalOr); /// functions -template -inline Node npow(Node&& left, T right) requires(std::is_arithmetic_v) +template +inline Node npow(Node&& left, T right) { return Node{OpNode{BasicOp::Power}, std::forward(left), LiteralNode{right}}; } -#define BINARY_FUNC_NODES(_func_, _node_) \ - template \ - inline Node _node_(L left, R right) requires(std::is_arithmetic_v && std::is_arithmetic_v) \ - { \ - return Node{OpNode{BasicOp::_func_}, LiteralNode{left}, LiteralNode{right}}; \ - } \ - \ - inline Node _node_(Node&& left, Node&& right) \ - { \ - return Node{OpNode{BasicOp::_func_}, std::forward(left), std::forward(right)}; \ - } \ - \ - inline Node _node_(Node&& left, BindingNode const& right) \ - { \ - return Node{OpNode{BasicOp::_func_}, std::forward(left), right}; \ - } \ - \ - inline Node _node_(BindingNode const& left, BindingNode const& right) \ - { \ - return Node{OpNode{BasicOp::_func_}, left, right}; \ - } \ - \ - inline Node _node_(BindingNode const& left, Node&& right) \ - { \ - return Node{OpNode{BasicOp::_func_}, left, std::forward(right)}; \ - } \ - \ - template \ - inline Node _node_(Node&& left, Configurable const& right) \ - { \ - return Node{OpNode{BasicOp::_func_}, std::forward(left), PlaceholderNode{right}}; \ - } \ - \ - template \ - inline Node _node_(Configurable const& left, Node&& right) \ - { \ - return Node{OpNode{BasicOp::_func_}, PlaceholderNode{left}, std::forward(right)}; \ - } \ - \ - template \ - inline Node _node_(BindingNode const& left, Configurable const& right) \ - { \ - return Node{OpNode{BasicOp::_func_}, left, PlaceholderNode{right}}; \ - } \ - \ - template \ - inline Node _node_(Configurable const& left, BindingNode const& right) \ - { \ - return Node{OpNode{BasicOp::_func_}, PlaceholderNode{left}, right}; \ +#define BINARY_FUNC_NODES(_func_, _node_) \ + template \ + inline Node _node_(L left, R right) \ + { \ + return Node{OpNode{BasicOp::_func_}, LiteralNode{left}, LiteralNode{right}}; \ + } \ + \ + inline Node _node_(Node&& left, Node&& right) \ + { \ + return Node{OpNode{BasicOp::_func_}, std::forward(left), std::forward(right)}; \ + } \ + \ + inline Node _node_(Node&& left, BindingNode const& right) \ + { \ + return Node{OpNode{BasicOp::_func_}, std::forward(left), right}; \ + } \ + \ + inline Node _node_(BindingNode const& left, BindingNode const& right) \ + { \ + return Node{OpNode{BasicOp::_func_}, left, right}; \ + } \ + \ + inline Node _node_(BindingNode const& left, Node&& right) \ + { \ + return Node{OpNode{BasicOp::_func_}, left, std::forward(right)}; \ + } \ + \ + template \ + inline Node _node_(Node&& left, Configurable const& right) \ + { \ + return Node{OpNode{BasicOp::_func_}, std::forward(left), PlaceholderNode{right}}; \ + } \ + \ + template \ + inline Node _node_(Configurable const& left, Node&& right) \ + { \ + return Node{OpNode{BasicOp::_func_}, PlaceholderNode{left}, std::forward(right)}; \ + } \ + \ + template \ + inline Node _node_(BindingNode const& left, Configurable const& right) \ + { \ + return Node{OpNode{BasicOp::_func_}, left, PlaceholderNode{right}}; \ + } \ + \ + template \ + inline Node _node_(Configurable const& left, BindingNode const& right) \ + { \ + return Node{OpNode{BasicOp::_func_}, PlaceholderNode{left}, right}; \ } BINARY_FUNC_NODES(Atan2, natan2); @@ -469,20 +503,20 @@ inline Node ifnode(Node&& condition_, Node&& then_, Node&& else_) return Node{ConditionalNode{}, std::forward(then_), std::forward(else_), std::forward(condition_)}; } -template -inline Node ifnode(Node&& condition_, Node&& then_, L else_) requires(std::is_arithmetic_v) +template +inline Node ifnode(Node&& condition_, Node&& then_, L else_) { return Node{ConditionalNode{}, std::forward(then_), LiteralNode{else_}, std::forward(condition_)}; } -template -inline Node ifnode(Node&& condition_, L then_, Node&& else_) requires(std::is_arithmetic_v) +template +inline Node ifnode(Node&& condition_, L then_, Node&& else_) { return Node{ConditionalNode{}, LiteralNode{then_}, std::forward(else_), std::forward(condition_)}; } -template -inline Node ifnode(Node&& condition_, L1 then_, L2 else_) requires(std::is_arithmetic_v&& std::is_arithmetic_v) +template +inline Node ifnode(Node&& condition_, L1 then_, L2 else_) { return Node{ConditionalNode{}, LiteralNode{then_}, LiteralNode{else_}, std::forward(condition_)}; } @@ -576,15 +610,29 @@ struct Filter { (void)designateSubtrees(node.get()); } + Filter(std::string const& input_) : input{input_} {} + Filter& operator=(Filter&& other) noexcept { node = std::move(other.node); + input = std::move(other.input); + return *this; + } + + Filter& operator=(std::string const& input_) + { + input = input_; + if (node != nullptr) { + node = nullptr; + } return *this; } std::unique_ptr node = nullptr; + std::string input; size_t designateSubtrees(Node* node, size_t index = 0); + void parse(); }; template @@ -644,6 +692,79 @@ std::shared_ptr createProjectors(framework::pack, std: } void updateFilterInfo(ExpressionInfo& info, std::shared_ptr& table); + +/* + * The formal grammar for framework expressions. + * Operations are in the order of increasing priority. + * Identifier includes namespaces, e.g. o2::aod::track::pt. + * + * top ::= primary + * + * primary ::= tier1 ('||' tier1)* + * tier1 ::= tier2 ('&&' tier2)* + * tier2 ::= tier3 ('|' tier3)* + * tier3 ::= tier4 ('^' tier4)* + * tier4 ::= tier5 ('&' tier5)* + * tier5 ::= tier6 (('=='|'!=') tier6)* + * tier6 ::= tier7 (('<'|'>'|'<='|'>=') tier7)* + * tier7 ::= tier8 (('+'|'-') tier8)* + * tier8 ::= base (('*'|'/') base)* + * + * base ::= identifier + * | number + * | function_call + * | '(' primary ')' + * + * number ::= -?[0-9]+(\.[0-9]*)?([uf])? + * identifier ::= [a-zA-Z][a-zA-Z0-9_]* ('::' [a-zA-Z][a-zA-Z0-9_]*)* + * function_call ::= identifier '(' (primary (',' primary)*)? ')' + */ + +/// String parsing +enum Token : int { + EoL = -1, + Identifier = -2, + IntegerNumber = -3, + FloatNumber = -4, + BinaryOp = -5, + Unexpected = -100 +}; + +struct Tokenizer { + std::string source; + std::string::iterator current; + std::string IdentifierStr; + std::string BinaryOpStr; + std::string StrValue; + std::string TokenStr; + std::variant IntegerValue; + std::variant FloatValue; + char LastChar; + int currentToken = Token::Unexpected; + + Tokenizer(std::string const& input = ""); + void reset(std::string const& input); + [[maybe_unused]] int nextToken(); + void pop(); + char peek(); +}; + +struct Parser { + static Node parse(std::string const& input); + static std::unique_ptr parsePrimary(Tokenizer& tk); + static std::unique_ptr parseTier1(Tokenizer& tk); + static std::unique_ptr parseTier2(Tokenizer& tk); + static std::unique_ptr parseTier3(Tokenizer& tk); + static std::unique_ptr parseTier4(Tokenizer& tk); + static std::unique_ptr parseTier5(Tokenizer& tk); + static std::unique_ptr parseTier6(Tokenizer& tk); + static std::unique_ptr parseTier7(Tokenizer& tk); + static std::unique_ptr parseTier8(Tokenizer& tk); + static std::unique_ptr parseBase(Tokenizer& tk); + + static OpNode opFromToken(std::string const& token); +}; + } // namespace o2::framework::expressions #endif // O2_FRAMEWORK_EXPRESSIONS_H_ diff --git a/Framework/Core/include/Framework/StringHelpers.h b/Framework/Core/include/Framework/StringHelpers.h index e450764576c29..8a2d892062f70 100644 --- a/Framework/Core/include/Framework/StringHelpers.h +++ b/Framework/Core/include/Framework/StringHelpers.h @@ -85,6 +85,13 @@ consteval uint32_t crc32(Ts... Vs) return crc; } +template + requires(std::same_as && ...) +consteval uint32_t compile_time_hash(Ts... Vs) +{ + return crc32(Vs...) ^ 0xFFFFFFFF; +} + consteval uint32_t compile_time_hash(char const* str) { return crc32(str, static_cast(__builtin_strlen(str)) - 1) ^ 0xFFFFFFFF; diff --git a/Framework/Core/src/Expressions.cxx b/Framework/Core/src/Expressions.cxx index 94649f8639a0a..1d4dec734ff21 100644 --- a/Framework/Core/src/Expressions.cxx +++ b/Framework/Core/src/Expressions.cxx @@ -19,6 +19,7 @@ #include #include #include +#include "CommonConstants/MathConstants.h" using namespace o2::framework; @@ -29,9 +30,67 @@ void unknownParameterUsed(const char* name) runtime_error_f("Unknown parameter used in expression: %s", name); } +/// a map between BasicOp and tokens in string expressions +constexpr std::array mapping{ + "&&", + "||", + "+", + "-", + "/", + "*", + "&", + "|", + "^", + "<", + "<=", + ">", + ">=", + "==", + "!=", + "natan2", + "npow", + "nsqrt", + "nexp", + "nlog", + "nlog10", + "nsin", + "ncos", + "ntan", + "nasin", + "nacos", + "natan", + "nabs", + "nround", + "nbitwise_not", + "ifnode"}; + +/// math constants to recognize in string expressions +constexpr std::array mathConstants{ + "Almost0", + "Epsilon", + "Almost1", + "VeryBig", + "PI", + "TwoPI", + "PIHalf", + "PIThird", + "PIQuarter"}; + +/// values of math constants to substiture +constexpr std::array mathConstantsValues{ + o2::constants::math::Almost0, + o2::constants::math::Epsilon, + o2::constants::math::Almost1, + o2::constants::math::VeryBig, + o2::constants::math::PI, + o2::constants::math::TwoPI, + o2::constants::math::PIHalf, + o2::constants::math::PIThird, + o2::constants::math::PIQuarter}; + /// a map between BasicOp and gandiva node definitions /// note that logical 'and' and 'or' are created separately -static const std::array basicOperationsMap = { +constexpr std::array basicOperationsMap = { "and", "or", "add", @@ -93,6 +152,12 @@ size_t Filter::designateSubtrees(Node* node, size_t index) return index; } +void Filter::parse() +{ + node = std::make_unique(Parser::parse(input)); + (void)designateSubtrees(node.get()); +} + template constexpr inline auto makeDatum(T const&) { @@ -175,11 +240,6 @@ std::string upcastTo(atype::type f) } } -bool operator==(DatumSpec const& lhs, DatumSpec const& rhs) -{ - return (lhs.datum == rhs.datum) && (lhs.type == rhs.type); -} - std::ostream& operator<<(std::ostream& os, DatumSpec const& spec) { std::visit( @@ -198,6 +258,9 @@ std::ostream& operator<<(std::ostream& os, DatumSpec const& spec) void updatePlaceholders(Filter& filter, InitContext& context) { + if (filter.node == nullptr && !filter.input.empty()) { + filter.parse(); + } expressions::walk(filter.node.get(), [&](Node* node) { if (node->self.index() == 3) { std::get_if<3>(&node->self)->reset(context); @@ -332,7 +395,7 @@ Operations createOperations(Filter const& expression) std::vector resultTypes; resultTypes.resize(OperationSpecs.size()); - auto inferResultType = [&resultTypes](DatumSpec& left, DatumSpec& right) { + auto inferResultType = [&resultTypes](BasicOp op, DatumSpec& left, DatumSpec& right) { // if the left datum is monostate (error) if (left.datum.index() == 0) { throw runtime_error("Malformed operation spec: empty left datum"); @@ -365,11 +428,15 @@ Operations createOperations(Filter const& expression) return (t == atype::UINT8) || (t == atype::INT8) || (t == atype::UINT16) || (t == atype::INT16) || (t == atype::UINT32) || (t == atype::INT32) || (t == atype::UINT64) || (t == atype::INT64); }; + auto isBitwiseOp = [](auto o) { + return ((o == BasicOp::BitwiseAnd) || (o == BasicOp::BitwiseNot) || (o == BasicOp::BitwiseOr) || (o == BasicOp::BitwiseXor)); + }; + if (isIntType(t1)) { - if (t2 == atype::FLOAT) { + if (t2 == atype::FLOAT && !isBitwiseOp(op)) { return atype::FLOAT; } - if (t2 == atype::DOUBLE) { + if (t2 == atype::DOUBLE && !isBitwiseOp(op)) { return atype::DOUBLE; } if (isIntType(t2)) { @@ -380,7 +447,7 @@ Operations createOperations(Filter const& expression) } } if (t1 == atype::FLOAT) { - if (isIntType(t2)) { + if (isIntType(t2) && !isBitwiseOp(op)) { return atype::FLOAT; } if (t2 == atype::DOUBLE) { @@ -390,11 +457,19 @@ Operations createOperations(Filter const& expression) if (t1 == atype::DOUBLE) { return atype::DOUBLE; } + + if (isIntType(t1) && isBitwiseOp(op)) { + return t1; + } + if (isIntType(t2) && isBitwiseOp(op)) { + return t2; + } + throw runtime_error_f("Invalid combination of argument types %s and %s", stringType(t1), stringType(t2)); }; for (auto it = OperationSpecs.rbegin(); it != OperationSpecs.rend(); ++it) { - auto type = inferResultType(it->left, it->right); + auto type = inferResultType(it->op, it->left, it->right); if (it->type == atype::NA) { it->type = type; } @@ -609,30 +684,34 @@ gandiva::NodePtr createExpressionTree(Operations const& opSpecs, throw runtime_error("Malformed DatumSpec"); }; + auto insertUpcastNode = [](gandiva::NodePtr node, atype::type t0, atype::type t) { + if (t != t0) { + auto upcast = gandiva::TreeExprBuilder::MakeFunction(upcastTo(t0), {node}, concreteArrowType(t0)); + node = upcast; + } + return node; + }; + + auto insertEqualizeUpcastNode = [](gandiva::NodePtr& node1, gandiva::NodePtr& node2, atype::type t1, atype::type t2) { + if (t2 > t1) { + auto upcast = gandiva::TreeExprBuilder::MakeFunction(upcastTo(t2), {node1}, concreteArrowType(t2)); + node1 = upcast; + } else if (t1 > t2) { + auto upcast = gandiva::TreeExprBuilder::MakeFunction(upcastTo(t1), {node2}, concreteArrowType(t1)); + node2 = upcast; + } + }; + + auto isBitwiseOp = [](auto o) { + return ((o == BasicOp::BitwiseAnd) || (o == BasicOp::BitwiseNot) || (o == BasicOp::BitwiseOr) || (o == BasicOp::BitwiseXor)); + }; + gandiva::NodePtr tree = nullptr; for (auto it = opSpecs.rbegin(); it != opSpecs.rend(); ++it) { auto leftNode = datumNode(it->left); auto rightNode = datumNode(it->right); auto condNode = datumNode(it->condition); - auto insertUpcastNode = [](gandiva::NodePtr node, atype::type t0, atype::type t) { - if (t != t0) { - auto upcast = gandiva::TreeExprBuilder::MakeFunction(upcastTo(t0), {node}, concreteArrowType(t0)); - node = upcast; - } - return node; - }; - - auto insertEqualizeUpcastNode = [](gandiva::NodePtr& node1, gandiva::NodePtr& node2, atype::type t1, atype::type t2) { - if (t2 > t1) { - auto upcast = gandiva::TreeExprBuilder::MakeFunction(upcastTo(t2), {node1}, concreteArrowType(t2)); - node1 = upcast; - } else if (t1 > t2) { - auto upcast = gandiva::TreeExprBuilder::MakeFunction(upcastTo(t1), {node2}, concreteArrowType(t1)); - node2 = upcast; - } - }; - gandiva::NodePtr temp_node; switch (it->op) { @@ -647,7 +726,7 @@ gandiva::NodePtr createExpressionTree(Operations const& opSpecs, break; default: if (it->op < BasicOp::Sqrt) { - if (it->type != atype::BOOL) { + if (it->type != atype::BOOL && !isBitwiseOp(it->op)) { leftNode = insertUpcastNode(leftNode, it->type, it->left.type); rightNode = insertUpcastNode(rightNode, it->type, it->right.type); } else if (it->op == BasicOp::Equal || it->op == BasicOp::NotEqual) { @@ -655,7 +734,9 @@ gandiva::NodePtr createExpressionTree(Operations const& opSpecs, } temp_node = gandiva::TreeExprBuilder::MakeFunction(basicOperationsMap[it->op], {leftNode, rightNode}, concreteArrowType(it->type)); } else { - leftNode = insertUpcastNode(leftNode, it->type, it->left.type); + if (!isBitwiseOp(it->op)) { + leftNode = insertUpcastNode(leftNode, it->type, it->left.type); + } temp_node = gandiva::TreeExprBuilder::MakeFunction(basicOperationsMap[it->op], {leftNode}, concreteArrowType(it->type)); } break; @@ -722,4 +803,464 @@ void updateFilterInfo(ExpressionInfo& info, std::shared_ptr& table } } +/// String parsing +Tokenizer::Tokenizer(std::string const& input) + : source{input}, + IdentifierStr{""}, + StrValue{""}, + IntegerValue{0}, + FloatValue{0.f} +{ + LastChar = ' '; + if (!source.empty()) { + source.erase(std::remove_if(source.begin(), source.end(), ::isspace), source.end()); + } + current = source.begin(); +} + +void Tokenizer::reset(std::string const& input) +{ + LastChar = ' '; + IdentifierStr = ""; + StrValue = ""; + IntegerValue = 0; + FloatValue = 0.f; + source = input; + if (!source.empty()) { + source.erase(std::remove_if(source.begin(), source.end(), ::isspace), source.end()); + } + current = source.begin(); + currentToken = Token::Unexpected; +} + +int Tokenizer::nextToken() +{ + // skip initial space + if (isspace(LastChar)) { + pop(); + } + // logical or bitwise OR + if (LastChar == '|') { + BinaryOpStr = LastChar; + if (peek() == '|') { + pop(); + BinaryOpStr += LastChar; + pop(); + TokenStr = BinaryOpStr; + currentToken = Token::BinaryOp; + return currentToken; + } else { + pop(); + TokenStr = BinaryOpStr; + currentToken = Token::BinaryOp; + return currentToken; + } + } + // logical or bitwise AND + if (LastChar == '&') { + BinaryOpStr = LastChar; + if (peek() == '&') { + pop(); + BinaryOpStr += LastChar; + pop(); + TokenStr = BinaryOpStr; + currentToken = Token::BinaryOp; + return currentToken; + } else { + pop(); + TokenStr = BinaryOpStr; + currentToken = Token::BinaryOp; + return currentToken; + } + } + // less than or less or equal than + if (LastChar == '<') { + BinaryOpStr = LastChar; + if (peek() == '=') { + pop(); + BinaryOpStr += LastChar; + pop(); + TokenStr = BinaryOpStr; + currentToken = Token::BinaryOp; + return currentToken; + } else { + pop(); + TokenStr = BinaryOpStr; + currentToken = Token::BinaryOp; + return currentToken; + } + } + // greater than or greater or equal than + if (LastChar == '>') { + BinaryOpStr = LastChar; + if (peek() == '=') { + pop(); + BinaryOpStr += LastChar; + pop(); + TokenStr = BinaryOpStr; + currentToken = Token::BinaryOp; + return currentToken; + } else { + pop(); + TokenStr = BinaryOpStr; + currentToken = Token::BinaryOp; + return currentToken; + } + } + // equal or error + if (LastChar == '=') { + BinaryOpStr = LastChar; + if (peek() == '=') { + pop(); + BinaryOpStr += LastChar; + pop(); + TokenStr = BinaryOpStr; + currentToken = Token::BinaryOp; + return currentToken; + } else { + pop(); + TokenStr = BinaryOpStr; + currentToken = Token::Unexpected; + return currentToken; + } + } + // not equal or error + if (LastChar == '!') { + BinaryOpStr = LastChar; + if (peek() == '=') { + pop(); + BinaryOpStr += LastChar; + pop(); + TokenStr = BinaryOpStr; + currentToken = Token::BinaryOp; + return currentToken; + } else { + pop(); + TokenStr = BinaryOpStr; + currentToken = Token::BinaryOp; + return currentToken; + } + } + // unambiguous single-character binary operations: addition, multiplication, subtraction, division, bitwise XOR + if (LastChar == '+' || LastChar == '*' || (LastChar == '-' && (currentToken != Token::BinaryOp && currentToken != '(' && currentToken != Token::Unexpected)) || LastChar == '/' || LastChar == '^') { + BinaryOpStr = LastChar; + pop(); + TokenStr = BinaryOpStr; + currentToken = Token::BinaryOp; + return currentToken; + } + // identifier: column, function, constant + if (isalpha(LastChar)) { + // identifier + IdentifierStr = LastChar; + pop(); + while (isalnum(LastChar) || (LastChar == '_') || (LastChar == ':')) { + IdentifierStr += LastChar; + pop(); + } + TokenStr = IdentifierStr; + currentToken = Token::Identifier; + return currentToken; + } + // number: integer, unsigned integer or float + if (isdigit(LastChar) || LastChar == '.' || (LastChar == '-' && isdigit(peek()))) { + // number + StrValue = ""; + bool isFloat = false; + bool isUnsigned = false; + do { + StrValue += LastChar; + pop(); + } while (isdigit(LastChar) || LastChar == '.'); + if (LastChar == 'f') { + isFloat = true; + pop(); + } + if (LastChar == 'u') { + isUnsigned = true; + pop(); + } + if (std::find(StrValue.begin(), StrValue.end(), '.') == StrValue.end() && !isFloat) { + if (!isUnsigned) { + IntegerValue = atoi(StrValue.c_str()); + } else { + IntegerValue = static_cast(atoi(StrValue.c_str())); + } + TokenStr = StrValue; + currentToken = Token::IntegerNumber; + return currentToken; + } + if (isFloat) { + FloatValue = strtof(StrValue.c_str(), nullptr); + } else { + FloatValue = strtod(StrValue.c_str(), nullptr); + } + TokenStr = StrValue; + currentToken = Token::FloatNumber; + return currentToken; + } + // end-of-line + if (LastChar == '\0') { + TokenStr = LastChar; + currentToken = Token::EoL; + return currentToken; + } + // generic character + currentToken = LastChar; + TokenStr = LastChar; + pop(); + return currentToken; +} + +void Tokenizer::pop() +{ + if (current != source.end()) { + LastChar = *current; + ++current; + } else { + LastChar = '\0'; + } +} + +char Tokenizer::peek() +{ + if (current != source.end()) { + return *current; + } else { + return '\0'; + } +} + +Node Parser::parse(std::string const& input) +{ + auto tk = Tokenizer(input); + tk.nextToken(); + auto node = parsePrimary(tk); + if (tk.currentToken != Token::EoL) { + throw runtime_error_f("Unexpected token after expression: %s", tk.TokenStr.c_str()); + } + return *node.get(); +} + +std::unique_ptr Parser::parsePrimary(Tokenizer& tk) +{ + auto root = parseTier1(tk); + while (tk.TokenStr == "||") { + auto opnode = std::make_unique(OpNode{BasicOp::LogicalOr}, std::move(root), LiteralNode{-1}); + root.swap(opnode); + tk.nextToken(); + root->right = parseTier1(tk); + } + return root; +} + +std::unique_ptr Parser::parseTier1(Tokenizer& tk) +{ + auto root = parseTier2(tk); + while (tk.TokenStr == "&&") { + auto opnode = std::make_unique(OpNode{BasicOp::LogicalAnd}, std::move(root), LiteralNode{-1}); + root.swap(opnode); + tk.nextToken(); + root->right = parseTier2(tk); + } + return root; +} + +std::unique_ptr Parser::parseTier2(Tokenizer& tk) +{ + auto root = parseTier3(tk); + while (tk.TokenStr == "|") { + auto opnode = std::make_unique(OpNode{BasicOp::BitwiseOr}, std::move(root), LiteralNode{-1}); + root.swap(opnode); + tk.nextToken(); + root->right = parseTier3(tk); + } + return root; +} + +std::unique_ptr Parser::parseTier3(Tokenizer& tk) +{ + auto root = parseTier4(tk); + while (tk.TokenStr == "^") { + auto opnode = std::make_unique(OpNode{BasicOp::BitwiseXor}, std::move(root), LiteralNode{-1}); + root.swap(opnode); + tk.nextToken(); + root->right = parseTier4(tk); + } + return root; +} + +std::unique_ptr Parser::parseTier4(Tokenizer& tk) +{ + auto root = parseTier5(tk); + while (tk.TokenStr == "&") { + auto opnode = std::make_unique(OpNode{BasicOp::BitwiseAnd}, std::move(root), LiteralNode{-1}); + root.swap(opnode); + tk.nextToken(); + root->right = parseTier5(tk); + } + return root; +} + +std::unique_ptr Parser::parseTier5(Tokenizer& tk) +{ + auto root = parseTier6(tk); + while (tk.TokenStr == "==" || tk.TokenStr == "!=") { + auto opnode = std::make_unique(opFromToken(tk.TokenStr), std::move(root), LiteralNode{-1}); + root.swap(opnode); + tk.nextToken(); + root->right = parseTier6(tk); + } + return root; +} + +std::unique_ptr Parser::parseTier6(Tokenizer& tk) +{ + auto root = parseTier7(tk); + while (tk.TokenStr == "<" || tk.TokenStr == "<=" || tk.TokenStr == "=>" || tk.TokenStr == ">") { + auto opnode = std::make_unique(opFromToken(tk.TokenStr), std::move(root), LiteralNode{-1}); + root.swap(opnode); + tk.nextToken(); + root->right = parseTier7(tk); + } + return root; +} + +std::unique_ptr Parser::parseTier7(Tokenizer& tk) +{ + auto root = parseTier8(tk); + while (tk.TokenStr == "+" || tk.TokenStr == "-") { + auto opnode = std::make_unique(opFromToken(tk.TokenStr), std::move(root), LiteralNode{-1}); + root.swap(opnode); + tk.nextToken(); + root->right = parseTier8(tk); + } + return root; +} + +std::unique_ptr Parser::parseTier8(Tokenizer& tk) +{ + auto root = parseBase(tk); + while (tk.TokenStr == "*" || tk.TokenStr == "/") { + auto opnode = std::make_unique(opFromToken(tk.TokenStr), std::move(root), LiteralNode{-1}); + root.swap(opnode); + tk.nextToken(); + root->right = parseBase(tk); + } + return root; +} + +std::unique_ptr Parser::parseBase(Tokenizer& tk) +{ + // parentheses + if (tk.currentToken == '(') { + tk.nextToken(); + auto node = parsePrimary(tk); + if (tk.currentToken != ')') { + throw runtime_error_f("Expected \")\" got %s", tk.TokenStr.c_str()); + } + tk.nextToken(); + return node; + } + + // identifier or function call + if (tk.currentToken == Token::Identifier) { + std::string id = tk.IdentifierStr; + tk.nextToken(); + if (tk.currentToken != '(') { // binding node or a constant + std::string binding = id; + auto posc = std::find(mathConstants.begin(), mathConstants.end(), id); + if (posc != mathConstants.end()) { // constant + return std::make_unique(LiteralNode{mathConstantsValues[std::distance(mathConstants.begin(), posc)]}); + } + // binding node + auto pos = binding.rfind(':'); + binding.erase(0, pos + 1); + binding[0] = std::toupper(binding[0]); + binding.insert(binding.begin(), 'f'); + return std::make_unique(BindingNode{runtime_hash(id.c_str()), atype::FLOAT}, binding); + } + + // function call + if (id == "ifnode") { // conditional, 3 args + auto node = std::make_unique(ConditionalNode{}, LiteralNode{-1}, LiteralNode{-1}, LiteralNode{-1}); + int args = 0; + while (tk.currentToken != ')') { + do { + tk.nextToken(); + if (args == 0) { + node->condition = parsePrimary(tk); + } else if (args == 1) { + node->left = parsePrimary(tk); + } else if (args == 2) { + node->right = parsePrimary(tk); + } else { + throw runtime_error_f("Extra argument in a conditional: %s", tk.TokenStr.c_str()); + } + ++args; + } while (tk.currentToken == ','); + } + tk.nextToken(); + return node; + } else { // normal function + auto node = std::make_unique(opFromToken(id), LiteralNode{-1}, LiteralNode{-1}); + int args = 0; + while (tk.currentToken != ')') { + do { + tk.nextToken(); + if (args == 0) { + node->left = parsePrimary(tk); + } else if (args == 1) { + node->right = parsePrimary(tk); + } else { + throw runtime_error_f("Extra argument in a function call: %s", tk.TokenStr.c_str()); + } + ++args; + } while (tk.currentToken == ','); + } + if (args == 1) { + node->right = nullptr; + } + tk.nextToken(); + return node; + } + } + + // number + if (tk.currentToken == Token::FloatNumber) { + tk.nextToken(); + switch (tk.FloatValue.index()) { + case 0: + return std::make_unique(LiteralNode{get<0>(tk.FloatValue)}); + case 1: + return std::make_unique(LiteralNode{get<1>(tk.FloatValue)}); + } + } + if (tk.currentToken == Token::IntegerNumber) { + tk.nextToken(); + switch (tk.IntegerValue.index()) { + case 0: + return std::make_unique(LiteralNode{get<0>(tk.IntegerValue)}); + case 1: + return std::make_unique(LiteralNode{get<1>(tk.IntegerValue)}); + case 2: + return std::make_unique(LiteralNode{get<2>(tk.IntegerValue)}); + case 3: + return std::make_unique(LiteralNode{get<3>(tk.IntegerValue)}); + } + } + + // error + throw runtime_error_f("Unexpected token %s in operand", tk.TokenStr.c_str()); +} + +OpNode Parser::opFromToken(std::string const& token) +{ + auto locate = std::find(mapping.begin(), mapping.end(), token); + if (locate == mapping.end()) { + throw runtime_error_f("No operation \"%s\" defined", token.c_str()); + } + return OpNode{static_cast(std::distance(mapping.begin(), locate))}; +} + } // namespace o2::framework::expressions diff --git a/Framework/Core/test/test_Expressions.cxx b/Framework/Core/test/test_Expressions.cxx index e8cf43e03e11d..eef0375f46086 100644 --- a/Framework/Core/test/test_Expressions.cxx +++ b/Framework/Core/test/test_Expressions.cxx @@ -14,6 +14,7 @@ #include "Framework/AnalysisDataModel.h" #include #include +#include using namespace o2::framework; using namespace o2::framework::expressions; @@ -127,7 +128,7 @@ TEST_CASE("TestTreeParsing") REQUIRE(ptfilter.node->left->self.index() == 1); REQUIRE(ptfilter.node->right->self.index() == 3); auto ptfilterspecs = createOperations(ptfilter); - REQUIRE(ptfilterspecs[0].left == (DatumSpec{std::string{"fPt"}, typeid(o2::aod::track::Pt).hash_code(), atype::FLOAT})); + REQUIRE(ptfilterspecs[0].left == (DatumSpec{std::string{"fPt"}, "o2::aod::track::pt"_h, atype::FLOAT})); REQUIRE(ptfilterspecs[0].right == (DatumSpec{LiteralNode::var_t{0.5f}, atype::FLOAT})); REQUIRE(ptfilterspecs[0].result == (DatumSpec{0u, atype::BOOL})); @@ -143,7 +144,7 @@ TEST_CASE("TestTreeParsing") REQUIRE(ptfilter2.node->right->self.index() == 3); REQUIRE(std::get(ptfilter2.node->right->self).name == "prefix.pTCut"); auto ptfilterspecs2 = createOperations(ptfilter2); - REQUIRE(ptfilterspecs2[0].left == (DatumSpec{std::string{"fPt"}, typeid(o2::aod::track::Pt).hash_code(), atype::FLOAT})); + REQUIRE(ptfilterspecs2[0].left == (DatumSpec{std::string{"fPt"}, "o2::aod::track::pt"_h, atype::FLOAT})); REQUIRE(ptfilterspecs2[0].right == (DatumSpec{LiteralNode::var_t{1.0f}, atype::FLOAT})); REQUIRE(ptfilterspecs2[0].result == (DatumSpec{0u, atype::BOOL})); @@ -161,12 +162,12 @@ TEST_CASE("TestGandivaTreeCreation") { Projector pze = o2::aod::track::Pze::Projector(); auto pzspecs = createOperations(pze); - REQUIRE(pzspecs[0].left == (DatumSpec{std::string{"fTgl"}, typeid(o2::aod::track::Tgl).hash_code(), atype::FLOAT})); + REQUIRE(pzspecs[0].left == (DatumSpec{std::string{"fTgl"}, "o2::aod::track::tgl"_h, atype::FLOAT})); REQUIRE(pzspecs[0].right == (DatumSpec{1u, atype::FLOAT})); REQUIRE(pzspecs[0].result == (DatumSpec{0u, atype::FLOAT})); REQUIRE(pzspecs[1].left == (DatumSpec{LiteralNode::var_t{1.f}, atype::FLOAT})); - REQUIRE(pzspecs[1].right == (DatumSpec{std::string{"fSigned1Pt"}, typeid(o2::aod::track::Signed1Pt).hash_code(), atype::FLOAT})); + REQUIRE(pzspecs[1].right == (DatumSpec{std::string{"fSigned1Pt"}, "o2::aod::track::signed1Pt"_h, atype::FLOAT})); REQUIRE(pzspecs[1].result == (DatumSpec{1u, atype::FLOAT})); auto infield1 = o2::aod::track::Signed1Pt::asArrowField(); auto infield2 = o2::aod::track::Tgl::asArrowField(); @@ -200,7 +201,7 @@ TEST_CASE("TestGandivaTreeCreation") REQUIRE(bwf[0].right == (DatumSpec{LiteralNode::var_t{0u}, atype::UINT32})); REQUIRE(bwf[0].result == (DatumSpec{0u, atype::BOOL})); - REQUIRE(bwf[1].left == (DatumSpec{std::string{"fFlags"}, typeid(o2::aod::track::Flags).hash_code(), atype::UINT32})); + REQUIRE(bwf[1].left == (DatumSpec{std::string{"fFlags"}, "o2::aod::track::flags"_h, atype::UINT32})); REQUIRE(bwf[1].right == (DatumSpec{LiteralNode::var_t{static_cast(o2::aod::track::TPCrefit)}, atype::UINT32})); REQUIRE(bwf[1].result == (DatumSpec{1u, atype::UINT32})); @@ -220,7 +221,7 @@ TEST_CASE("TestGandivaTreeCreation") REQUIRE(rf[0].right == (DatumSpec{LiteralNode::var_t{0.1f}, atype::FLOAT})); REQUIRE(rf[0].result == (DatumSpec{0u, atype::BOOL})); - REQUIRE(rf[1].left == (DatumSpec{std::string{"fPt"}, typeid(o2::aod::track::Pt).hash_code(), atype::FLOAT})); + REQUIRE(rf[1].left == (DatumSpec{std::string{"fPt"}, "o2::aod::track::pt"_h, atype::FLOAT})); REQUIRE(rf[1].right == (DatumSpec{})); REQUIRE(rf[1].result == (DatumSpec{1u, atype::FLOAT})); @@ -249,15 +250,15 @@ TEST_CASE("TestConditionalExpressions") REQUIRE(cfspecs[1].condition == (DatumSpec{5u, atype::BOOL})); REQUIRE(cfspecs[1].result == (DatumSpec{2u, atype::BOOL})); - REQUIRE(cfspecs[2].left == (DatumSpec{std::string{"fPt"}, typeid(o2::aod::track::Pt).hash_code(), atype::FLOAT})); + REQUIRE(cfspecs[2].left == (DatumSpec{std::string{"fPt"}, "o2::aod::track::pt"_h, atype::FLOAT})); REQUIRE(cfspecs[2].right == (DatumSpec{LiteralNode::var_t{1.0f}, atype::FLOAT})); REQUIRE(cfspecs[2].result == (DatumSpec{5u, atype::BOOL})); - REQUIRE(cfspecs[3].left == (DatumSpec{std::string{"fPhi"}, typeid(o2::aod::track::Phi).hash_code(), atype::FLOAT})); + REQUIRE(cfspecs[3].left == (DatumSpec{std::string{"fPhi"}, "o2::aod::track::phi"_h, atype::FLOAT})); REQUIRE(cfspecs[3].right == (DatumSpec{LiteralNode::var_t{(float)(M_PI / 2.)}, atype::FLOAT})); REQUIRE(cfspecs[3].result == (DatumSpec{4u, atype::BOOL})); - REQUIRE(cfspecs[4].left == (DatumSpec{std::string{"fPhi"}, typeid(o2::aod::track::Phi).hash_code(), atype::FLOAT})); + REQUIRE(cfspecs[4].left == (DatumSpec{std::string{"fPhi"}, "o2::aod::track::phi"_h, atype::FLOAT})); REQUIRE(cfspecs[4].right == (DatumSpec{LiteralNode::var_t{(float)(M_PI / 2.)}, atype::FLOAT})); REQUIRE(cfspecs[4].result == (DatumSpec{3u, atype::BOOL})); @@ -265,7 +266,7 @@ TEST_CASE("TestConditionalExpressions") REQUIRE(cfspecs[5].right == (DatumSpec{LiteralNode::var_t{1.0f}, atype::FLOAT})); REQUIRE(cfspecs[5].result == (DatumSpec{1u, atype::BOOL})); - REQUIRE(cfspecs[6].left == (DatumSpec{std::string{"fEta"}, typeid(o2::aod::track::Eta).hash_code(), atype::FLOAT})); + REQUIRE(cfspecs[6].left == (DatumSpec{std::string{"fEta"}, "o2::aod::track::eta"_h, atype::FLOAT})); REQUIRE(cfspecs[6].right == (DatumSpec{})); REQUIRE(cfspecs[6].result == (DatumSpec{6u, atype::FLOAT})); @@ -324,3 +325,54 @@ TEST_CASE("TestBinnedExpressions") auto tree2 = createExpressionTree(p2specs, schema2); REQUIRE(tree2->ToString() == "if (bool less_than((float) fPhi, (const float) 0 raw(0))) { (const float) -1 raw(bf800000) } else { if (bool less_than((float) fPhi, (const float) 1.5708 raw(3fc90fdb))) { float add(float add(float multiply(float multiply((const float) 1 raw(3f800000), (float) fX), (float) fX), float multiply(float multiply((const float) 2 raw(40000000), (float) fY), (float) fY)), float multiply(float multiply((const float) 3 raw(40400000), (float) fZ), (float) fZ)) } else { if (bool less_than((float) fPhi, (const float) 3.14159 raw(40490fdb))) { float add(float add(float multiply(float multiply((const float) 1.1 raw(3f8ccccd), (float) fX), (float) fX), float multiply(float multiply((const float) 2.1 raw(40066666), (float) fY), (float) fY)), float multiply(float multiply((const float) 3.1 raw(40466666), (float) fZ), (float) fZ)) } else { if (bool less_than((float) fPhi, (const float) 4.71239 raw(4096cbe4))) { float add(float add(float multiply(float multiply((const float) 1.2 raw(3f99999a), (float) fX), (float) fX), float multiply(float multiply((const float) 2.2 raw(400ccccd), (float) fY), (float) fY)), float multiply(float multiply((const float) 3.2 raw(404ccccd), (float) fZ), (float) fZ)) } else { if (bool less_than((float) fPhi, (const float) 6.28319 raw(40c90fdb))) { float add(float add(float multiply(float multiply((const float) 1.3 raw(3fa66666), (float) fX), (float) fX), float multiply(float multiply((const float) 2.3 raw(40133333), (float) fY), (float) fY)), float multiply(float multiply((const float) 3.3 raw(40533333), (float) fZ), (float) fZ)) } else { (const float) -1 raw(bf800000) } } } } }"); } + +void printTokens(Tokenizer& t) +{ + int token; + while ((token = t.nextToken()) && (token != Token::EoL)) { + std::cout << t.TokenStr << " "; + }; + std::cout << std::endl; +} + +TEST_CASE("TestStringExpressionsParsing") +{ + Filter f = (o2::aod::track::flags & 1u) != 0u && (o2::aod::track::pt <= 10.f); + std::string input = "(o2::aod::track::flags & 1u) != 0u && (o2::aod::track::pt <= 10.f)"; + + auto t1 = createOperations(f); + Filter ff = Parser::parse(input); + auto t2 = createOperations(ff); + + auto schema = std::make_shared(std::vector{o2::aod::track::Flags::asArrowField(), o2::aod::track::Pt::asArrowField()}); + auto tree1 = createExpressionTree(t1, schema); + auto tree2 = createExpressionTree(t2, schema); + + REQUIRE(tree1->ToString() == tree2->ToString()); + + Projector p = -1.f * nlog(ntan(o2::constants::math::PIQuarter - 0.5f * natan(o2::aod::fwdtrack::tgl))); + input = "-1.f * nlog(ntan(PIQuarter - 0.5f * natan(o2::aod::fwdtrack::tgl)))"; + + auto tp1 = createOperations(p); + Projector pp = Parser::parse(input); + auto tp2 = createOperations(pp); + + schema = std::make_shared(std::vector{o2::aod::fwdtrack::Tgl::asArrowField()}); + auto treep1 = createExpressionTree(tp1, schema); + auto treep2 = createExpressionTree(tp2, schema); + + REQUIRE(treep1->ToString() == treep2->ToString()); + + Filter f2 = o2::aod::track::signed1Pt > 0.f && ifnode(nabs(o2::aod::track::eta) < 1.0f, nabs(o2::aod::track::x) > 2.0f, nabs(o2::aod::track::y) > 3.0f); + input = "o2::aod::track::signed1Pt > 0.f && ifnode(nabs(o2::aod::track::eta) < 1.0f, nabs(o2::aod::track::x) > 2.0f, nabs(o2::aod::track::y) > 3.0f)"; + + auto tf1 = createOperations(f2); + Filter ff2 = Parser::parse(input); + auto tf2 = createOperations(ff2); + + schema = std::make_shared(std::vector{o2::aod::track::Eta::asArrowField(), o2::aod::track::Signed1Pt::asArrowField(), o2::aod::track::X::asArrowField(), o2::aod::track::Y::asArrowField()}); + auto treef1 = createExpressionTree(tf1, schema); + auto treef2 = createExpressionTree(tf2, schema); + + REQUIRE(treef1->ToString() == treef2->ToString()); +} diff --git a/Framework/Core/test/test_StringHelpers.cxx b/Framework/Core/test/test_StringHelpers.cxx index 44f3fffd4efee..96abe20b814a9 100644 --- a/Framework/Core/test/test_StringHelpers.cxx +++ b/Framework/Core/test/test_StringHelpers.cxx @@ -12,6 +12,9 @@ #include #include "Framework/StringHelpers.h" +static constexpr std::string_view part1 = "o2::aod::track::"; +static constexpr std::string_view part2 = "pt"; + TEST_CASE("StringHelpersHash") { std::string s{"test-string"}; @@ -19,6 +22,8 @@ TEST_CASE("StringHelpersHash") REQUIRE(runtime_hash(s.c_str()) == compile_time_hash("test-string")); REQUIRE(runtime_hash(cs) == compile_time_hash("test-string")); REQUIRE(runtime_hash(s.c_str()) == runtime_hash(cs)); + + REQUIRE(compile_time_hash(part1, part2) == "o2::aod::track::pt"_h); } template