// 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 #include #include #include 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> represents an expression whose result is of a // specific intrinsic type category and kind, e.g. Type // - Expr wraps data and procedure references that result in an // instance of a derived type // - Expr> is a union of Expr> for each // kind type parameter value K in that intrinsic type category. It represents // an expression with known category and any kind. // - Expr is a union of Expr> 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> Fold(FoldingContext &c); // std::optional> 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, SomeKind, // or SomeType. template using ResultType = typename std::decay_t::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. 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 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; public: using Derived = DERIVED; using Result = RESULT; static_assert(Result::isSpecificIntrinsicType); static constexpr std::size_t operands{sizeof...(OPERANDS)}; template using Operand = std::tuple_element_t; 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>>, std::tuple>...>>; public: CLASS_BOILERPLATE(Operation) explicit Operation(const Expr &... x) : operand_{x...} {} explicit Operation(Expr &&... x) : operand_{std::forward>(x)...} {} Derived &derived() { return *static_cast(this); } const Derived &derived() const { return *static_cast(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 Expr> &operand() { if constexpr (operands == 1) { static_assert(J == 0); return *operand_; } else { return *std::get(operand_); } } template const Expr> &operand() const { if constexpr (operands == 1) { static_assert(J == 0); return *operand_; } else { return *std::get(operand_); } } Expr> &left() { return operand<0>(); } const Expr> &left() const { return operand<0>(); } std::conditional_t<(operands > 1), Expr> &, void> right() { if constexpr (operands > 1) { return operand<1>(); } } std::conditional_t<(operands > 1), const Expr> &, void> right() const { if constexpr (operands > 1) { return operand<1>(); } } static constexpr std::optional 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> 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 struct Convert : public Operation, TO, SomeKind> { // 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; using Base = Operation; using Base::Base; static std::optional> FoldScalar( FoldingContext &, const Scalar &); std::ostream &Dump(std::ostream &) const; }; template struct Parentheses : public Operation, A, A> { using Result = A; using Operand = A; using Base = Operation; using Base::Base; static std::optional> FoldScalar( FoldingContext &, const Scalar &x) { return {x}; } }; template struct Negate : public Operation, A, A> { using Result = A; using Operand = A; using Base = Operation; using Base::Base; static std::optional> FoldScalar( FoldingContext &, const Scalar &); static std::ostream &Prefix(std::ostream &o) { return o << "(-"; } }; template struct ComplexComponent : public Operation, Type, Type> { using Result = Type; using Operand = Type; using Base = Operation; CLASS_BOILERPLATE(ComplexComponent) ComplexComponent(bool isImaginary, const Expr &x) : Base{x}, isImaginaryPart{isImaginary} {} ComplexComponent(bool isImaginary, Expr &&x) : Base{std::move(x)}, isImaginaryPart{isImaginary} {} std::optional> FoldScalar( FoldingContext &, const Scalar &) const; std::ostream &Suffix(std::ostream &o) const { return o << (isImaginaryPart ? "%IM)" : "%RE)"); } bool isImaginaryPart{true}; }; template struct Not : public Operation, Type, Type> { using Result = Type; using Operand = Result; using Base = Operation; using Base::Base; static std::optional> FoldScalar( FoldingContext &, const Scalar &); static std::ostream &Prefix(std::ostream &o) { return o << "(.NOT."; } }; // Binary operations template struct Add : public Operation, A, A, A> { using Result = A; using Operand = A; using Base = Operation; using Base::Base; static std::optional> FoldScalar( FoldingContext &, const Scalar &, const Scalar &); static std::ostream &Infix(std::ostream &o) { return o << '+'; } }; template struct Subtract : public Operation, A, A, A> { using Result = A; using Operand = A; using Base = Operation; using Base::Base; static std::optional> FoldScalar( FoldingContext &, const Scalar &, const Scalar &); static std::ostream &Infix(std::ostream &o) { return o << '-'; } }; template struct Multiply : public Operation, A, A, A> { using Result = A; using Operand = A; using Base = Operation; using Base::Base; static std::optional> FoldScalar( FoldingContext &, const Scalar &, const Scalar &); static std::ostream &Infix(std::ostream &o) { return o << '*'; } }; template struct Divide : public Operation, A, A, A> { using Result = A; using Operand = A; using Base = Operation; using Base::Base; static std::optional> FoldScalar( FoldingContext &, const Scalar &, const Scalar &); static std::ostream &Infix(std::ostream &o) { return o << '/'; } }; template struct Power : public Operation, A, A, A> { using Result = A; using Operand = A; using Base = Operation; using Base::Base; static std::optional> FoldScalar( FoldingContext &, const Scalar &, const Scalar &); static std::ostream &Infix(std::ostream &o) { return o << "**"; } }; template struct RealToIntPower : public Operation, A, A, SomeInteger> { using Base = Operation; using Result = A; using BaseOperand = A; using ExponentOperand = SomeInteger; using Base::Base; static std::optional> FoldScalar(FoldingContext &, const Scalar &, const Scalar &); static std::ostream &Infix(std::ostream &o) { return o << "**"; } }; template struct Extremum : public Operation, A, A, A> { using Result = A; using Operand = A; using Base = Operation; CLASS_BOILERPLATE(Extremum) Extremum(const Expr &x, const Expr &y, Ordering ord = Ordering::Greater) : Base{x, y}, ordering{ord} {} Extremum( Expr &&x, Expr &&y, Ordering ord = Ordering::Greater) : Base{std::move(x), std::move(y)}, ordering{ord} {} std::optional> FoldScalar( FoldingContext &, const Scalar &, const Scalar &) const; std::ostream &Prefix(std::ostream &o) const { return o << (ordering == Ordering::Less ? "MIN(" : "MAX("); } Ordering ordering{Ordering::Greater}; }; template struct ComplexConstructor : public Operation, Type, Type, Type> { using Result = Type; using Operand = Type; using Base = Operation; using Base::Base; static std::optional> FoldScalar( FoldingContext &, const Scalar &, const Scalar &); }; template struct Concat : public Operation, Type, Type, Type> { using Result = Type; using Operand = Result; using Base = Operation; using Base::Base; static std::optional> FoldScalar( FoldingContext &, const Scalar &, const Scalar &); static std::ostream &Infix(std::ostream &o) { return o << "//"; } }; ENUM_CLASS(LogicalOperator, And, Or, Eqv, Neqv) template struct LogicalOperation : public Operation, Type, Type, Type> { using Result = Type; using Operand = Result; using Base = Operation; CLASS_BOILERPLATE(LogicalOperation) LogicalOperation( LogicalOperator opr, const Expr &x, const Expr &y) : Base{x, y}, logicalOperator{opr} {} LogicalOperation(LogicalOperator opr, Expr &&x, Expr &&y) : Base{std::move(x), std::move(y)}, logicalOperator{opr} {} std::optional> FoldScalar( FoldingContext &, const Scalar &, const Scalar &) const; std::ostream &Infix(std::ostream &) const; LogicalOperator logicalOperator; }; // Per-category expression representations // Common Expr<> behaviors template struct ExpressionBase { using Result = RESULT; using Derived = Expr; Derived &derived() { return *static_cast(this); } const Derived &derived() const { return *static_cast(this); } template Derived &operator=(const A &x) { Derived &d{derived()}; d.u = x; return d; } template Derived &operator=(std::enable_if_t, A> &&x) { Derived &d{derived()}; d.u = std::move(x); return d; } std::optional GetType() const; int Rank() const; std::ostream &Dump(std::ostream &) const; std::optional> Fold(FoldingContext &c); std::optional> ScalarValue() const; }; template class Expr> : public ExpressionBase> { public: using Result = Type; using IsFoldableTrait = std::true_type; // TODO: R916 type-param-inquiry EVALUATE_UNION_CLASS_BOILERPLATE(Expr) explicit Expr(const Scalar &x) : u{Constant{x}} {} template explicit Expr(std::enable_if_t, INT> n) : u{Constant{n}} {} private: using Conversions = std::variant, Convert>; using Operations = std::variant, Negate, Add, Subtract, Multiply, Divide, Power, Extremum>; using Others = std::variant, Designator, FunctionRef>; public: common::CombineVariants u; }; template class Expr> : public ExpressionBase> { public: using Result = Type; using IsFoldableTrait = std::true_type; EVALUATE_UNION_CLASS_BOILERPLATE(Expr) explicit Expr(const Scalar &x) : u{Constant{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>; using Operations = std::variant, Parentheses, Negate, Add, Subtract, Multiply, Divide, Power, RealToIntPower, Extremum>; using Others = std::variant, Designator, FunctionRef>; public: common::CombineVariants u; }; template class Expr> : public ExpressionBase> { public: using Result = Type; using IsFoldableTrait = std::true_type; EVALUATE_UNION_CLASS_BOILERPLATE(Expr) explicit Expr(const Scalar &x) : u{Constant{x}} {} // Note that many COMPLEX operations are represented as REAL operations // over their components (viz., conversions, negation, add, and subtract). using Operations = std::variant, Multiply, Divide, Power, RealToIntPower, ComplexConstructor>; using Others = std::variant, Designator, FunctionRef>; public: common::CombineVariants 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 class Expr> : public ExpressionBase> { public: using Result = Type; using IsFoldableTrait = std::true_type; EVALUATE_UNION_CLASS_BOILERPLATE(Expr) explicit Expr(const Scalar &x) : u{Constant{x}} {} explicit Expr(Scalar &&x) : u{Constant{std::move(x)}} {} Expr LEN() const; std::variant, Designator, FunctionRef, Parentheses, Concat, Extremum> 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 struct Relational : public Operation, LogicalResult, A, A> { using Base = Operation; using typename Base::Result; using Operand = typename Base::template Operand<0>; CLASS_BOILERPLATE(Relational) Relational( RelationalOperator r, const Expr &a, const Expr &b) : Base{a, b}, opr{r} {} Relational(RelationalOperator r, Expr &&a, Expr &&b) : Base{std::move(a), std::move(b)}, opr{r} {} std::optional> FoldScalar( FoldingContext &c, const Scalar &, const Scalar &); std::ostream &Infix(std::ostream &) const; RelationalOperator opr; }; template<> class Relational { // COMPLEX data are compared piecewise. using DirectlyComparableTypes = common::CombineTuples; public: using Result = LogicalResult; EVALUATE_UNION_CLASS_BOILERPLATE(Relational) static constexpr std::optional 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 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; template class Expr> : public ExpressionBase> { public: using Result = Type; using IsFoldableTrait = std::true_type; EVALUATE_UNION_CLASS_BOILERPLATE(Expr) explicit Expr(const Scalar &x) : u{Constant{x}} {} explicit Expr(bool x) : u{Constant{x}} {} private: using Operations = std::variant, Parentheses, Not, LogicalOperation, Relational>; using Others = std::variant, Designator, FunctionRef>; public: common::CombineVariants 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> // for each supported kind K in the category. template class Expr> : public ExpressionBase> { public: using Result = SomeKind; using IsFoldableTrait = std::true_type; EVALUATE_UNION_CLASS_BOILERPLATE(Expr) common::MapTemplate> u; }; // Note that Expr does not inherit from ExpressionBase // since Constant and Scalar are not defined // for derived types.. template<> class Expr { public: using Result = SomeDerived; using IsFoldableTrait = std::false_type; EVALUATE_UNION_CLASS_BOILERPLATE(Expr) std::optional GetType() const; int Rank() const; std::ostream &Dump(std::ostream &) const; std::variant, FunctionRef> u; }; // A completely generic expression, polymorphic across all of the intrinsic type // categories and each of their kinds. template<> class Expr : public ExpressionBase { 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 explicit Expr(const Expr> &x) : u{Expr>{x}} {} template explicit Expr(Expr> &&x) : u{Expr>{std::move(x)}} {} template Expr &operator=(const Expr> &x) { u = Expr>{x}; return *this; } template Expr &operator=(Expr> &&x) { u = Expr>{std::move(x)}; return *this; } private: using Others = std::variant; using Categories = common::MapTemplate; public: common::CombineVariants 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 &&x) : v{std::move(x)} {} Expr 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 std::optional> AsVariable(const Expr &expr) { using Variant = decltype(Variable::u); return std::visit( [](const auto &x) -> std::optional> { if constexpr (common::HasMember, Variant>) { return std::make_optional>(x); } return std::nullopt; }, expr.u); } // Predicate: true when an expression is a variable reference template bool IsVariable(const Expr &expr) { return AsVariable(expr).has_value(); } template bool IsVariable(const Expr> &expr) { return std::visit([](const auto &x) { return IsVariable(x); }, expr.u); } template<> inline bool IsVariable(const Expr &) { return true; } template<> inline bool IsVariable(const Expr &expr) { return std::visit( [](const auto &x) { if constexpr (!std::is_same_v>) { return IsVariable(x); } return false; }, expr.u); } FOR_EACH_CATEGORY_TYPE(extern template class Expr) FOR_EACH_TYPE_AND_KIND(extern template struct ExpressionBase) } // namespace Fortran::evaluate #endif // FORTRAN_EVALUATE_EXPRESSION_H_