// Copyright (c) 2018-2019, 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. Expressions may be compared // for equality. #include "common.h" #include "constant.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 #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. // // 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. (Exception: BOZ literal constants in generic Expr.) template using ResultType = typename std::decay_t::Result; // Common Expr<> behaviors: every Expr derives from ExpressionBase. template class ExpressionBase { public: using Result = RESULT; private: using Derived = Expr; #if defined(__APPLE__) && defined(__GNUC__) Derived &derived(); const Derived &derived() const; #else Derived &derived() { return *static_cast(this); } const Derived &derived() const { return *static_cast(this); } #endif public: 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 &AsFortran(std::ostream &) const; static Derived Rewrite(FoldingContext &, Derived &&); }; // 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 final 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(IsSpecificIntrinsicType); static constexpr std::size_t operands{sizeof...(OPERANDS)}; template using Operand = std::tuple_element_t; // 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_.value(); } else { return std::get(operand_).value(); } } template const Expr> &operand() const { if constexpr (operands == 1) { static_assert(J == 0); return operand_.value(); } else { return std::get(operand_).value(); } } 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) { return std::max(rank, right().Rank()); } else { return rank; } } bool operator==(const Operation &that) const { return operand_ == that.operand_; } std::ostream &AsFortran(std::ostream &) const; protected: // Overridable functions for AsFortran() 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 apart from // assignments, and in those the data must be convertible to/from 7-bit ASCII. // 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::Character && FROMCAT == TypeCategory::Character) || (TO::category == TypeCategory::Logical && FROMCAT == TypeCategory::Logical)); using Result = TO; using Operand = SomeKind; using Base = Operation; using Base::Base; std::ostream &AsFortran(std::ostream &) const; }; template struct Parentheses : public Operation, A, A> { using Result = A; using Operand = A; using Base = Operation; using Base::Base; }; template struct Negate : public Operation, A, A> { using Result = A; using Operand = A; using Base = Operation; using Base::Base; 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::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::ostream &Prefix(std::ostream &o) { return o << "(.NOT."; } }; // Character lengths are determined by context in Fortran and do not // have explicit syntax for changing them. Expressions represent // changes of length (e.g., for assignments and structure constructors) // with this operation. template struct SetLength : public Operation, Type, Type, SubscriptInteger> { using Result = Type; using CharacterOperand = Result; using LengthOperand = SubscriptInteger; using Base = Operation; using Base::Base; static std::ostream &Prefix(std::ostream &o) { return o << "%SET_LENGTH("; } }; // Binary operations template struct Add : public Operation, A, A, A> { using Result = A; using Operand = A; using Base = Operation; using Base::Base; 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::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::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::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::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::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::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; }; template struct Concat : public Operation, Type, Type, Type> { using Result = Type; using Operand = Result; using Base = Operation; using Base::Base; 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::ostream &Infix(std::ostream &) const; LogicalOperator logicalOperator; }; // Array constructors template class ArrayConstructorValues; struct ImpliedDoIndex { using Result = SubscriptInteger; bool operator==(const ImpliedDoIndex &) const; static constexpr int Rank() { return 0; } parser::CharBlock name; // nested implied DOs must use distinct names }; template class ImpliedDo { public: using Result = RESULT; using Index = ResultType; ImpliedDo(parser::CharBlock name, Expr &&lower, Expr &&upper, Expr &&stride, ArrayConstructorValues &&values) : name_{name}, lower_{std::move(lower)}, upper_{std::move(upper)}, stride_{std::move(stride)}, values_{std::move(values)} {} DEFAULT_CONSTRUCTORS_AND_ASSIGNMENTS(ImpliedDo) bool operator==(const ImpliedDo &) const; parser::CharBlock name() const { return name_; } Expr &lower() { return lower_.value(); } const Expr &lower() const { return lower_.value(); } Expr &upper() { return upper_.value(); } const Expr &upper() const { return upper_.value(); } Expr &stride() { return stride_.value(); } const Expr &stride() const { return stride_.value(); } ArrayConstructorValues &values() { return values_.value(); } const ArrayConstructorValues &values() const { return values_.value(); } private: parser::CharBlock name_; common::CopyableIndirection> lower_, upper_, stride_; common::CopyableIndirection> values_; }; template struct ArrayConstructorValue { using Result = RESULT; EVALUATE_UNION_CLASS_BOILERPLATE(ArrayConstructorValue) std::variant>, ImpliedDo> u; }; template class ArrayConstructorValues { public: using Result = RESULT; using Values = std::vector>; DEFAULT_CONSTRUCTORS_AND_ASSIGNMENTS(ArrayConstructorValues) ArrayConstructorValues() {} bool operator==(const ArrayConstructorValues &) const; static constexpr int Rank() { return 1; } template void Push(A &&x) { values_.emplace_back(std::move(x)); } Values &values() { return values_; } const Values &values() const { return values_; } protected: Values values_; }; template class ArrayConstructor : public ArrayConstructorValues { public: using Result = RESULT; using Base = ArrayConstructorValues; DEFAULT_CONSTRUCTORS_AND_ASSIGNMENTS(ArrayConstructor) explicit ArrayConstructor(Base &&values) : Base{std::move(values)} {} static constexpr DynamicType GetType() { return Result::GetType(); } std::ostream &AsFortran(std::ostream &) const; }; template class ArrayConstructor> : public ArrayConstructorValues> { public: using Result = Type; using Base = ArrayConstructorValues; CLASS_BOILERPLATE(ArrayConstructor) ArrayConstructor(Expr &&len, Base &&v) : Base{std::move(v)}, length_{std::move(len)} {} bool operator==(const ArrayConstructor &) const; static constexpr DynamicType GetType() { return Result::GetType(); } std::ostream &AsFortran(std::ostream &) const; const Expr &LEN() const { return length_.value(); } private: common::CopyableIndirection> length_; }; template<> class ArrayConstructor : public ArrayConstructorValues { public: using Result = SomeDerived; using Base = ArrayConstructorValues; CLASS_BOILERPLATE(ArrayConstructor) ArrayConstructor(const semantics::DerivedTypeSpec &spec, Base &&v) : Base{std::move(v)}, derivedTypeSpec_{&spec} {} bool operator==(const ArrayConstructor &) const; const semantics::DerivedTypeSpec &derivedTypeSpec() const { return *derivedTypeSpec_; } DynamicType GetType() const { return DynamicType{derivedTypeSpec()}; } std::ostream &AsFortran(std::ostream &) const; private: const semantics::DerivedTypeSpec *derivedTypeSpec_; }; // Expression representations for each type category. template class Expr> : public ExpressionBase> { public: using Result = Type; 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::tuple, Convert>; using Operations = std::tuple, Negate, Add, Subtract, Multiply, Divide, Power, Extremum>; using Indices = std::conditional_t, std::tuple<>>; using Others = std::tuple, ArrayConstructor, TypeParamInquiry, Designator, FunctionRef>; public: common::TupleToVariant< common::CombineTuples> u; }; template class Expr> : public ExpressionBase> { public: using Result = 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, ArrayConstructor, Designator, FunctionRef>; public: common::CombineVariants u; }; template class Expr> : public ExpressionBase> { public: using Result = 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, ArrayConstructor, 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; 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, ArrayConstructor, Designator, FunctionRef, Parentheses, Convert, Concat, Extremum, SetLength> 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 Result = LogicalResult; using Base = Operation; using Operand = typename Base::template Operand<0>; static_assert(Operand::category == TypeCategory::Integer || Operand::category == TypeCategory::Real || Operand::category == TypeCategory::Character); 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::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 DynamicType GetType() { return Result::GetType(); } int Rank() const { return std::visit([](const auto &x) { return x.Rank(); }, u); } std::ostream &AsFortran(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; // Logical expressions of a kind bigger than LogicalResult // do not include Relational<> operations as possibilities, // since the results of Relationals are always LogicalResult // (kind=1). template class Expr> : public ExpressionBase> { public: using Result = 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::tuple, Parentheses, Not, LogicalOperation>; using Relations = std::conditional_t>, std::tuple<>>; using Others = std::tuple, ArrayConstructor, Designator, FunctionRef>; public: common::TupleToVariant> u; }; FOR_EACH_LOGICAL_KIND(extern template class Expr) // StructureConstructor pairs a StructureConstructorValues instance // (a map associating symbols with expressions) with a derived type // specification. There are two other similar classes: // - ArrayConstructor comprises a derived type spec & // zero or more instances of Expr; it has rank 1 // but not (in the most general case) a known shape. // - Constant comprises a derived type spec, zero or more // homogeneous instances of StructureConstructorValues whose type // parameters and component expressions are all constant, and a // known shape (possibly scalar). // StructureConstructor represents a scalar value of derived type that // is not necessarily a constant. It is used only as an Expr // alternative and as the type Scalar (with an assumption // of constant component value expressions). class StructureConstructor { public: explicit StructureConstructor(const semantics::DerivedTypeSpec &spec) : derivedTypeSpec_{&spec} {} StructureConstructor( const semantics::DerivedTypeSpec &, const StructureConstructorValues &); StructureConstructor( const semantics::DerivedTypeSpec &, StructureConstructorValues &&); CLASS_BOILERPLATE(StructureConstructor) const semantics::DerivedTypeSpec &derivedTypeSpec() const { return *derivedTypeSpec_; } StructureConstructorValues &values() { return values_; } const StructureConstructorValues &values() const { return values_; } bool operator==(const StructureConstructor &) const; StructureConstructor &Add(const semantics::Symbol &, Expr &&); int Rank() const { return 0; } DynamicType GetType() const; std::ostream &AsFortran(std::ostream &) const; private: const semantics::DerivedTypeSpec *derivedTypeSpec_; StructureConstructorValues values_; }; // An expression whose result has a derived type. template<> class Expr : public ExpressionBase { public: using Result = SomeDerived; EVALUATE_UNION_CLASS_BOILERPLATE(Expr) std::variant, ArrayConstructor, StructureConstructor, Designator, FunctionRef> u; }; // 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; EVALUATE_UNION_CLASS_BOILERPLATE(Expr) int GetKind() const; common::MapTemplate> u; }; template<> class Expr : public ExpressionBase { public: using Result = SomeCharacter; EVALUATE_UNION_CLASS_BOILERPLATE(Expr) int GetKind() const; Expr LEN() const; common::MapTemplate> u; }; // 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; // Null pointers without MOLD= arguments are typed by context. struct NullPointer { constexpr bool operator==(const NullPointer &) const { return true; } constexpr int Rank() const { return 0; } }; // 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; 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 // an owning pointer, to cache analyzed expressions in parse tree nodes. struct GenericExprWrapper { GenericExprWrapper(Expr &&x) : v{std::move(x)} {} ~GenericExprWrapper(); bool operator==(const GenericExprWrapper &) const; Expr v; }; std::ostream &DerivedTypeSpecAsFortran( std::ostream &, const semantics::DerivedTypeSpec &); FOR_EACH_CATEGORY_TYPE(extern template class Expr) FOR_EACH_TYPE_AND_KIND(extern template class ExpressionBase) FOR_EACH_INTRINSIC_KIND(extern template class ArrayConstructorValues) FOR_EACH_INTRINSIC_KIND(extern template class ArrayConstructor) } #endif // FORTRAN_EVALUATE_EXPRESSION_H_