// 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/idioms.h" #include "../lib/parser/char-block.h" #include "../lib/parser/message.h" #include #include namespace Fortran::evaluate { template class Expr; // An expression whose result is within one particular type category and // of any supported kind. using SomeKindIntegerExpr = Expr>; using SomeKindRealExpr = Expr>; using SomeKindComplexExpr = Expr>; using SomeKindCharacterExpr = Expr>; using SomeKindLogicalExpr = Expr>; // Helper base classes for packaging subexpressions. template class Unary { public: using Result = RESULT; using Operand = A; using FoldableTrait = std::true_type; CLASS_BOILERPLATE(Unary) Unary(const Expr &a) : operand_{a} {} Unary(Expr &&a) : operand_{std::move(a)} {} Unary(CopyableIndirection> &&a) : operand_{std::move(a)} {} const Expr &operand() const { return *operand_; } Expr &operand() { return *operand_; } std::ostream &Dump(std::ostream &, const char *opr) const; int Rank() const { return operand_.Rank(); } std::optional> Fold(FoldingContext &); // TODO: array result private: CopyableIndirection> operand_; }; template class Binary { public: using Result = RESULT; using Left = A; using Right = B; using FoldableTrait = std::true_type; CLASS_BOILERPLATE(Binary) Binary(const Expr &a, const Expr &b) : left_{a}, right_{b} {} Binary(Expr &&a, Expr &&b) : left_{std::move(a)}, right_{std::move(b)} {} Binary(CopyableIndirection> &&a, CopyableIndirection> &&b) : left_{std::move(a)}, right_{std::move(b)} {} const Expr &left() const { return *left_; } Expr &left() { return *left_; } const Expr &right() const { return *right_; } Expr &right() { return *right_; } std::ostream &Dump( std::ostream &, const char *opr, const char *before = "(") const; int Rank() const; std::optional> Fold(FoldingContext &); private: CopyableIndirection> left_; CopyableIndirection> right_; }; // Per-category expressions template class Expr> { public: using Result = Type; using FoldableTrait = std::true_type; struct ConvertInteger : public Unary> { using Unary>::Unary; static std::optional> FoldScalar( FoldingContext &, const SomeKindScalar &); }; struct ConvertReal : public Unary> { using Unary>::Unary; static std::optional> FoldScalar( FoldingContext &, const SomeKindScalar &); }; template using Un = Unary; template using Bin = Binary; struct Parentheses : public Un { using Un::Un; static std::optional> FoldScalar( FoldingContext &, const Scalar &x) { return {x}; } }; struct Negate : public Un { using Un::Un; static std::optional> FoldScalar( FoldingContext &, const Scalar &); }; struct Add : public Bin { using Bin::Bin; static std::optional> FoldScalar( FoldingContext &, const Scalar &, const Scalar &); }; struct Subtract : public Bin { using Bin::Bin; static std::optional> FoldScalar( FoldingContext &, const Scalar &, const Scalar &); }; struct Multiply : public Bin { using Bin::Bin; static std::optional> FoldScalar( FoldingContext &, const Scalar &, const Scalar &); }; struct Divide : public Bin { using Bin::Bin; static std::optional> FoldScalar( FoldingContext &, const Scalar &, const Scalar &); }; struct Power : public Bin { using Bin::Bin; static std::optional> FoldScalar( FoldingContext &, const Scalar &, const Scalar &); }; struct Max : public Bin { using Bin::Bin; static std::optional> FoldScalar( FoldingContext &, const Scalar &, const Scalar &); }; struct Min : public Bin { using Bin::Bin; static std::optional> FoldScalar( FoldingContext &, const Scalar &, const Scalar &); }; // 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 SomeKindIntegerExpr &x) : u_{ConvertInteger{x}} {} Expr(SomeKindIntegerExpr &&x) : u_{ConvertInteger{std::move(x)}} {} template Expr(const Expr> &x) : u_{ConvertInteger{SomeKindIntegerExpr{x}}} {} template Expr(Expr> &&x) : u_{ConvertInteger{SomeKindIntegerExpr{std::move(x)}}} {} Expr(const SomeKindRealExpr &x) : u_{ConvertReal{x}} {} Expr(SomeKindRealExpr &&x) : u_{ConvertReal{std::move(x)}} {} template Expr(const Expr> &x) : u_{ConvertReal{SomeKindRealExpr{x}}} {} template Expr(Expr> &&x) : u_{ConvertReal{SomeKindRealExpr{std::move(x)}}} {} template Expr(const A &x) : u_{x} {} template Expr(std::enable_if_t && (std::is_base_of_v || std::is_base_of_v), A> &&x) : u_(std::move(x)) {} template Expr(CopyableIndirection &&x) : u_{std::move(x)} {} std::optional> ScalarValue() const { return common::GetIf>(u_); } std::optional> Fold(FoldingContext &c); private: std::variant, CopyableIndirection, CopyableIndirection, ConvertInteger, ConvertReal, Parentheses, Negate, Add, Subtract, Multiply, Divide, Power, Max, Min> 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. struct ConvertInteger : public Unary> { using Unary>::Unary; static std::optional> FoldScalar( FoldingContext &, const SomeKindScalar &); }; struct ConvertReal : public Unary> { using Unary>::Unary; static std::optional> FoldScalar( FoldingContext &, const SomeKindScalar &); }; template using Un = Unary; template using Bin = Binary; struct Parentheses : public Un { using Un::Un; static std::optional> FoldScalar( FoldingContext &, const Scalar &x) { return {x}; } }; struct Negate : public Un { using Un::Un; static std::optional> FoldScalar( FoldingContext &, const Scalar &); }; struct Add : public Bin { using Bin::Bin; static std::optional> FoldScalar( FoldingContext &, const Scalar &, const Scalar &); }; struct Subtract : public Bin { using Bin::Bin; static std::optional> FoldScalar( FoldingContext &, const Scalar &, const Scalar &); }; struct Multiply : public Bin { using Bin::Bin; static std::optional> FoldScalar( FoldingContext &, const Scalar &, const Scalar &); }; struct Divide : public Bin { using Bin::Bin; static std::optional> FoldScalar( FoldingContext &, const Scalar &, const Scalar &); }; struct Power : public Bin { using Bin::Bin; static std::optional> FoldScalar( FoldingContext &, const Scalar &, const Scalar &); }; struct IntPower : public Binary> { using Binary>::Binary; static std::optional> FoldScalar(FoldingContext &, const Scalar &, const SomeKindScalar &); }; struct Max : public Bin { using Bin::Bin; static std::optional> FoldScalar( FoldingContext &, const Scalar &, const Scalar &); }; struct Min : public Bin { using Bin::Bin; static std::optional> FoldScalar( FoldingContext &, const Scalar &, const Scalar &); }; template using ComplexUn = Unary>; struct RealPart : public ComplexUn { using ComplexUn::ComplexUn; static std::optional> FoldScalar(FoldingContext &, const Scalar> &); }; struct AIMAG : public ComplexUn { using ComplexUn::ComplexUn; static std::optional> FoldScalar(FoldingContext &, const Scalar> &); }; CLASS_BOILERPLATE(Expr) Expr(const Scalar &x) : u_{x} {} Expr(const SomeKindIntegerExpr &x) : u_{ConvertInteger{x}} {} Expr(SomeKindIntegerExpr &&x) : u_{ConvertInteger{std::move(x)}} {} template Expr(const Expr> &x) : u_{ConvertInteger{SomeKindIntegerExpr{x}}} {} template Expr(Expr> &&x) : u_{ConvertInteger{SomeKindIntegerExpr{std::move(x)}}} {} Expr(const SomeKindRealExpr &x) : u_{ConvertReal{x}} {} Expr(SomeKindRealExpr &&x) : u_{ConvertReal{std::move(x)}} {} template Expr(const Expr> &x) : u_{ConvertReal{SomeKindRealExpr{x}}} {} template Expr(Expr> &&x) : u_{ConvertReal{SomeKindRealExpr{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 { return common::GetIf>(u_); } std::optional> Fold(FoldingContext &c); private: std::variant, CopyableIndirection, CopyableIndirection, CopyableIndirection, ConvertInteger, ConvertReal, Parentheses, Negate, Add, Subtract, Multiply, Divide, Power, IntPower, Max, Min, RealPart, AIMAG> u_; }; template class Expr> { public: using Result = Type; using FoldableTrait = std::true_type; template using Un = Unary; template using Bin = Binary; struct Parentheses : public Un { using Un::Un; static std::optional> FoldScalar( FoldingContext &, const Scalar &x) { return {x}; } }; struct Negate : public Un { using Un::Un; static std::optional> FoldScalar( FoldingContext &, const Scalar &); }; struct Add : public Bin { using Bin::Bin; static std::optional> FoldScalar( FoldingContext &, const Scalar &, const Scalar &); }; struct Subtract : public Bin { using Bin::Bin; static std::optional> FoldScalar( FoldingContext &, const Scalar &, const Scalar &); }; struct Multiply : public Bin { using Bin::Bin; static std::optional> FoldScalar( FoldingContext &, const Scalar &, const Scalar &); }; struct Divide : public Bin { using Bin::Bin; static std::optional> FoldScalar( FoldingContext &, const Scalar &, const Scalar &); }; struct Power : public Bin { using Bin::Bin; static std::optional> FoldScalar( FoldingContext &, const Scalar &, const Scalar &); }; struct IntPower : public Binary> { using Binary>::Binary; static std::optional> FoldScalar(FoldingContext &, const Scalar &, const SomeKindScalar &); }; struct CMPLX : public Binary> { using Binary>::Binary; static std::optional> FoldScalar(FoldingContext &, const Scalar> &, const Scalar> &); }; 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 { return common::GetIf>(u_); } std::optional> Fold(FoldingContext &c); private: std::variant, CopyableIndirection, CopyableIndirection, Parentheses, Negate, Add, Subtract, Multiply, Divide, Power, IntPower, CMPLX> u_; }; template class Expr> { public: using Result = Type; using FoldableTrait = std::true_type; template using Bin = Binary; struct Concat : public Bin { using Bin::Bin; static std::optional> FoldScalar( FoldingContext &, const Scalar &, const Scalar &); }; struct Max : public Bin { using Bin::Bin; static std::optional> FoldScalar( FoldingContext &, const Scalar &, const Scalar &); }; struct Min : public Bin { using Bin::Bin; static std::optional> FoldScalar( FoldingContext &, const Scalar &, const Scalar &); }; 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 { return common::GetIf>(u_); } std::optional> Fold(FoldingContext &c); Expr LEN() const; private: std::variant, CopyableIndirection, CopyableIndirection, CopyableIndirection, Concat, Max, Min> u_; }; // The Comparison class template is a helper for constructing logical // expressions with polymorphism over the cross product of the possible // categories and kinds of comparable operands. ENUM_CLASS(RelationalOperator, LT, LE, EQ, NE, GE, GT) template struct Comparison : public Binary, Type, A> { using Result = Type; using Operand = A; using Base = Binary; CLASS_BOILERPLATE(Comparison) Comparison( RelationalOperator r, const Expr &a, const Expr &b) : Base{a, b}, opr{r} {} Comparison(RelationalOperator r, Expr &&a, Expr &&b) : Base{std::move(a), std::move(b)}, opr{r} {} std::optional> FoldScalar( FoldingContext &c, const Scalar &, const Scalar &); RelationalOperator opr; }; extern template struct Comparison>; extern template struct Comparison>; extern template struct Comparison>; extern template struct Comparison>; extern template struct Comparison>; extern template struct Comparison>; extern template struct Comparison>; extern template struct Comparison>; extern template struct Comparison>; extern template struct Comparison>; extern template struct Comparison>; extern template struct Comparison>; extern template struct Comparison>; extern template struct Comparison>; extern template struct Comparison>; extern template struct Comparison>; // Dynamically polymorphic comparisons whose operands are expressions of // the same supported kind of a particular type category. template struct CategoryComparison { using Result = Type; CLASS_BOILERPLATE(CategoryComparison) template using KindComparison = Comparison>; template CategoryComparison(const KindComparison &x) : u{x} {} template CategoryComparison(KindComparison &&x) : u{std::move(x)} {} std::optional> Fold(FoldingContext &c); typename KindsVariant::type u; }; template class Expr> { public: using Result = Type; using FoldableTrait = std::true_type; struct Not : Unary { using Unary::Unary; static std::optional> FoldScalar( FoldingContext &, const Scalar &); }; template using Bin = Binary; struct And : public Bin { using Bin::Bin; static std::optional> FoldScalar( FoldingContext &, const Scalar &, const Scalar &); }; struct Or : public Bin { using Bin::Bin; static std::optional> FoldScalar( FoldingContext &, const Scalar &, const Scalar &); }; struct Eqv : public Bin { using Bin::Bin; static std::optional> FoldScalar( FoldingContext &, const Scalar &, const Scalar &); }; struct Neqv : public Bin { using Bin::Bin; static std::optional> FoldScalar( FoldingContext &, const Scalar &, const Scalar &); }; CLASS_BOILERPLATE(Expr) Expr(const Scalar &x) : u_{x} {} Expr(bool x) : u_{Scalar{x}} {} template Expr(const Comparison> &x) : u_{CategoryComparison{x}} {} template Expr(Comparison> &&x) : u_{CategoryComparison{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 { return common::GetIf>(u_); } std::optional> Fold(FoldingContext &c); private: std::variant, CopyableIndirection, CopyableIndirection, Not, And, Or, Eqv, Neqv, CategoryComparison, CategoryComparison, CategoryComparison, CategoryComparison> 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>; extern template class Expr>; extern template class Expr>; extern template class Expr>; extern template class Expr>; extern template class Expr>; // 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; CLASS_BOILERPLATE(Expr) template using KindExpr = Expr>; template Expr(const KindExpr &x) : u{x} {} template Expr(KindExpr &&x) : u{std::move(x)} {} std::optional> ScalarValue() const; std::optional> Fold(FoldingContext &); typename KindsVariant::type u; }; extern template class Expr>; extern template class Expr>; extern template class Expr>; extern template class Expr>; extern template class Expr>; // 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::optional> Fold(FoldingContext &); int Rank() const { return 1; } // TODO std::variant u; }; extern template class Expr; using GenericExpr = Expr; // TODO: delete name? template using ResultType = typename std::decay_t::Result; // Convenience functions and operator overloadings for expression construction. // These definitions are created with temporary helper macros to reduce // C++ boilerplate. All combinations of lvalue and rvalue references are // allowed for operands. #define UNARY(FUNC, CONSTR) \ template A FUNC(const A &x) { return {typename A::CONSTR{x}}; } UNARY(Parentheses, Parentheses) UNARY(operator-, Negate) #undef UNARY #define BINARY(FUNC, CONSTR) \ template A FUNC(const A &x, const A &y) { \ return {typename A::CONSTR{x, y}}; \ } \ template \ std::enable_if_t, A> FUNC(const A &x, A &&y) { \ return {typename A::CONSTR{A{x}, std::move(y)}}; \ } \ template \ std::enable_if_t, A> FUNC(A &&x, const A &y) { \ return {typename A::CONSTR{std::move(x), A{y}}}; \ } \ template \ std::enable_if_t, A> FUNC(A &&x, A &&y) { \ return {typename A::CONSTR{std::move(x), std::move(y)}}; \ } BINARY(operator+, Add) BINARY(operator-, Subtract) BINARY(operator*, Multiply) BINARY(operator/, Divide) BINARY(Power, Power) #undef BINARY #define BINARY(FUNC, OP) \ template Expr FUNC(const A &x, const A &y) { \ return {Comparison>{OP, x, y}}; \ } \ template \ std::enable_if_t, Expr> FUNC( \ const A &x, A &&y) { \ return {Comparison>{OP, x, std::move(y)}}; \ } \ template \ std::enable_if_t, Expr> FUNC( \ A &&x, const A &y) { \ return {Comparison>{OP, std::move(x), y}}; \ } \ template \ std::enable_if_t, Expr> FUNC( \ A &&x, A &&y) { \ return {Comparison>{OP, std::move(x), std::move(y)}}; \ } BINARY(operator<, RelationalOperator::LT) BINARY(operator<=, RelationalOperator::LE) BINARY(operator==, RelationalOperator::EQ) BINARY(operator!=, RelationalOperator::NE) BINARY(operator>=, RelationalOperator::GE) BINARY(operator>, RelationalOperator::GT) #undef BINARY } // namespace Fortran::evaluate #endif // FORTRAN_EVALUATE_EXPRESSION_H_