| // Copyright (c) 2018, NVIDIA CORPORATION. All rights reserved. |
| // |
| // 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. |
| |
| #ifndef FORTRAN_EVALUATE_EXPRESSION_H_ |
| #define FORTRAN_EVALUATE_EXPRESSION_H_ |
| |
| // Represent Fortran expressions in a type-safe manner. |
| // Expressions are the sole owners of their constituents; i.e., there is no |
| // context-independent hash table or sharing of common subexpressions, and |
| // thus these are trees, not DAGs. Both deep copy and move semantics are |
| // supported for expression construction. |
| |
| #include "common.h" |
| #include "type.h" |
| #include "variable.h" |
| #include "../lib/common/fortran.h" |
| #include "../lib/common/idioms.h" |
| #include "../lib/common/template.h" |
| #include "../lib/parser/char-block.h" |
| #include "../lib/parser/message.h" |
| #include <ostream> |
| #include <tuple> |
| #include <type_traits> |
| #include <variant> |
| |
| namespace Fortran::evaluate { |
| |
| using common::RelationalOperator; |
| |
| // Expressions are represented by specializations of the class template Expr. |
| // Each of these specializations wraps a single data member "u" that |
| // is a std::variant<> discriminated union over all of the representational |
| // types for the constants, variables, operations, and other entities that |
| // can be valid expressions in that context: |
| // - Expr<Type<CATEGORY, KIND>> represents an expression whose result is of a |
| // specific intrinsic type category and kind, e.g. Type<TypeCategory::Real, 4> |
| // - Expr<SomeDerived> wraps data and procedure references that result in an |
| // instance of a derived type |
| // - Expr<SomeKind<CATEGORY>> is a union of Expr<Type<CATEGORY, K>> for each |
| // kind type parameter value K in that intrinsic type category. It represents |
| // an expression with known category and any kind. |
| // - Expr<SomeType> is a union of Expr<SomeKind<CATEGORY>> over the five |
| // intrinsic type categories of Fortran. It represents any valid expression. |
| // |
| // Every Expr specialization supports at least these interfaces: |
| // using Result = ...; // type of a result of this expression |
| // using IsFoldableTrait = ...; |
| // DynamicType GetType() const; |
| // int Rank() const; |
| // std::ostream &Dump(std::ostream &) const; |
| // // If IsFoldableTrait::value is true, then these exist: |
| // std::optional<Constant<Result>> Fold(FoldingContext &c); |
| // std::optional<Scalar<Result>> ScalarValue() const; |
| |
| // Everything that can appear in, or as, a valid Fortran expression must be |
| // represented with an instance of some class containing a Result typedef that |
| // maps to some instantiation of Type<CATEGORY, KIND>, SomeKind<CATEGORY>, |
| // or SomeType. |
| template<typename A> using ResultType = typename std::decay_t<A>::Result; |
| |
| // BOZ literal "typeless" constants must be wide enough to hold a numeric |
| // value of any supported kind of INTEGER or REAL. They must also be |
| // distinguishable from other integer constants, since they are permitted |
| // to be used in only a few situations. |
| using BOZLiteralConstant = typename LargestReal::Scalar::Word; |
| |
| // Operations always have specific Fortran result types (i.e., with known |
| // intrinsic type category and kind parameter value). The classes that |
| // represent the operations all inherit from this Operation<> base class |
| // template. Note that Operation has as its first type parameter (DERIVED) a |
| // "curiously reoccurring template pattern (CRTP)" reference to the specific |
| // operation class being derived from Operation; e.g., Add is defined with |
| // struct Add : public Operation<Add, ...>. Uses of instances of Operation<>, |
| // including its own member functions, can access each specific class derived |
| // from it via its derived() member function with compile-time type safety. |
| template<typename DERIVED, typename RESULT, typename... OPERANDS> |
| class Operation { |
| // The extra "int" member is a dummy that allows a safe unused reference |
| // to element 1 to arise indirectly in the definition of "right()" below |
| // when the operation has but a single operand. |
| using OperandTypes = std::tuple<OPERANDS..., int>; |
| |
| public: |
| using Derived = DERIVED; |
| using Result = RESULT; |
| static_assert(Result::isSpecificIntrinsicType); |
| static constexpr std::size_t operands{sizeof...(OPERANDS)}; |
| template<int J> using Operand = std::tuple_element_t<J, OperandTypes>; |
| using IsFoldableTrait = std::true_type; |
| |
| // Unary operations wrap a single Expr with a CopyableIndirection. |
| // Binary operations wrap a tuple of CopyableIndirections to Exprs. |
| private: |
| using Container = |
| std::conditional_t<operands == 1, CopyableIndirection<Expr<Operand<0>>>, |
| std::tuple<CopyableIndirection<Expr<OPERANDS>>...>>; |
| |
| public: |
| CLASS_BOILERPLATE(Operation) |
| explicit Operation(const Expr<OPERANDS> &... x) : operand_{x...} {} |
| explicit Operation(Expr<OPERANDS> &&... x) |
| : operand_{std::forward<Expr<OPERANDS>>(x)...} {} |
| |
| Derived &derived() { return *static_cast<Derived *>(this); } |
| const Derived &derived() const { return *static_cast<const Derived *>(this); } |
| |
| // References to operand expressions from member functions of derived |
| // classes for specific operators can be made by index, e.g. operand<0>(), |
| // which must be spelled like "this->template operand<0>()" when |
| // inherited in a derived class template. There are convenience aliases |
| // left() and right() that are not templates. |
| template<int J> Expr<Operand<J>> &operand() { |
| if constexpr (operands == 1) { |
| static_assert(J == 0); |
| return *operand_; |
| } else { |
| return *std::get<J>(operand_); |
| } |
| } |
| template<int J> const Expr<Operand<J>> &operand() const { |
| if constexpr (operands == 1) { |
| static_assert(J == 0); |
| return *operand_; |
| } else { |
| return *std::get<J>(operand_); |
| } |
| } |
| |
| Expr<Operand<0>> &left() { return operand<0>(); } |
| const Expr<Operand<0>> &left() const { return operand<0>(); } |
| |
| std::conditional_t<(operands > 1), Expr<Operand<1>> &, void> right() { |
| if constexpr (operands > 1) { |
| return operand<1>(); |
| } |
| } |
| std::conditional_t<(operands > 1), const Expr<Operand<1>> &, void> |
| right() const { |
| if constexpr (operands > 1) { |
| return operand<1>(); |
| } |
| } |
| |
| static constexpr std::optional<DynamicType> GetType() { |
| return Result::GetType(); |
| } |
| int Rank() const { |
| int rank{left().Rank()}; |
| if constexpr (operands > 1) { |
| int rightRank{right().Rank()}; |
| if (rightRank > rank) { |
| rank = rightRank; |
| } |
| } |
| return rank; |
| } |
| |
| std::ostream &Dump(std::ostream &) const; |
| std::optional<Constant<Result>> Fold(FoldingContext &); |
| |
| protected: |
| // Overridable functions for Dump() |
| static std::ostream &Prefix(std::ostream &o) { return o << '('; } |
| static std::ostream &Infix(std::ostream &o) { return o << ','; } |
| static std::ostream &Suffix(std::ostream &o) { return o << ')'; } |
| |
| private: |
| Container operand_; |
| }; |
| |
| // Unary operations |
| |
| // Conversions to specific types from expressions of known category and |
| // dynamic kind. |
| template<typename TO, TypeCategory FROMCAT> |
| struct Convert : public Operation<Convert<TO, FROMCAT>, TO, SomeKind<FROMCAT>> { |
| // Fortran doesn't have conversions between kinds of CHARACTER. |
| // Conversions between kinds of COMPLEX are represented piecewise. |
| static_assert(((TO::category == TypeCategory::Integer || |
| TO::category == TypeCategory::Real) && |
| (FROMCAT == TypeCategory::Integer || |
| FROMCAT == TypeCategory::Real)) || |
| (TO::category == TypeCategory::Logical && |
| FROMCAT == TypeCategory::Logical)); |
| using Result = TO; |
| using Operand = SomeKind<FROMCAT>; |
| using Base = Operation<Convert, Result, Operand>; |
| using Base::Base; |
| static std::optional<Scalar<Result>> FoldScalar( |
| FoldingContext &, const Scalar<Operand> &); |
| std::ostream &Dump(std::ostream &) const; |
| }; |
| |
| template<typename A> |
| struct Parentheses : public Operation<Parentheses<A>, A, A> { |
| using Result = A; |
| using Operand = A; |
| using Base = Operation<Parentheses, A, A>; |
| using Base::Base; |
| static std::optional<Scalar<Result>> FoldScalar( |
| FoldingContext &, const Scalar<Operand> &x) { |
| return {x}; |
| } |
| }; |
| |
| template<typename A> struct Negate : public Operation<Negate<A>, A, A> { |
| using Result = A; |
| using Operand = A; |
| using Base = Operation<Negate, A, A>; |
| using Base::Base; |
| static std::optional<Scalar<Result>> FoldScalar( |
| FoldingContext &, const Scalar<Operand> &); |
| static std::ostream &Prefix(std::ostream &o) { return o << "(-"; } |
| }; |
| |
| template<int KIND> |
| struct ComplexComponent |
| : public Operation<ComplexComponent<KIND>, Type<TypeCategory::Real, KIND>, |
| Type<TypeCategory::Complex, KIND>> { |
| using Result = Type<TypeCategory::Real, KIND>; |
| using Operand = Type<TypeCategory::Complex, KIND>; |
| using Base = Operation<ComplexComponent, Result, Operand>; |
| CLASS_BOILERPLATE(ComplexComponent) |
| ComplexComponent(bool isImaginary, const Expr<Operand> &x) |
| : Base{x}, isImaginaryPart{isImaginary} {} |
| ComplexComponent(bool isImaginary, Expr<Operand> &&x) |
| : Base{std::move(x)}, isImaginaryPart{isImaginary} {} |
| |
| std::optional<Scalar<Result>> FoldScalar( |
| FoldingContext &, const Scalar<Operand> &) const; |
| std::ostream &Suffix(std::ostream &o) const { |
| return o << (isImaginaryPart ? "%IM)" : "%RE)"); |
| } |
| |
| bool isImaginaryPart{true}; |
| }; |
| |
| template<int KIND> |
| struct Not : public Operation<Not<KIND>, Type<TypeCategory::Logical, KIND>, |
| Type<TypeCategory::Logical, KIND>> { |
| using Result = Type<TypeCategory::Logical, KIND>; |
| using Operand = Result; |
| using Base = Operation<Not, Result, Operand>; |
| using Base::Base; |
| static std::optional<Scalar<Result>> FoldScalar( |
| FoldingContext &, const Scalar<Operand> &); |
| static std::ostream &Prefix(std::ostream &o) { return o << "(.NOT."; } |
| }; |
| |
| // Binary operations |
| |
| template<typename A> struct Add : public Operation<Add<A>, A, A, A> { |
| using Result = A; |
| using Operand = A; |
| using Base = Operation<Add, A, A, A>; |
| using Base::Base; |
| static std::optional<Scalar<Result>> FoldScalar( |
| FoldingContext &, const Scalar<Operand> &, const Scalar<Operand> &); |
| static std::ostream &Infix(std::ostream &o) { return o << '+'; } |
| }; |
| |
| template<typename A> struct Subtract : public Operation<Subtract<A>, A, A, A> { |
| using Result = A; |
| using Operand = A; |
| using Base = Operation<Subtract, A, A, A>; |
| using Base::Base; |
| static std::optional<Scalar<Result>> FoldScalar( |
| FoldingContext &, const Scalar<Operand> &, const Scalar<Operand> &); |
| static std::ostream &Infix(std::ostream &o) { return o << '-'; } |
| }; |
| |
| template<typename A> struct Multiply : public Operation<Multiply<A>, A, A, A> { |
| using Result = A; |
| using Operand = A; |
| using Base = Operation<Multiply, A, A, A>; |
| using Base::Base; |
| static std::optional<Scalar<Result>> FoldScalar( |
| FoldingContext &, const Scalar<Operand> &, const Scalar<Operand> &); |
| static std::ostream &Infix(std::ostream &o) { return o << '*'; } |
| }; |
| |
| template<typename A> struct Divide : public Operation<Divide<A>, A, A, A> { |
| using Result = A; |
| using Operand = A; |
| using Base = Operation<Divide, A, A, A>; |
| using Base::Base; |
| static std::optional<Scalar<Result>> FoldScalar( |
| FoldingContext &, const Scalar<Operand> &, const Scalar<Operand> &); |
| static std::ostream &Infix(std::ostream &o) { return o << '/'; } |
| }; |
| |
| template<typename A> struct Power : public Operation<Power<A>, A, A, A> { |
| using Result = A; |
| using Operand = A; |
| using Base = Operation<Power, A, A, A>; |
| using Base::Base; |
| static std::optional<Scalar<Result>> FoldScalar( |
| FoldingContext &, const Scalar<Operand> &, const Scalar<Operand> &); |
| static std::ostream &Infix(std::ostream &o) { return o << "**"; } |
| }; |
| |
| template<typename A> |
| struct RealToIntPower : public Operation<RealToIntPower<A>, A, A, SomeInteger> { |
| using Base = Operation<RealToIntPower, A, A, SomeInteger>; |
| using Result = A; |
| using BaseOperand = A; |
| using ExponentOperand = SomeInteger; |
| using Base::Base; |
| static std::optional<Scalar<Result>> FoldScalar(FoldingContext &, |
| const Scalar<BaseOperand> &, const Scalar<ExponentOperand> &); |
| static std::ostream &Infix(std::ostream &o) { return o << "**"; } |
| }; |
| |
| template<typename A> struct Extremum : public Operation<Extremum<A>, A, A, A> { |
| using Result = A; |
| using Operand = A; |
| using Base = Operation<Extremum, A, A, A>; |
| CLASS_BOILERPLATE(Extremum) |
| Extremum(const Expr<Operand> &x, const Expr<Operand> &y, |
| Ordering ord = Ordering::Greater) |
| : Base{x, y}, ordering{ord} {} |
| Extremum( |
| Expr<Operand> &&x, Expr<Operand> &&y, Ordering ord = Ordering::Greater) |
| : Base{std::move(x), std::move(y)}, ordering{ord} {} |
| |
| std::optional<Scalar<Result>> FoldScalar( |
| FoldingContext &, const Scalar<Operand> &, const Scalar<Operand> &) const; |
| std::ostream &Prefix(std::ostream &o) const { |
| return o << (ordering == Ordering::Less ? "MIN(" : "MAX("); |
| } |
| |
| Ordering ordering{Ordering::Greater}; |
| }; |
| |
| template<int KIND> |
| struct ComplexConstructor |
| : public Operation<ComplexConstructor<KIND>, |
| Type<TypeCategory::Complex, KIND>, Type<TypeCategory::Real, KIND>, |
| Type<TypeCategory::Real, KIND>> { |
| using Result = Type<TypeCategory::Complex, KIND>; |
| using Operand = Type<TypeCategory::Real, KIND>; |
| using Base = Operation<ComplexConstructor, Result, Operand, Operand>; |
| using Base::Base; |
| static std::optional<Scalar<Result>> FoldScalar( |
| FoldingContext &, const Scalar<Operand> &, const Scalar<Operand> &); |
| }; |
| |
| template<int KIND> |
| struct Concat |
| : public Operation<Concat<KIND>, Type<TypeCategory::Character, KIND>, |
| Type<TypeCategory::Character, KIND>, |
| Type<TypeCategory::Character, KIND>> { |
| using Result = Type<TypeCategory::Character, KIND>; |
| using Operand = Result; |
| using Base = Operation<Concat, Result, Operand, Operand>; |
| using Base::Base; |
| static std::optional<Scalar<Result>> FoldScalar( |
| FoldingContext &, const Scalar<Operand> &, const Scalar<Operand> &); |
| static std::ostream &Infix(std::ostream &o) { return o << "//"; } |
| }; |
| |
| ENUM_CLASS(LogicalOperator, And, Or, Eqv, Neqv) |
| |
| template<int KIND> |
| struct LogicalOperation |
| : public Operation<LogicalOperation<KIND>, Type<TypeCategory::Logical, KIND>, |
| Type<TypeCategory::Logical, KIND>, Type<TypeCategory::Logical, KIND>> { |
| using Result = Type<TypeCategory::Logical, KIND>; |
| using Operand = Result; |
| using Base = Operation<LogicalOperation, Result, Operand, Operand>; |
| CLASS_BOILERPLATE(LogicalOperation) |
| LogicalOperation( |
| LogicalOperator opr, const Expr<Operand> &x, const Expr<Operand> &y) |
| : Base{x, y}, logicalOperator{opr} {} |
| LogicalOperation(LogicalOperator opr, Expr<Operand> &&x, Expr<Operand> &&y) |
| : Base{std::move(x), std::move(y)}, logicalOperator{opr} {} |
| |
| std::optional<Scalar<Result>> FoldScalar( |
| FoldingContext &, const Scalar<Operand> &, const Scalar<Operand> &) const; |
| std::ostream &Infix(std::ostream &) const; |
| |
| LogicalOperator logicalOperator; |
| }; |
| |
| // Per-category expression representations |
| |
| // Common Expr<> behaviors |
| template<typename RESULT> struct ExpressionBase { |
| using Result = RESULT; |
| using Derived = Expr<Result>; |
| |
| Derived &derived() { return *static_cast<Derived *>(this); } |
| const Derived &derived() const { return *static_cast<const Derived *>(this); } |
| |
| template<typename A> Derived &operator=(const A &x) { |
| Derived &d{derived()}; |
| d.u = x; |
| return d; |
| } |
| |
| template<typename A> |
| Derived &operator=(std::enable_if_t<!std::is_reference_v<A>, A> &&x) { |
| Derived &d{derived()}; |
| d.u = std::move(x); |
| return d; |
| } |
| |
| std::optional<DynamicType> GetType() const; |
| int Rank() const; |
| std::ostream &Dump(std::ostream &) const; |
| std::optional<Constant<Result>> Fold(FoldingContext &c); |
| std::optional<Scalar<Result>> ScalarValue() const; |
| }; |
| |
| template<int KIND> |
| class Expr<Type<TypeCategory::Integer, KIND>> |
| : public ExpressionBase<Type<TypeCategory::Integer, KIND>> { |
| public: |
| using Result = Type<TypeCategory::Integer, KIND>; |
| using IsFoldableTrait = std::true_type; |
| // TODO: R916 type-param-inquiry |
| |
| EVALUATE_UNION_CLASS_BOILERPLATE(Expr) |
| explicit Expr(const Scalar<Result> &x) : u{Constant<Result>{x}} {} |
| template<typename INT> |
| explicit Expr(std::enable_if_t<std::is_integral_v<INT>, INT> n) |
| : u{Constant<Result>{n}} {} |
| |
| private: |
| using Conversions = std::variant<Convert<Result, TypeCategory::Integer>, |
| Convert<Result, TypeCategory::Real>>; |
| using Operations = std::variant<Parentheses<Result>, Negate<Result>, |
| Add<Result>, Subtract<Result>, Multiply<Result>, Divide<Result>, |
| Power<Result>, Extremum<Result>>; |
| using Others = |
| std::variant<Constant<Result>, Designator<Result>, FunctionRef<Result>>; |
| |
| public: |
| common::CombineVariants<Operations, Conversions, Others> u; |
| }; |
| |
| template<int KIND> |
| class Expr<Type<TypeCategory::Real, KIND>> |
| : public ExpressionBase<Type<TypeCategory::Real, KIND>> { |
| public: |
| using Result = Type<TypeCategory::Real, KIND>; |
| using IsFoldableTrait = std::true_type; |
| |
| EVALUATE_UNION_CLASS_BOILERPLATE(Expr) |
| explicit Expr(const Scalar<Result> &x) : u{Constant<Result>{x}} {} |
| |
| private: |
| // N.B. Real->Complex and Complex->Real conversions are done with CMPLX |
| // and part access operations (resp.). Conversions between kinds of |
| // Complex are done via decomposition to Real and reconstruction. |
| using Conversions = std::variant<Convert<Result, TypeCategory::Integer>, |
| Convert<Result, TypeCategory::Real>>; |
| using Operations = std::variant<ComplexComponent<KIND>, Parentheses<Result>, |
| Negate<Result>, Add<Result>, Subtract<Result>, Multiply<Result>, |
| Divide<Result>, Power<Result>, RealToIntPower<Result>, Extremum<Result>>; |
| using Others = |
| std::variant<Constant<Result>, Designator<Result>, FunctionRef<Result>>; |
| |
| public: |
| common::CombineVariants<Operations, Conversions, Others> u; |
| }; |
| |
| template<int KIND> |
| class Expr<Type<TypeCategory::Complex, KIND>> |
| : public ExpressionBase<Type<TypeCategory::Complex, KIND>> { |
| public: |
| using Result = Type<TypeCategory::Complex, KIND>; |
| using IsFoldableTrait = std::true_type; |
| EVALUATE_UNION_CLASS_BOILERPLATE(Expr) |
| explicit Expr(const Scalar<Result> &x) : u{Constant<Result>{x}} {} |
| |
| // Note that many COMPLEX operations are represented as REAL operations |
| // over their components (viz., conversions, negation, add, and subtract). |
| using Operations = |
| std::variant<Parentheses<Result>, Multiply<Result>, Divide<Result>, |
| Power<Result>, RealToIntPower<Result>, ComplexConstructor<KIND>>; |
| using Others = |
| std::variant<Constant<Result>, Designator<Result>, FunctionRef<Result>>; |
| |
| public: |
| common::CombineVariants<Operations, Others> u; |
| }; |
| |
| FOR_EACH_INTEGER_KIND(extern template class Expr) |
| FOR_EACH_REAL_KIND(extern template class Expr) |
| FOR_EACH_COMPLEX_KIND(extern template class Expr) |
| |
| template<int KIND> |
| class Expr<Type<TypeCategory::Character, KIND>> |
| : public ExpressionBase<Type<TypeCategory::Character, KIND>> { |
| public: |
| using Result = Type<TypeCategory::Character, KIND>; |
| using IsFoldableTrait = std::true_type; |
| EVALUATE_UNION_CLASS_BOILERPLATE(Expr) |
| explicit Expr(const Scalar<Result> &x) : u{Constant<Result>{x}} {} |
| explicit Expr(Scalar<Result> &&x) : u{Constant<Result>{std::move(x)}} {} |
| |
| Expr<SubscriptInteger> LEN() const; |
| |
| std::variant<Constant<Result>, Designator<Result>, FunctionRef<Result>, |
| Parentheses<Result>, Concat<KIND>, Extremum<Result>> |
| u; |
| }; |
| |
| FOR_EACH_CHARACTER_KIND(extern template class Expr) |
| |
| // The Relational class template is a helper for constructing logical |
| // expressions with polymorphism over the cross product of the possible |
| // categories and kinds of comparable operands. |
| // Fortran defines a numeric relation with distinct types or kinds as |
| // first undergoing the same operand conversions that occur with the intrinsic |
| // addition operator. Character relations must have the same kind. |
| // There are no relations between LOGICAL values. |
| |
| template<typename A> |
| struct Relational : public Operation<Relational<A>, LogicalResult, A, A> { |
| using Base = Operation<Relational, LogicalResult, A, A>; |
| using typename Base::Result; |
| using Operand = typename Base::template Operand<0>; |
| CLASS_BOILERPLATE(Relational) |
| Relational( |
| RelationalOperator r, const Expr<Operand> &a, const Expr<Operand> &b) |
| : Base{a, b}, opr{r} {} |
| Relational(RelationalOperator r, Expr<Operand> &&a, Expr<Operand> &&b) |
| : Base{std::move(a), std::move(b)}, opr{r} {} |
| |
| std::optional<Scalar<Result>> FoldScalar( |
| FoldingContext &c, const Scalar<Operand> &, const Scalar<Operand> &); |
| std::ostream &Infix(std::ostream &) const; |
| |
| RelationalOperator opr; |
| }; |
| |
| template<> class Relational<SomeType> { |
| // COMPLEX data are compared piecewise. |
| using DirectlyComparableTypes = |
| common::CombineTuples<IntegerTypes, RealTypes, CharacterTypes>; |
| |
| public: |
| using Result = LogicalResult; |
| EVALUATE_UNION_CLASS_BOILERPLATE(Relational) |
| static constexpr std::optional<DynamicType> GetType() { |
| return Result::GetType(); |
| } |
| int Rank() const { |
| return std::visit([](const auto &x) { return x.Rank(); }, u); |
| } |
| std::ostream &Dump(std::ostream &o) const; |
| common::MapTemplate<Relational, DirectlyComparableTypes> u; |
| }; |
| |
| FOR_EACH_INTEGER_KIND(extern template struct Relational) |
| FOR_EACH_REAL_KIND(extern template struct Relational) |
| FOR_EACH_CHARACTER_KIND(extern template struct Relational) |
| extern template struct Relational<SomeType>; |
| |
| template<int KIND> |
| class Expr<Type<TypeCategory::Logical, KIND>> |
| : public ExpressionBase<Type<TypeCategory::Logical, KIND>> { |
| public: |
| using Result = Type<TypeCategory::Logical, KIND>; |
| using IsFoldableTrait = std::true_type; |
| EVALUATE_UNION_CLASS_BOILERPLATE(Expr) |
| explicit Expr(const Scalar<Result> &x) : u{Constant<Result>{x}} {} |
| explicit Expr(bool x) : u{Constant<Result>{x}} {} |
| |
| private: |
| using Operations = |
| std::variant<Convert<Result, TypeCategory::Logical>, Parentheses<Result>, |
| Not<KIND>, LogicalOperation<KIND>, Relational<SomeType>>; |
| using Others = |
| std::variant<Constant<Result>, Designator<Result>, FunctionRef<Result>>; |
| |
| public: |
| common::CombineVariants<Operations, Others> u; |
| }; |
| |
| FOR_EACH_LOGICAL_KIND(extern template class Expr) |
| |
| // A polymorphic expression of known intrinsic type category, but dynamic |
| // kind, represented as a discriminated union over Expr<Type<CAT, K>> |
| // for each supported kind K in the category. |
| template<TypeCategory CAT> |
| class Expr<SomeKind<CAT>> : public ExpressionBase<SomeKind<CAT>> { |
| public: |
| using Result = SomeKind<CAT>; |
| using IsFoldableTrait = std::true_type; |
| EVALUATE_UNION_CLASS_BOILERPLATE(Expr) |
| common::MapTemplate<Expr, CategoryTypes<CAT>> u; |
| }; |
| |
| // Note that Expr<SomeDerived> does not inherit from ExpressionBase |
| // since Constant<SomeDerived> and Scalar<SomeDerived> are not defined |
| // for derived types.. |
| template<> class Expr<SomeDerived> { |
| public: |
| using Result = SomeDerived; |
| using IsFoldableTrait = std::false_type; |
| EVALUATE_UNION_CLASS_BOILERPLATE(Expr) |
| |
| std::optional<DynamicType> GetType() const; |
| int Rank() const; |
| std::ostream &Dump(std::ostream &) const; |
| |
| std::variant<Designator<Result>, FunctionRef<Result>> u; |
| }; |
| |
| // A completely generic expression, polymorphic across all of the intrinsic type |
| // categories and each of their kinds. |
| template<> class Expr<SomeType> : public ExpressionBase<SomeType> { |
| public: |
| using Result = SomeType; |
| using IsFoldableTrait = std::true_type; |
| EVALUATE_UNION_CLASS_BOILERPLATE(Expr) |
| |
| // Owning references to these generic expressions can appear in other |
| // compiler data structures (viz., the parse tree and symbol table), so |
| // its destructor is externalized to reduce redundant default instances. |
| ~Expr(); |
| |
| template<TypeCategory CAT, int KIND> |
| explicit Expr(const Expr<Type<CAT, KIND>> &x) : u{Expr<SomeKind<CAT>>{x}} {} |
| |
| template<TypeCategory CAT, int KIND> |
| explicit Expr(Expr<Type<CAT, KIND>> &&x) |
| : u{Expr<SomeKind<CAT>>{std::move(x)}} {} |
| |
| template<TypeCategory CAT, int KIND> |
| Expr &operator=(const Expr<Type<CAT, KIND>> &x) { |
| u = Expr<SomeKind<CAT>>{x}; |
| return *this; |
| } |
| |
| template<TypeCategory CAT, int KIND> |
| Expr &operator=(Expr<Type<CAT, KIND>> &&x) { |
| u = Expr<SomeKind<CAT>>{std::move(x)}; |
| return *this; |
| } |
| |
| private: |
| using Others = std::variant<BOZLiteralConstant>; |
| using Categories = common::MapTemplate<Expr, SomeCategory>; |
| |
| public: |
| common::CombineVariants<Others, Categories> u; |
| }; |
| |
| // This wrapper class is used, by means of a forward reference with |
| // OwningPointer, to implement owning pointers to analyzed expressions |
| // from parse tree nodes. |
| struct GenericExprWrapper { |
| GenericExprWrapper(Expr<SomeType> &&x) : v{std::move(x)} {} |
| Expr<SomeType> v; |
| }; |
| |
| // When an Expr holds something that is a Variable (i.e., a Designator |
| // or pointer-valued FunctionRef), return a copy of its contents in |
| // a Variable. |
| template<typename A> |
| std::optional<Variable<A>> AsVariable(const Expr<A> &expr) { |
| using Variant = decltype(Variable<A>::u); |
| return std::visit( |
| [](const auto &x) -> std::optional<Variable<A>> { |
| if constexpr (common::HasMember<std::decay_t<decltype(x)>, Variant>) { |
| return std::make_optional<Variable<A>>(x); |
| } |
| return std::nullopt; |
| }, |
| expr.u); |
| } |
| |
| // Predicate: true when an expression is a variable reference |
| template<typename A> bool IsVariable(const Expr<A> &expr) { |
| return AsVariable(expr).has_value(); |
| } |
| |
| template<TypeCategory CATEGORY> |
| bool IsVariable(const Expr<SomeKind<CATEGORY>> &expr) { |
| return std::visit([](const auto &x) { return IsVariable(x); }, expr.u); |
| } |
| |
| template<> inline bool IsVariable(const Expr<SomeDerived> &) { return true; } |
| |
| template<> inline bool IsVariable(const Expr<SomeType> &expr) { |
| return std::visit( |
| [](const auto &x) { |
| if constexpr (!std::is_same_v<BOZLiteralConstant, |
| std::decay_t<decltype(x)>>) { |
| return IsVariable(x); |
| } |
| return false; |
| }, |
| expr.u); |
| } |
| |
| FOR_EACH_CATEGORY_TYPE(extern template class Expr) |
| FOR_EACH_TYPE_AND_KIND(extern template struct ExpressionBase) |
| } |
| #endif // FORTRAN_EVALUATE_EXPRESSION_H_ |