// 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. // Both deep copy and move semantics are supported for expression construction // and manipulation in place. #include "common.h" #include "type.h" #include "variable.h" #include "../lib/common/fortran.h" #include "../lib/common/idioms.h" #include "../lib/parser/char-block.h" #include "../lib/parser/message.h" #include #include #include namespace Fortran::evaluate { using common::RelationalOperator; // Expr represents an expression whose result is the Fortran type A, // which can be a specific Type, or SomeKind for a type category C, or // Expr for a wholly generic expression. Instances of Expr<> // wrap discriminated unions. template class Expr; template using ResultType = typename std::decay_t::Result; // Abstract Operation<> base class. The first type parameter is a "CRTP" // reference to the specific operation class; e.g., Add is defined with // struct Add : public Operation. template class Operation { public: using Derived = DERIVED; using Result = RESULT; using OperandTypes = std::tuple; using OperandTuple = std::tuple...>; template using Operand = std::tuple_element_t; using FoldableTrait = std::true_type; static_assert(Result::kind > 0); // Operations have specific Result types CLASS_BOILERPLATE(Operation) Operation(const Expr &... x) : operand_{OperandTuple{x...}} {} Operation(Expr &&... x) : operand_{OperandTuple{std::forward>(x)...}} {} DERIVED &derived() { return *static_cast(this); } const DERIVED &derived() const { return *static_cast(this); } static constexpr auto operands() { return std::tuple_size_v; } template Expr> &operand() { return std::get(*operand_); } template const Expr> &operand() const { return std::get(*operand_); } std::ostream &Dump(std::ostream &) const; std::optional> Fold(FoldingContext &); // TODO rank > 0 protected: // Overridable string functions for Dump() static const char *prefix() { return "("; } static const char *infix() { return ","; } static const char *suffix() { return ")"; } private: CopyableIndirection operand_; }; // Unary operations template struct Convert : public Operation, TO, FROM> { using Base = Operation, TO, FROM>; using Base::Base; using typename Base::Result; using Operand = typename Base::template Operand<0>; static std::optional> FoldScalar( FoldingContext &, const Scalar &); }; template struct Parentheses : public Operation, A, A> { using Base = Operation; using Base::Base; using typename Base::Result; using Operand = typename Base::template Operand<0>; static std::optional> FoldScalar( FoldingContext &, const Scalar &x) { return {x}; } }; template struct Negate : public Operation, A, A> { using Base = Operation; using Base::Base; using typename Base::Result; using Operand = typename Base::template Operand<0>; static std::optional> FoldScalar( FoldingContext &, const Scalar &); static const char *prefix() { return "(-"; } }; template struct ComplexComponent : public Operation, Type, Type> { using Base = Operation, Type>; using typename Base::Result; using Operand = typename Base::template Operand<0>; 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; const char *suffix() const { return isImaginaryPart ? "%IM)" : "%RE)"; } bool isImaginaryPart{true}; }; template struct Not : public Operation, Type, Type> { using Base = Operation, Type>; using typename Base::Result; using Operand = typename Base::template Operand<0>; using Base::Base; static std::optional> FoldScalar( FoldingContext &, const Scalar &); static const char *prefix() { return "(.NOT."; } }; // Binary operations template struct Add : public Operation, A, A, A> { using Base = Operation; using typename Base::Result; using Operand = typename Base::template Operand<0>; using Base::Base; static std::optional> FoldScalar( FoldingContext &, const Scalar &, const Scalar &); static constexpr const char *infix() { return "+"; } }; template struct Subtract : public Operation, A, A, A> { using Base = Operation; using typename Base::Result; using Operand = typename Base::template Operand<0>; using Base::Base; static std::optional> FoldScalar( FoldingContext &, const Scalar &, const Scalar &); static constexpr const char *infix() { return "-"; } }; template struct Multiply : public Operation, A, A, A> { using Base = Operation; using typename Base::Result; using Operand = typename Base::template Operand<0>; using Base::Base; static std::optional> FoldScalar( FoldingContext &, const Scalar &, const Scalar &); static constexpr const char *infix() { return "*"; } }; template struct Divide : public Operation, A, A, A> { using Base = Operation; using typename Base::Result; using Operand = typename Base::template Operand<0>; using Base::Base; static std::optional> FoldScalar( FoldingContext &, const Scalar &, const Scalar &); static constexpr const char *infix() { return "/"; } }; template struct Power : public Operation, A, A, A> { using Base = Operation; using typename Base::Result; using Operand = typename Base::template Operand<0>; using Base::Base; static std::optional> FoldScalar( FoldingContext &, const Scalar &, const Scalar &); static constexpr const char *infix() { return "**"; } }; template struct RealToIntPower : public Operation, A, A, B> { using Base = Operation; using typename Base::Result; using Operand = typename Base::template Operand<0>; using ExponentOperand = typename Base::template Operand<1>; using Base::Base; static std::optional> FoldScalar(FoldingContext &, const Scalar &, const Scalar &); static constexpr const char *infix() { return "**"; } }; template struct Extremum : public Operation, A, A, A> { using Base = Operation; using typename Base::Result; using Operand = typename Base::template Operand<0>; 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; const char *prefix() const { return ordering == Ordering::Less ? "MIN(" : "MAX("; } Ordering ordering{Ordering::Greater}; }; template struct ComplexConstructor : public Operation, Type, Type, Type> { using Base = Operation, Type, Type>; using typename Base::Result; using Operand = typename Base::template Operand<0>; using Base::Base; static std::optional> FoldScalar( FoldingContext &, const Scalar &, const Scalar &); }; template struct Concat : public Operation, Type, Type, Type> { using Base = Operation, Type, Type>; using typename Base::Result; using Operand = typename Base::template Operand<0>; using Base::Base; static std::optional> FoldScalar( FoldingContext &, const Scalar &, const Scalar &); static constexpr const char *infix() { return "//"; } }; ENUM_CLASS(LogicalOperator, And, Or, Eqv, Neqv) template struct LogicalOperation : public Operation, Type, Type, Type> { using Base = Operation, Type, Type>; using typename Base::Result; using Operand = typename Base::template Operand<0>; CLASS_BOILERPLATE(LogicalOperation) LogicalOperation( const Expr &x, const Expr &y, LogicalOperator opr) : Base{x, y}, logicalOperator{opr} {} LogicalOperation(Expr &&x, Expr &&y, LogicalOperator opr) : Base{std::move(x), std::move(y)}, logicalOperator{opr} {} std::optional> FoldScalar( FoldingContext &, const Scalar &, const Scalar &) const; const char *infix() const; LogicalOperator logicalOperator; }; // Per-category expressions template class Expr> { public: using Result = Type; using FoldableTrait = std::true_type; // TODO: R916 type-param-inquiry CLASS_BOILERPLATE(Expr) Expr(const Scalar &x) : u_{x} {} Expr(std::int64_t n) : u_{Scalar{n}} {} Expr(std::uint64_t n) : u_{Scalar{n}} {} Expr(int n) : u_{Scalar{n}} {} Expr(const Expr &x) : u_{Convert{x}} {} Expr(Expr &&x) : u_{Convert{std::move(x)}} {} template Expr(const Expr> &x) : u_{Convert{Expr{x}}} {} template Expr(Expr> &&x) : u_{Convert{Expr{std::move(x)}}} {} Expr(const Expr &x) : u_{Convert{x}} {} Expr(Expr &&x) : u_{Convert{std::move(x)}} {} template Expr(const Expr> &x) : u_{Convert{Expr{x}}} {} template Expr(Expr> &&x) : u_{Convert{Expr{std::move(x)}}} {} template Expr(const A &x) : u_{x} {} template Expr(std::enable_if_t, A> &&x) : u_(std::move(x)) {} template Expr(CopyableIndirection &&x) : u_{std::move(x)} {} std::optional> ScalarValue() const { // TODO: Also succeed when parenthesized constant return common::GetIf>(u_); } std::ostream &Dump(std::ostream &) const; std::optional> Fold(FoldingContext &c); int Rank() const { return 1; } // TODO private: std::variant, CopyableIndirection, CopyableIndirection, Convert, Convert, Parentheses, Negate, Add, Subtract, Multiply, Divide, Power, Extremum> u_; }; template class Expr> { public: using Result = Type; using FoldableTrait = std::true_type; // 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. CLASS_BOILERPLATE(Expr) Expr(const Scalar &x) : u_{x} {} Expr(const Expr &x) : u_{Convert{x}} {} Expr(Expr &&x) : u_{Convert{std::move(x)}} {} template Expr(const Expr> &x) : u_{Convert{Expr{x}}} {} template Expr(Expr> &&x) : u_{Convert{Expr{std::move(x)}}} {} Expr(const Expr &x) : u_{Convert{x}} {} Expr(Expr &&x) : u_{Convert{std::move(x)}} {} template Expr(const Expr> &x) : u_{Convert{Expr{x}}} {} template Expr(Expr> &&x) : u_{Convert{Expr{std::move(x)}}} {} template Expr(const A &x) : u_{x} {} template Expr(std::enable_if_t, A> &&x) : u_{std::move(x)} {} template Expr(CopyableIndirection &&x) : u_{std::move(x)} {} std::optional> ScalarValue() const { // TODO: parenthesized constants too return common::GetIf>(u_); } std::ostream &Dump(std::ostream &) const; std::optional> Fold(FoldingContext &c); int Rank() const { return 1; } // TODO private: std::variant, CopyableIndirection, CopyableIndirection, CopyableIndirection, Convert, Convert, ComplexComponent, Parentheses, Negate, Add, Subtract, Multiply, Divide, Power, RealToIntPower, Extremum> u_; }; template class Expr> { public: using Result = Type; using FoldableTrait = std::true_type; CLASS_BOILERPLATE(Expr) Expr(const Scalar &x) : u_{x} {} template Expr(const A &x) : u_{x} {} template Expr(std::enable_if_t, A> &&x) : u_{std::move(x)} {} template Expr(CopyableIndirection &&x) : u_{std::move(x)} {} std::optional> ScalarValue() const { // TODO: parenthesized constants too return common::GetIf>(u_); } std::ostream &Dump(std::ostream &) const; std::optional> Fold(FoldingContext &c); int Rank() const { return 1; } // TODO private: std::variant, CopyableIndirection, CopyableIndirection, Parentheses, Negate, Add, Subtract, Multiply, Divide, Power, RealToIntPower, ComplexConstructor> u_; }; extern template class Expr>; extern template class Expr>; extern template class Expr>; extern template class Expr>; extern template class Expr>; extern template class Expr>; extern template class Expr>; extern template class Expr>; extern template class Expr>; extern template class Expr>; extern template class Expr>; extern template class Expr>; extern template class Expr>; extern template class Expr>; extern template class Expr>; template class Expr> { public: using Result = Type; using FoldableTrait = std::true_type; CLASS_BOILERPLATE(Expr) Expr(const Scalar &x) : u_{x} {} Expr(Scalar &&x) : u_{std::move(x)} {} template Expr(const A &x) : u_{x} {} template Expr(std::enable_if_t, A> &&x) : u_{std::move(x)} {} template Expr(CopyableIndirection &&x) : u_{std::move(x)} {} std::optional> ScalarValue() const { // TODO: parenthesized constants too return common::GetIf>(u_); } std::ostream &Dump(std::ostream &) const; std::optional> Fold(FoldingContext &c); int Rank() const { return 1; } // TODO Expr LEN() const; private: std::variant, CopyableIndirection, CopyableIndirection, CopyableIndirection, // TODO Parentheses, Concat, Extremum> u_; }; // The Relation 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 // undergoing the same operand conversions that occur with the addition // intrinsic operator first. Character relations must have the same kind. // There are no relations between logicals. 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::string infix() const; RelationalOperator opr; }; // A generic relation between two operands of the same kind in some intrinsic // type category (except LOGICAL). struct AnyRelational { using Result = LogicalResult; template AnyRelational(const A &x) : u{x} {} template AnyRelational(std::enable_if_t, A> &&x) : u{std::move(x)} {} std::optional> Fold(FoldingContext &); std::ostream &Dump(std::ostream &) const; common::MapTemplate u; }; template class Expr> { public: using Result = Type; using FoldableTrait = std::true_type; CLASS_BOILERPLATE(Expr) Expr(const Scalar &x) : u_{x} {} Expr(bool x) : u_{Scalar{x}} {} template Expr(const Relational> &x) : u_{Relational>{x}} {} template Expr(Relational> &&x) : u_{Relational>{std::move(x)}} {} template Expr(const A &x) : u_(x) {} template Expr(std::enable_if_t, A> &&x) : u_{std::move(x)} {} template Expr(CopyableIndirection &&x) : u_{std::move(x)} {} std::optional> ScalarValue() const { // TODO: parenthesized constants too return common::GetIf>(u_); } std::ostream &Dump(std::ostream &) const; std::optional> Fold(FoldingContext &c); int Rank() const { return 1; } // TODO private: std::variant, CopyableIndirection, CopyableIndirection, // TODO Parentheses, Not, LogicalOperation, AnyRelational> u_; }; // Dynamically polymorphic expressions that can hold any supported kind // of a specific intrinsic type category. template class Expr> { public: using Result = SomeKind; using FoldableTrait = std::true_type; static constexpr TypeCategory category{CAT}; CLASS_BOILERPLATE(Expr) template using KindExpr = Expr>; using Variant = CategoryUnion; Expr(Variant &&x) : u{std::move(x)} {} template Expr(const KindExpr &x) : u{x} {} template Expr(KindExpr &&x) : u{std::move(x)} {} std::optional> ScalarValue() const; std::ostream &Dump(std::ostream &) const; std::optional> Fold(FoldingContext &); int Rank() const; template static std::optional ForceKind(int kind, A &&x) { if (std::optional result{ Variant::template ForceKind(kind, std::move(x))}) { return {Expr{std::move(*result)}}; } return std::nullopt; } template static void AtKind(A &x, int kind) { Variant::template AtKind(x, kind); } Variant u; }; // BOZ literal constants need to be wide enough to hold an integer or real // value of any supported kind. They also need to be distinguishable from // other integer constants, since they are permitted to be used in only a // few situations. using BOZLiteralConstant = value::Integer<128>; // A completely generic expression, polymorphic across the intrinsic type // categories and each of their kinds. template<> class Expr { public: using Result = SomeType; using FoldableTrait = std::true_type; CLASS_BOILERPLATE(Expr) template Expr(const A &x) : u{x} {} template Expr(std::enable_if_t, A> &&x) : u{std::move(x)} {} template Expr(const Expr> &x) : u{Expr>{x}} {} template Expr(Expr> &&x) : u{Expr>{std::move(x)}} {} std::optional> ScalarValue() const; std::ostream &Dump(std::ostream &) const; std::optional> Fold(FoldingContext &); int Rank() const; std::variant, Expr, Expr, Expr, Expr, BOZLiteralConstant> u; }; extern template class Expr>; // TODO others extern template struct Relational>; extern template struct Relational>; extern template struct Relational>; extern template struct Relational>; extern template struct Relational>; extern template struct Relational>; extern template struct Relational>; extern template struct Relational>; extern template struct Relational>; extern template struct Relational>; extern template struct Relational>; extern template struct Relational>; extern template struct Relational>; extern template struct Relational>; extern template struct Relational>; extern template struct Relational>; // TODO // more extern template class Expr>; extern template class Expr>; extern template class Expr>; extern template class Expr>; extern template class Expr; extern template class Expr; extern template class Expr; extern template class Expr; extern template class Expr; extern template class Expr; } // namespace Fortran::evaluate #endif // FORTRAN_EVALUATE_EXPRESSION_H_