diff --git a/include/utilities/dsl/add.hpp b/include/utilities/dsl/add.hpp new file mode 100644 index 00000000..e3e57151 --- /dev/null +++ b/include/utilities/dsl/add.hpp @@ -0,0 +1,46 @@ +/* + * Copyright 2024 NWChemEx-Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once +#include +#include + +namespace utilities::dsl { + +/** @brief Represents the addition of two terms. + * + * @tparam LHSType The type of the object on the left of the plus sign. + * @tparam RHSType The type of the object on the right of the plus sign. + * + * This class is essentially a strong type over top of BinaryOp to signal + * that the binary operation is addition (or at the least represented with a + * plus sign). + */ +template +class Add : public BinaryOp, LHSType, RHSType> { +private: + /// Type of *this + using my_type = Add; + + /// Type *this inherits from + using op_type = BinaryOp; + +public: + /// Reuse the base class's ctor + using op_type::op_type; +}; + +} // namespace utilities::dsl \ No newline at end of file diff --git a/include/utilities/dsl/binary_op.hpp b/include/utilities/dsl/binary_op.hpp new file mode 100644 index 00000000..6b3621da --- /dev/null +++ b/include/utilities/dsl/binary_op.hpp @@ -0,0 +1,222 @@ +/* + * Copyright 2024 NWChemEx-Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once +#include +#include +#include +#include + +namespace utilities::dsl { + +/** @brief Code factorization for binary operations. + * + * @tparam DerivedType the operation *this is implementing. + * @tparam LHSType The const-qualified type of the object on the left side of + * the operation. + * @tparam RHSType The const-qualified type of the object on the right side of + * the operation. + * + * The DSL implementation of most of the binary operations are the same and is + * implemented by this class. + */ +template +class BinaryOp : public Term { +private: + /// Works out the types associated with LHSType + using lhs_traits = TermTraits; + + /// Works out the types associated with RHSType + using rhs_traits = TermTraits; + +public: + /// Unqualified type of the object on the left side of the operator + using lhs_type = typename lhs_traits::value_type; + + /// Type acting like `lhs_type&`, but respecting const-ness of @p LHSType + using lhs_reference = typename lhs_traits::reference; + + /// Type acting like `const lhs_type&` + using const_lhs_reference = typename lhs_traits::const_reference; + + /// Unqualified type of the object on the right side of the operator + using rhs_type = typename rhs_traits::value_type; + + /// Type acting like `rhs_type&`, but respecting const-ness of @p RHSType + using rhs_reference = typename rhs_traits::reference; + + /// Type acting like `const rhs_type&`. + using const_rhs_reference = typename rhs_traits::const_reference; + + /** @brief Creates a new binary operation by aliasing @p l and @p r. + * + * Generally speaking binary operations will want to alias the terms on + * the left and right of the operator (as opposed to copying them or taking + * ownership). This ctor takes references to the two objects and stores + * them internally as `TermTraits::holder_type` objects (where T is + * @p LHSType and @p RHSType respectively for @p lhs and @p rhs). Thus + * whether *this ultimately owns the objects referenced by @p lhs and + * @p rhs are controlled by the respective specializations of `TermTraits`. + * + * @param[in] l An alias to the object on the left side of the operator. + * @param[in] r An alias to the object on the right side of the operator. + * + * @throw ??? Throws if converting either @p l or @p r to the holder type + * throws. Same throw guarantee. + */ + BinaryOp(lhs_reference l, rhs_reference r) : m_lhs_(l), m_rhs_(r) {} + + // ------------------------------------------------------------------------- + // -- Getters and setters + // ------------------------------------------------------------------------- + + /** @brief Returns a (possibly) mutable reference to the object on the left + * of the operator. + * + * *this is associated with two objects. The one that was on the left side + * of the operator is termed "lhs" and can be accessed via this method. + * + * @return A (possibly) mutable reference to the object which was on the + * left of the operator. The mutable-ness of the return is + * controlled by TermTraits. + * + * @throw ??? Throws if converting from the held type to lhs_reference + * throws. Same throw guarantee. + */ + lhs_reference lhs() { return m_lhs_; } + + /** @brief Returns a read-only reference to the object on the left of the + * operator. + * + * This method is identical to the non-const version except that the return + * is guaranteed to be read-only. + * + * @return A read-only reference to the object on the left of the + * operator. + * + * @throw ??? Throws if converting from the held type to + * const_lhs_reference throws. Same throw guarantee. + */ + const_lhs_reference lhs() const { return m_lhs_; } + + /** @brief Returns a (possibly) mutable reference to the object on the right + * of the operator. + * + * *this is associated with two objects. The one that was on the right side + * of the operator is termed "rhs" and can be accessed via this method. + * + * @return A (possibly) mutable reference to the object which was on the + * right of the operator. The mutable-ness of the return is + * controlled by TermTraits. + * + * @throw ??? Throws if converting from the held type to rhs_reference + * throws. Same throw guarantee. + */ + rhs_reference rhs() { return m_rhs_; } + + /** @brief Returns a read-only reference to the object on the right of the + * operator. + * + * This method is identical to the non-const version except that the return + * is guaranteed to be read-only. + * + * @return A read-only reference to the object on the right of the + * operator. + * + * @throw ??? Throws if converting from the held type to + * const_rhs_reference throws. Same throw guarantee. + */ + const_rhs_reference rhs() const { return m_rhs_; } + + // ------------------------------------------------------------------------- + // -- Utility methods + // ------------------------------------------------------------------------- + + /** @brief Is *this the same binary op as @p other? + * + * @tparam DerivedType2 The type @p other implements. + * @tparam LHSType2 The type of lhs in @p other. + * @tparam RHSType2 The type of rhs in @p other. + * + * Two BinaryOp objects are the same if they: + * - Implement the same operation, e.g., both are implementing addition, + * - Both have the same value of lhs, and + * - Both have the same value of rhs. + * + * It should be noted that following C++ convention, value comparisons are + * done with const references and thus the const-ness of @tparam LHSType + * and @tparam RHSType vs the respective const-ness of @tparam LHSType2 + * and @tparam RHSType2 is not considered. + * + * @param[in] other The object to compare to. + * + * @return True if *this is value equal and false otherwise. + * + * @throw None No throw guarantee. + */ + template + bool operator==( + const BinaryOp& other) const noexcept; + + /** @brief Is *this different than @p other? + * + * @tparam DerivedType2 The type @p other implements. + * @tparam LHSType2 The type of lhs in @p other. + * @tparam RHSType2 The type of rhs in @p other. + * + * This method defines "different" as not value equal. See the description + * for operator== for the definition of value equal. + * + * @param[in] other The object to compare to *this. + * + * @return False if *this is value equal to @p other and true otherwise. + * + * @throw None No throw guarantee. + */ + template + bool operator!=( + const BinaryOp& other) const noexcept { + return !((*this) == other); + } + +private: + /// The object on the left side of the operator + typename lhs_traits::holder_type m_lhs_; + + /// The object on the right side of the operator + typename rhs_traits::holder_type m_rhs_; +}; + +// ----------------------------------------------------------------------------- +// -- Out of line inline definitions +// ----------------------------------------------------------------------------- + +template +template +bool BinaryOp::operator==( + const BinaryOp& other) const noexcept { + using lhs2_type = typename TermTraits::value_type; + using rhs2_type = typename TermTraits::value_type; + constexpr auto l_same = std::is_same_v; + constexpr auto r_same = std::is_same_v; + if constexpr(l_same && r_same) { + return std::tie(lhs(), rhs()) == std::tie(other.lhs(), other.rhs()); + } else { + return false; + } +} + +} // namespace utilities::dsl \ No newline at end of file diff --git a/include/utilities/dsl/divide.hpp b/include/utilities/dsl/divide.hpp new file mode 100644 index 00000000..224bd997 --- /dev/null +++ b/include/utilities/dsl/divide.hpp @@ -0,0 +1,46 @@ +/* + * Copyright 2024 NWChemEx-Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once +#include +#include + +namespace utilities::dsl { + +/** @brief Represents the division of two terms. + * + * @tparam LHSType The type of the object on the left of the division sign. + * @tparam RHSType The type of the object on the right of the division sign. + * + * This class is essentially a strong type over top of BinaryOp to signal + * that the binary operation is division (or at the least represented + * with a divide sign). + */ +template +class Divide : public BinaryOp, LHSType, RHSType> { +private: + /// Type of *this + using my_type = Divide; + + /// Type *this inherits from + using op_type = BinaryOp; + +public: + /// Reuse the base class's ctor + using op_type::op_type; +}; + +} // namespace utilities::dsl \ No newline at end of file diff --git a/include/utilities/dsl/dsl.hpp b/include/utilities/dsl/dsl.hpp new file mode 100644 index 00000000..8b23ba0c --- /dev/null +++ b/include/utilities/dsl/dsl.hpp @@ -0,0 +1,22 @@ +/* + * Copyright 2024 NWChemEx-Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once +#include +#include +#include +#include +#include \ No newline at end of file diff --git a/include/utilities/dsl/dsl_fwd.hpp b/include/utilities/dsl/dsl_fwd.hpp new file mode 100644 index 00000000..eb9fc856 --- /dev/null +++ b/include/utilities/dsl/dsl_fwd.hpp @@ -0,0 +1,49 @@ +/* + * Copyright 2024 NWChemEx-Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +/** @file dsl_fwd.hpp + * + * This file forward declares the classes needed to power the DSL. The + * forward declarations are primarily useful for declaring interfaces and + * for template meta-programming. + */ + +namespace utilities::dsl { + +template +class Add; + +template +class BinaryOp; + +template +class Divide; + +template +class Multiply; + +template +class Subtract; + +template +class Term; + +template +class TermTraits; + +} // namespace utilities::dsl \ No newline at end of file diff --git a/include/utilities/dsl/multiply.hpp b/include/utilities/dsl/multiply.hpp new file mode 100644 index 00000000..257a3cdd --- /dev/null +++ b/include/utilities/dsl/multiply.hpp @@ -0,0 +1,46 @@ +/* + * Copyright 2024 NWChemEx-Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once +#include +#include + +namespace utilities::dsl { + +/** @brief Represents the multiplication of two terms. + * + * @tparam LHSType The type of the object on the left of the times sign. + * @tparam RHSType The type of the object on the right of the times sign. + * + * This class is essentially a strong type over top of BinaryOp to signal + * that the binary operation is multiplication (or at the least represented + * with a times sign). + */ +template +class Multiply : public BinaryOp, LHSType, RHSType> { +private: + /// Type of *this + using my_type = Multiply; + + /// Type *this inherits from + using op_type = BinaryOp; + +public: + /// Reuse the base class's ctor + using op_type::op_type; +}; + +} // namespace utilities::dsl \ No newline at end of file diff --git a/include/utilities/dsl/subtract.hpp b/include/utilities/dsl/subtract.hpp new file mode 100644 index 00000000..95ff79d9 --- /dev/null +++ b/include/utilities/dsl/subtract.hpp @@ -0,0 +1,46 @@ +/* + * Copyright 2024 NWChemEx-Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once +#include +#include + +namespace utilities::dsl { + +/** @brief Represents the subtraction of two terms. + * + * @tparam LHSType The type of the object on the left of the subtraction sign. + * @tparam RHSType The type of the object on the right of the subtraction sign. + * + * This class is essentially a strong type over top of BinaryOp to signal + * that the binary operation is subtraction (or at the least represented + * with a minus sign). + */ +template +class Subtract : public BinaryOp, LHSType, RHSType> { +private: + /// Type of *this + using my_type = Subtract; + + /// Type *this inherits from + using op_type = BinaryOp; + +public: + /// Reuse the base class's ctor + using op_type::op_type; +}; + +} // namespace utilities::dsl \ No newline at end of file diff --git a/include/utilities/dsl/term.hpp b/include/utilities/dsl/term.hpp new file mode 100644 index 00000000..bfbe4ff4 --- /dev/null +++ b/include/utilities/dsl/term.hpp @@ -0,0 +1,119 @@ +/* + * Copyright 2024 NWChemEx-Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once +#include +#include +#include + +namespace utilities::dsl { + +/** @brief Base class for all elements of the DSL. + * + * @tparam DerivedType Type of the object *this is implementing. + * + * Users of the DSL need to implement operator+, operator-, etc. for their + * leaves. The returns of those functions are DSL Term objects. Those objects + * can then further be composed. The Term class implements further + * composition with DSL objects. + */ +template +class Term { +public: + /** @brief Adds *this to @p rhs. + * + * @tparam RHSType The type of @p rhs. + * + * This method will create an object representing left addition by *this + * to @p rhs. + * + * @param[in] rhs The object to *this will be added. + * + * @throw ??? Throws if creation of the new DSL term throws. Same throw + * guarantee. + */ + template + auto operator+(RHSType&& rhs) { + auto& lhs = static_cast(*this); + using no_ref_t = std::remove_reference_t; + return Add(lhs, std::forward(rhs)); + } + + /** @brief Subtracts @p rhs from this. + * + * @tparam RHSType The type of @p rhs. + * + * This method will create an object representing subtracting @p rhs from + * *this. + * + * @param[in] rhs The object to be subtracted from *this. + * + * @throw ??? Throws if creation of the new DSL term throws. Same throw + * guarantee. + */ + template + auto operator-(RHSType&& rhs) { + auto& lhs = static_cast(*this); + using no_ref_t = std::remove_reference_t; + return Subtract(lhs, std::forward(rhs)); + } + + /** @brief Multiplies *this to @p rhs. + * + * @tparam RHSType The type of @p rhs. + * + * This method will create an object representing left multiplication by + * *this to @p rhs. + * + * @param[in] rhs The object *this will be multiply. + * + * @throw ??? Throws if creation of the new DSL term throws. Same throw + * guarantee. + */ + template + auto operator*(RHSType&& rhs) { + auto& lhs = static_cast(*this); + using no_ref_t = std::remove_reference_t; + return Multiply(lhs, std::forward(rhs)); + } + + /** @brief Divides *this by @p rhs. + * + * @tparam RHSType The type of @p rhs. + * + * This method will create an object representing division of + * *this by @p rhs. + * + * @param[in] rhs The object to divide *this by. + * + * @throw ??? Throws if creation of the new DSL term throws. Same throw + * guarantee. + */ + template + auto operator/(RHSType&& rhs) { + auto& lhs = static_cast(*this); + using no_ref_t = std::remove_reference_t; + return Divide(lhs, std::forward(rhs)); + } + + decltype(auto) downcast() { return static_cast(*this); } + + decltype(auto) downcast() const { + return static_cast(*this); + } +}; + +} // namespace utilities::dsl \ No newline at end of file diff --git a/include/utilities/dsl/term_traits.hpp b/include/utilities/dsl/term_traits.hpp new file mode 100644 index 00000000..44fd805b --- /dev/null +++ b/include/utilities/dsl/term_traits.hpp @@ -0,0 +1,97 @@ +/* + * Copyright 2024 NWChemEx-Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once +#include +#include + +namespace utilities::dsl { + +/** @brief Class used to work out the DSL traits for an object of type @p T. + * + * @tparam T The type we are working out the traits for. Can be mutable or + * const-qualified + * + * The DSL component is designed to work with a myriad of object types. In + * order for this to work we need to know how to take both mutable and + * read-only references to the object. This trait works out the types of those + * references. Users can specialize this class to modify the behavior of the + * DSL for their type + */ +template +class TermTraits { +public: + /// Can @p T be expressed as `const U` where U is a non-const type? + static constexpr bool is_const_v = std::is_const_v; + + /** @brief The type of @p T with all qualifiers removed + * + * The TermTraits class allows the template type parameter to be qualified. + * This member type strips all of the qualifiers off. This member type is + * used throughout this class for: + * + * - Deriving new types (e.g., adding an ampersand creates the reference + * type). + * - Determining if two TermTraits objects describe the same type + */ + using value_type = std::decay_t; + + /** @brief The type of a read-only reference to an object of type @p T. + * + * When a DSL term contains an object of type @p T, requests to access the + * term via a read-only reference will return an object of this type. This + * class defines that type as `const value_type&`. + */ + using const_reference = const value_type&; + + /** @brief The type of a (possibly) mutable reference to an object of + * type @p T. + * + * When a DSL term contains an object of type @p T, requests to access the + * term as a mutable reference will return an object of this type. This + * class defines that type as `const_reference` if @p T is a const- + * qualified type and `value_type&` if @p T is not const-qualified. + */ + using reference = + std::conditional_t; + + /** @brief Is @p T part of the DSL layer? + * + * Terms that are part of the DSL layer are often unnamed temporaries and + * their storage must be handled specially. This member variable is used + * to determine if @p T either derives from dsl::Term, or if it is a + * floating point type (floating point types are often specified inline as + * if they were part of the DSL). + */ + static constexpr bool is_dsl_term_v = + std::is_base_of_v, value_type> || + std::is_floating_point_v; + + /** @brief The type terms will hold @p T as. + * + * The terms of the DSL capture the inputs as the DSL is built up. When a + * term needs to hold an object of type @p T it does so by storing it as + * an object of type `holder_type`. For leaf objects (objects not part of + * the DSL) the default is to hold them as `reference` objects. This means + * that the DSL is NOT responsible for their lifetime. If @p T is an + * object that is part of the DSL then @p T is captured by value (DSL + * terms are expected to be light-weight and temporary). + */ + using holder_type = + std::conditional_t; +}; + +} // namespace utilities::dsl \ No newline at end of file diff --git a/tests/unit_tests/dsl/add.cpp b/tests/unit_tests/dsl/add.cpp new file mode 100644 index 00000000..96d90448 --- /dev/null +++ b/tests/unit_tests/dsl/add.cpp @@ -0,0 +1,51 @@ +/* + * Copyright 2024 NWChemEx-Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "test_dsl.hpp" +#include + +/* Testing Strategy. + * + * Add is basically a strong type, we just test it can be constructed with all + * of the possible const-variations. + */ + +TEMPLATE_LIST_TEST_CASE("Add", "", test_utilities::binary_types) { + using lhs_type = std::tuple_element_t<0, TestType>; + using rhs_type = std::tuple_element_t<1, TestType>; + + auto values = test_utilities::binary_values(); + auto [lhs, rhs] = std::get(values); + + utilities::dsl::Add a_xx(lhs, rhs); + utilities::dsl::Add a_cx(lhs, rhs); + utilities::dsl::Add a_xc(lhs, rhs); + utilities::dsl::Add a_cc(lhs, rhs); + + SECTION("CTors") { + REQUIRE(a_xx.lhs() == lhs); + REQUIRE(a_xx.rhs() == rhs); + + REQUIRE(a_cx.lhs() == lhs); + REQUIRE(a_cx.rhs() == rhs); + + REQUIRE(a_xc.lhs() == lhs); + REQUIRE(a_xc.rhs() == rhs); + + REQUIRE(a_cc.lhs() == lhs); + REQUIRE(a_cc.rhs() == rhs); + } +} \ No newline at end of file diff --git a/tests/unit_tests/dsl/binary_op.cpp b/tests/unit_tests/dsl/binary_op.cpp new file mode 100644 index 00000000..1816843b --- /dev/null +++ b/tests/unit_tests/dsl/binary_op.cpp @@ -0,0 +1,121 @@ +/* + * Copyright 2024 NWChemEx-Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "test_dsl.hpp" +#include + +/* Testing Strategy. + * + * The classes which derive from BinaryOp are strong types. We thus only need + * to test the BinaryOp infrastructure for one derived class (we must test + * through the derived class because of the CRTP usage). + */ + +TEMPLATE_LIST_TEST_CASE("BinaryOp", "", test_utilities::binary_types) { + using lhs_type = std::tuple_element_t<0, TestType>; + using rhs_type = std::tuple_element_t<1, TestType>; + + auto values = test_utilities::binary_values(); + auto [lhs, rhs] = std::get(values); + + utilities::dsl::Add a_xx(lhs, rhs); + utilities::dsl::Add a_cx(lhs, rhs); + utilities::dsl::Add a_xc(lhs, rhs); + utilities::dsl::Add a_cc(lhs, rhs); + + SECTION("CTors") { + REQUIRE(a_xx.lhs() == lhs); + REQUIRE(a_xx.rhs() == rhs); + + REQUIRE(a_cx.lhs() == lhs); + REQUIRE(a_cx.rhs() == rhs); + + REQUIRE(a_xc.lhs() == lhs); + REQUIRE(a_xc.rhs() == rhs); + + REQUIRE(a_cc.lhs() == lhs); + REQUIRE(a_cc.rhs() == rhs); + } + + SECTION("lhs()") { + REQUIRE(a_xx.lhs() == lhs); + REQUIRE(a_cx.lhs() == lhs); + REQUIRE(a_xc.lhs() == lhs); + REQUIRE(a_cc.lhs() == lhs); + } + + SECTION("lhs() const") { + REQUIRE(std::as_const(a_xx).lhs() == lhs); + REQUIRE(std::as_const(a_cx).lhs() == lhs); + REQUIRE(std::as_const(a_xc).lhs() == lhs); + REQUIRE(std::as_const(a_cc).lhs() == lhs); + } + + SECTION("rhs()") { + REQUIRE(a_xx.rhs() == rhs); + REQUIRE(a_cx.rhs() == rhs); + REQUIRE(a_xc.rhs() == rhs); + REQUIRE(a_cc.rhs() == rhs); + } + + SECTION("rhs() const") { + REQUIRE(std::as_const(a_xx).rhs() == rhs); + REQUIRE(std::as_const(a_cx).rhs() == rhs); + REQUIRE(std::as_const(a_xc).rhs() == rhs); + REQUIRE(std::as_const(a_cc).rhs() == rhs); + } + + SECTION("operator==") { + SECTION("Same values") { + utilities::dsl::Add add2(lhs, rhs); + REQUIRE(a_xx == add2); + } + + SECTION("Different const-ness") { + REQUIRE(a_xx == a_cx); + REQUIRE(a_xx == a_xc); + REQUIRE(a_xx == a_cc); + REQUIRE(a_cx == a_xc); + REQUIRE(a_cx == a_cc); + REQUIRE(a_xc == a_cc); + } + + SECTION("Different values") { + lhs_type lhs2; + rhs_type rhs2; + utilities::dsl::Add add_l(lhs2, rhs); + utilities::dsl::Add add_r(lhs, rhs2); + REQUIRE_FALSE(a_xx == add_l); + REQUIRE_FALSE(a_xx == add_r); + } + + SECTION("Different type") { + char a = 'a'; + utilities::dsl::Add add_l(a, rhs); + utilities::dsl::Add add_r(lhs, a); + REQUIRE_FALSE(a_xx == add_l); + REQUIRE_FALSE(a_xx == add_r); + } + } + + SECTION("operator!=") { + // Just negates operator== so spot check + lhs_type lhs2; + utilities::dsl::Add add_r(lhs2, rhs); + REQUIRE_FALSE(a_xx != a_cx); + REQUIRE(a_xx != add_r); + } +} \ No newline at end of file diff --git a/tests/unit_tests/dsl/divide.cpp b/tests/unit_tests/dsl/divide.cpp new file mode 100644 index 00000000..63fd90d6 --- /dev/null +++ b/tests/unit_tests/dsl/divide.cpp @@ -0,0 +1,51 @@ +/* + * Copyright 2024 NWChemEx-Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "test_dsl.hpp" +#include + +/* Testing Strategy. + * + * Divide is basically a strong type, we just test it can be constructed with + * all of the possible const-variations. + */ + +TEMPLATE_LIST_TEST_CASE("Divide", "", test_utilities::binary_types) { + using lhs_type = std::tuple_element_t<0, TestType>; + using rhs_type = std::tuple_element_t<1, TestType>; + + auto values = test_utilities::binary_values(); + auto [lhs, rhs] = std::get(values); + + utilities::dsl::Divide a_xx(lhs, rhs); + utilities::dsl::Divide a_cx(lhs, rhs); + utilities::dsl::Divide a_xc(lhs, rhs); + utilities::dsl::Divide a_cc(lhs, rhs); + + SECTION("CTors") { + REQUIRE(a_xx.lhs() == lhs); + REQUIRE(a_xx.rhs() == rhs); + + REQUIRE(a_cx.lhs() == lhs); + REQUIRE(a_cx.rhs() == rhs); + + REQUIRE(a_xc.lhs() == lhs); + REQUIRE(a_xc.rhs() == rhs); + + REQUIRE(a_cc.lhs() == lhs); + REQUIRE(a_cc.rhs() == rhs); + } +} \ No newline at end of file diff --git a/tests/unit_tests/dsl/multiply.cpp b/tests/unit_tests/dsl/multiply.cpp new file mode 100644 index 00000000..9ea604ee --- /dev/null +++ b/tests/unit_tests/dsl/multiply.cpp @@ -0,0 +1,51 @@ +/* + * Copyright 2024 NWChemEx-Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "test_dsl.hpp" +#include + +/* Testing Strategy. + * + * Multiply is basically a strong type, we just test it can be constructed with + * all of the possible const-variations. + */ + +TEMPLATE_LIST_TEST_CASE("Multiply", "", test_utilities::binary_types) { + using lhs_type = std::tuple_element_t<0, TestType>; + using rhs_type = std::tuple_element_t<1, TestType>; + + auto values = test_utilities::binary_values(); + auto [lhs, rhs] = std::get(values); + + utilities::dsl::Multiply a_xx(lhs, rhs); + utilities::dsl::Multiply a_cx(lhs, rhs); + utilities::dsl::Multiply a_xc(lhs, rhs); + utilities::dsl::Multiply a_cc(lhs, rhs); + + SECTION("CTors") { + REQUIRE(a_xx.lhs() == lhs); + REQUIRE(a_xx.rhs() == rhs); + + REQUIRE(a_cx.lhs() == lhs); + REQUIRE(a_cx.rhs() == rhs); + + REQUIRE(a_xc.lhs() == lhs); + REQUIRE(a_xc.rhs() == rhs); + + REQUIRE(a_cc.lhs() == lhs); + REQUIRE(a_cc.rhs() == rhs); + } +} \ No newline at end of file diff --git a/tests/unit_tests/dsl/subtract.cpp b/tests/unit_tests/dsl/subtract.cpp new file mode 100644 index 00000000..7b75537b --- /dev/null +++ b/tests/unit_tests/dsl/subtract.cpp @@ -0,0 +1,51 @@ +/* + * Copyright 2024 NWChemEx-Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "test_dsl.hpp" +#include + +/* Testing Strategy. + * + * Subtract is basically a strong type, we just test it can be constructed with + * all of the possible const-variations. + */ + +TEMPLATE_LIST_TEST_CASE("Subtract", "", test_utilities::binary_types) { + using lhs_type = std::tuple_element_t<0, TestType>; + using rhs_type = std::tuple_element_t<1, TestType>; + + auto values = test_utilities::binary_values(); + auto [lhs, rhs] = std::get(values); + + utilities::dsl::Subtract a_xx(lhs, rhs); + utilities::dsl::Subtract a_cx(lhs, rhs); + utilities::dsl::Subtract a_xc(lhs, rhs); + utilities::dsl::Subtract a_cc(lhs, rhs); + + SECTION("CTors") { + REQUIRE(a_xx.lhs() == lhs); + REQUIRE(a_xx.rhs() == rhs); + + REQUIRE(a_cx.lhs() == lhs); + REQUIRE(a_cx.rhs() == rhs); + + REQUIRE(a_xc.lhs() == lhs); + REQUIRE(a_xc.rhs() == rhs); + + REQUIRE(a_cc.lhs() == lhs); + REQUIRE(a_cc.rhs() == rhs); + } +} \ No newline at end of file diff --git a/tests/unit_tests/dsl/term.cpp b/tests/unit_tests/dsl/term.cpp new file mode 100644 index 00000000..0b1c306a --- /dev/null +++ b/tests/unit_tests/dsl/term.cpp @@ -0,0 +1,54 @@ +/* + * Copyright 2024 NWChemEx-Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "test_dsl.hpp" +#include + +/* Testing Strategy. + * + * The Term class ensures that it is possible to combine DSL objects without + * having to overload operator+, operator*, etc. for every object type in the + * DSL. Thus to test it, it suffices to have a DSL object that derives from + * Term and then to operate on that object. For this purpose we define an object + * "a" which represents adding 4 and 2. We then add, multiply, etc. to "a" the + * integer 42. + */ + +TEST_CASE("Term") { + int four(4), two(2), forty_two(42); + using lhs_type = utilities::dsl::Add; + lhs_type a(four, two); + + SECTION("operator+") { + utilities::dsl::Add corr(a, forty_two); + REQUIRE((a + forty_two) == corr); + } + + SECTION("operator-") { + utilities::dsl::Subtract corr(a, forty_two); + REQUIRE((a - forty_two) == corr); + } + + SECTION("operator*") { + utilities::dsl::Multiply corr(a, forty_two); + REQUIRE((a * forty_two) == corr); + } + + SECTION("operator/") { + utilities::dsl::Divide corr(a, forty_two); + REQUIRE((a / forty_two) == corr); + } +} \ No newline at end of file diff --git a/tests/unit_tests/dsl/term_traits.cpp b/tests/unit_tests/dsl/term_traits.cpp new file mode 100644 index 00000000..aa1bc479 --- /dev/null +++ b/tests/unit_tests/dsl/term_traits.cpp @@ -0,0 +1,94 @@ +/* + * Copyright 2024 NWChemEx-Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "../catch.hpp" +#include +#include + +using namespace utilities; + +/* Testing strategy. + * + * Our goal here is to verify that we implemented the TMP in the TermTraits + * class correctly. That really depends on whether or not the template type + * parameter T: + * + * - is const qualified, + * - is derived from dsl::Term, + * - is a floating point type + */ + +TEST_CASE("TermTraits") { + using traits = dsl::TermTraits; + STATIC_REQUIRE_FALSE(traits::is_const_v); + STATIC_REQUIRE(std::is_same_v); + STATIC_REQUIRE(std::is_same_v); + STATIC_REQUIRE(std::is_same_v); + STATIC_REQUIRE_FALSE(traits::is_dsl_term_v); + STATIC_REQUIRE(std::is_same_v); +} + +TEST_CASE("TermTraits") { + using traits = dsl::TermTraits; + STATIC_REQUIRE(traits::is_const_v); + STATIC_REQUIRE(std::is_same_v); + STATIC_REQUIRE(std::is_same_v); + STATIC_REQUIRE(std::is_same_v); + STATIC_REQUIRE_FALSE(traits::is_dsl_term_v); + STATIC_REQUIRE(std::is_same_v); +} + +TEST_CASE("TermTraits") { + using traits = dsl::TermTraits; + STATIC_REQUIRE_FALSE(traits::is_const_v); + STATIC_REQUIRE(std::is_same_v); + STATIC_REQUIRE(std::is_same_v); + STATIC_REQUIRE(std::is_same_v); + STATIC_REQUIRE(traits::is_dsl_term_v); + STATIC_REQUIRE(std::is_same_v); +} + +TEST_CASE("TermTraits") { + using traits = dsl::TermTraits; + STATIC_REQUIRE(traits::is_const_v); + STATIC_REQUIRE(std::is_same_v); + STATIC_REQUIRE(std::is_same_v); + STATIC_REQUIRE(std::is_same_v); + STATIC_REQUIRE(traits::is_dsl_term_v); + STATIC_REQUIRE(std::is_same_v); +} + +TEST_CASE("TermTraits>") { + using op_t = dsl::Add; + using traits = dsl::TermTraits; + STATIC_REQUIRE_FALSE(traits::is_const_v); + STATIC_REQUIRE(std::is_same_v); + STATIC_REQUIRE(std::is_same_v); + STATIC_REQUIRE(std::is_same_v); + STATIC_REQUIRE(traits::is_dsl_term_v); + STATIC_REQUIRE(std::is_same_v); +} + +TEST_CASE("TermTraits>") { + using op_t = dsl::Add; + using traits = dsl::TermTraits; + STATIC_REQUIRE(traits::is_const_v); + STATIC_REQUIRE(std::is_same_v); + STATIC_REQUIRE(std::is_same_v); + STATIC_REQUIRE(std::is_same_v); + STATIC_REQUIRE(traits::is_dsl_term_v); + STATIC_REQUIRE(std::is_same_v); +} \ No newline at end of file diff --git a/tests/unit_tests/dsl/test_dsl.hpp b/tests/unit_tests/dsl/test_dsl.hpp new file mode 100644 index 00000000..07d14edd --- /dev/null +++ b/tests/unit_tests/dsl/test_dsl.hpp @@ -0,0 +1,60 @@ +/* + * Copyright 2024 NWChemEx-Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "../test_helpers.hpp" +#include +#include +#include + +/* Testing strategy. + * + * The DSL component doesn't really care what the types are as long as they + * are comparable via operator==. So we test with built-in types: + * + * - double + * - int + * + * and "user-defined" (actually defined in standard library) types like: + * + * - std::vector + * - std::map. + * + * For the purposes of integration testing, the tests in operator_impl.cpp + * will ensure that operators can be added together. + */ + +namespace test_utilities { + +/// Provides a tuple of values to initialize unary DSL terms with +inline auto unary_values() { + return std::make_tuple(double{3.14}, int{42}, std::vector{1, 2, 3}, + std::map{{3.14, 42}}); +} + +/// Tuple type returned from unary_values +using unary_types = decltype(unary_values()); + +/// Provides a tuple of pairs of values to initialize binary DSL terms with +inline auto binary_values() { + auto [v0, v1, v2, v3] = unary_values(); + return std::make_tuple(std::pair(v0, v0), std::pair(v0, v1), + std::pair(v2, v3), std::pair(v3, v3)); +} + +/// Tuple type returned from binary_values +using binary_types = decltype(binary_values()); + +} // namespace test_utilities diff --git a/tests/unit_tests/test_helpers.hpp b/tests/unit_tests/test_helpers.hpp new file mode 100644 index 00000000..55c84ae7 --- /dev/null +++ b/tests/unit_tests/test_helpers.hpp @@ -0,0 +1,75 @@ +/* + * Copyright 2024 NWChemEx-Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once +#include "catch.hpp" +#include + +namespace test_utilities { + +template +void test_copy_ctor(T&& object2test) { + using clean_type = std::decay_t; // Was given a reference + + clean_type object_copy(object2test); + REQUIRE(object_copy == object2test); // Check value +} + +template +void test_copy_assignment(T&& object2test) { + using clean_type = std::decay_t; // Was given a reference + + clean_type object_copy; + auto pobject_copy = &(object_copy = object2test); + + REQUIRE(pobject_copy == &object_copy); // Check returns *this + REQUIRE(object_copy == object2test); // Check value +} + +template +void test_move_ctor(T&& object2test) { + using clean_type = std::decay_t; // Was given a reference + + clean_type object_copy(object2test); + clean_type object_move(std::move(object2test)); + REQUIRE(object_copy == object_move); // Check value +} + +template +void test_move_assignment(T&& object2test) { + using clean_type = std::decay_t; // Was given a reference + + clean_type object_copy(object2test); + clean_type object_move; + auto pobject_move = &(object_move = object2test); + + REQUIRE(pobject_move == &object_move); // Check returns *this + REQUIRE(object_copy == object_move); // Check value +} + +template +void test_copy_and_move(Args&&... args) { + SECTION("Copy Ctor") { (test_copy_ctor(std::forward(args)), ...); } + SECTION("Move Ctor") { (test_move_ctor(std::forward(args)), ...); } + SECTION("Copy Assignment") { + (test_copy_assignment(std::forward(args)), ...); + } + SECTION("Move Assignment") { + (test_move_assignment(std::forward(args)), ...); + } +} + +} // namespace test_utilities \ No newline at end of file