// 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_TOOLS_H_ #define FORTRAN_EVALUATE_TOOLS_H_ #include "constant.h" #include "expression.h" #include "../common/idioms.h" #include "../common/unwrap.h" #include "../parser/message.h" #include "../semantics/attr.h" #include "../semantics/symbol.h" #include #include #include namespace Fortran::evaluate { // Some expression predicates and extractors. // 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); } template std::optional> AsVariable(const std::optional> &expr) { if (expr.has_value()) { return AsVariable(*expr); } else { return std::nullopt; } } // Predicate: true when an expression is a variable reference template bool IsVariable(const A &) { return false; } template bool IsVariable(const Designator &designator) { if constexpr (common::HasMember::u)>) { if (const auto *substring{std::get_if(&designator.u)}) { return substring->GetLastSymbol() != nullptr; } } return true; } template bool IsVariable(const FunctionRef &funcRef) { if (const semantics::Symbol * symbol{funcRef.proc().GetSymbol()}) { return symbol->attrs().test(semantics::Attr::POINTER); } else { return false; } } template bool IsVariable(const Expr &expr) { return std::visit([](const auto &x) { return IsVariable(x); }, expr.u); } template bool IsVariable(const std::optional &x) { return x.has_value() && IsVariable(*x); } // Predicate: true when an expression is assumed-rank bool IsAssumedRank(const semantics::Symbol &); bool IsAssumedRank(const ActualArgument &); template bool IsAssumedRank(const A &) { return false; } template bool IsAssumedRank(const Designator &designator) { if (const auto *symbol{ std::get_if(&designator.u)}) { return IsAssumedRank(*symbol); } else { return false; } } template bool IsAssumedRank(const Expr &expr) { return std::visit([](const auto &x) { return IsAssumedRank(x); }, expr.u); } template bool IsAssumedRank(const std::optional &x) { return x.has_value() && IsAssumedRank(*x); } // Generalizing packagers: these take operations and expressions of more // specific types and wrap them in Expr<> containers of more abstract types. template common::IfNoLvalue>, A> AsExpr(A &&x) { return Expr>{std::move(x)}; } template Expr AsExpr(Expr &&x) { static_assert(IsSpecificIntrinsicType); return std::move(x); } template Expr> AsCategoryExpr(Expr> &&x) { return std::move(x); } template common::IfNoLvalue, A> AsGenericExpr(A &&x) { if constexpr (common::HasMember) { return Expr{std::move(x)}; } else { return Expr{AsCategoryExpr(std::move(x))}; } } template common::IfNoLvalue::category>>, A> AsCategoryExpr( A &&x) { return Expr::category>>{AsExpr(std::move(x))}; } inline Expr AsGenericExpr(Expr &&x) { return std::move(x); } Expr GetComplexPart( const Expr &, bool isImaginary = false); template Expr MakeComplex(Expr> &&re, Expr> &&im) { return AsCategoryExpr(ComplexConstructor{std::move(re), std::move(im)}); } template constexpr bool IsNumericCategoryExpr() { if constexpr (common::HasMember) { return false; } else { return common::HasMember, NumericCategoryTypes>; } } // Specializing extractor. If an Expr wraps some type of object, perhaps // in several layers, return a pointer to it; otherwise null. Also works // with expressions contained in ActualArgument. template auto UnwrapExpr(B &x) -> common::Constify * { using Ty = std::decay_t; if constexpr (std::is_same_v) { return &x; } else if constexpr (std::is_same_v) { if (auto *expr{x.UnwrapExpr()}) { return UnwrapExpr(*expr); } } else if constexpr (std::is_same_v>) { return std::visit([](auto &x) { return UnwrapExpr(x); }, x.u); } else if constexpr (!common::HasMember) { if constexpr (std::is_same_v>> || std::is_same_v::category>>>) { return std::visit([](auto &x) { return UnwrapExpr(x); }, x.u); } } return nullptr; } template const A *UnwrapExpr(const std::optional &x) { if (x.has_value()) { return UnwrapExpr(*x); } else { return nullptr; } } template A *UnwrapExpr(std::optional &x) { if (x.has_value()) { return UnwrapExpr(*x); } else { return nullptr; } } // If an expression simply wraps a DataRef, extract and return it. template common::IfNoLvalue, A> ExtractDataRef(const A &) { return std::nullopt; // default base casec } template std::optional ExtractDataRef(const Designator &d) { return std::visit( [](const auto &x) -> std::optional { if constexpr (common::HasMember) { return DataRef{x}; } return std::nullopt; }, d.u); } template std::optional ExtractDataRef(const Expr &expr) { return std::visit([](const auto &x) { return ExtractDataRef(x); }, expr.u); } template std::optional ExtractDataRef(const std::optional &x) { if (x.has_value()) { return ExtractDataRef(*x); } else { return std::nullopt; } } // If an expression is simply a whole symbol data designator, // extract and return that symbol, else null. template const Symbol *UnwrapWholeSymbolDataRef(const A &x) { if (auto dataRef{ExtractDataRef(x)}) { if (const Symbol **p{std::get_if(&dataRef->u)}) { return *p; } } return nullptr; } // Creation of conversion expressions can be done to either a known // specific intrinsic type with ConvertToType(x) or by converting // one arbitrary expression to the type of another with ConvertTo(to, from). template Expr ConvertToType(Expr> &&x) { static_assert(IsSpecificIntrinsicType); if constexpr (FROMCAT != TO::category) { if constexpr (TO::category == TypeCategory::Complex) { using Part = typename TO::Part; Scalar zero; return Expr{ComplexConstructor{ ConvertToType(std::move(x)), Expr{Constant{zero}}}}; } else if constexpr (FROMCAT == TypeCategory::Complex) { // Extract and convert the real component of a complex value return std::visit( [&](auto &&z) { using ZType = ResultType; using Part = typename ZType::Part; return ConvertToType(Expr{ Expr{ComplexComponent{false, std::move(z)}}}); }, std::move(x.u)); } else { return Expr{Convert{std::move(x)}}; } } else { // Same type category if (auto *already{std::get_if>(&x.u)}) { return std::move(*already); } if constexpr (TO::category == TypeCategory::Complex) { // Extract, convert, and recombine the components. return Expr{std::visit( [](auto &z) { using FromType = ResultType; using FromPart = typename FromType::Part; using FromGeneric = SomeKind; using ToPart = typename TO::Part; Convert re{Expr{ Expr{ComplexComponent{false, z}}}}; Convert im{Expr{ Expr{ComplexComponent{true, z}}}}; return ComplexConstructor{ AsExpr(std::move(re)), AsExpr(std::move(im))}; }, x.u)}; } else { return Expr{Convert{std::move(x)}}; } } } template Expr ConvertToType(Expr> &&x) { return ConvertToType(Expr>{std::move(x)}); } template Expr ConvertToType(BOZLiteralConstant &&x) { static_assert(IsSpecificIntrinsicType); if constexpr (TO::category == TypeCategory::Integer) { return Expr{ Constant{Scalar::ConvertUnsigned(std::move(x)).value}}; } else { static_assert(TO::category == TypeCategory::Real); using Word = typename Scalar::Word; return Expr{ Constant{Scalar{Word::ConvertUnsigned(std::move(x)).value}}}; } } // Conversions to dynamic types std::optional> ConvertToType( const DynamicType &, Expr &&); std::optional> ConvertToType( const DynamicType &, std::optional> &&); std::optional> ConvertToType( const semantics::Symbol &, Expr &&); std::optional> ConvertToType( const semantics::Symbol &, std::optional> &&); // Conversions to the type of another expression template common::IfNoLvalue>, FROM> ConvertTo( const Expr> &, FROM &&x) { return ConvertToType>(std::move(x)); } template common::IfNoLvalue>, FROM> ConvertTo( const Expr> &to, FROM &&from) { return std::visit( [&](const auto &toKindExpr) { using KindExpr = std::decay_t; return AsCategoryExpr( ConvertToType>(std::move(from))); }, to.u); } template common::IfNoLvalue, FROM> ConvertTo( const Expr &to, FROM &&from) { return std::visit( [&](const auto &toCatExpr) { return AsGenericExpr(ConvertTo(toCatExpr, std::move(from))); }, to.u); } // Convert an expression of some known category to a dynamically chosen // kind of some category (usually but not necessarily distinct). template struct ConvertToKindHelper { using Result = std::optional>>; using Types = CategoryTypes; ConvertToKindHelper(int k, VALUE &&x) : kind{k}, value{std::move(x)} {} template Result Test() { if (kind == T::kind) { return std::make_optional( AsCategoryExpr(ConvertToType(std::move(value)))); } return std::nullopt; } int kind; VALUE value; }; template common::IfNoLvalue>, VALUE> ConvertToKind( int kind, VALUE &&x) { return common::SearchTypes( ConvertToKindHelper{kind, std::move(x)}) .value(); } // Given a type category CAT, SameKindExprs is a variant that // holds an arrays of expressions of the same supported kind in that // category. template using SameExprs = std::array, N>; template struct SameKindExprsHelper { template using SameExprs = std::array, N>; }; template using SameKindExprs = common::MapTemplate::template SameExprs, CategoryTypes>; // Given references to two expressions of arbitrary kind in the same type // category, convert one to the kind of the other when it has the smaller kind, // then return them in a type-safe package. template SameKindExprs AsSameKindExprs( Expr> &&x, Expr> &&y) { return std::visit( [&](auto &&kx, auto &&ky) -> SameKindExprs { using XTy = ResultType; using YTy = ResultType; if constexpr (std::is_same_v) { return {SameExprs{std::move(kx), std::move(ky)}}; } else if constexpr (XTy::kind < YTy::kind) { return {SameExprs{ConvertTo(ky, std::move(kx)), std::move(ky)}}; } else { return {SameExprs{std::move(kx), ConvertTo(kx, std::move(ky))}}; } #if !__clang__ && 100 * __GNUC__ + __GNUC_MINOR__ == 801 // Silence a bogus warning about a missing return with G++ 8.1.0. // Doesn't execute, but must be correctly typed. CHECK(!"can't happen"); return {SameExprs{std::move(kx), std::move(kx)}}; #endif }, std::move(x.u), std::move(y.u)); } // Ensure that both operands of an intrinsic REAL operation (or CMPLX() // constructor) are INTEGER or REAL, then convert them as necessary to the // same kind of REAL. using ConvertRealOperandsResult = std::optional>; ConvertRealOperandsResult ConvertRealOperands(parser::ContextualMessages &, Expr &&, Expr &&, int defaultRealKind); // Per F'2018 R718, if both components are INTEGER, they are both converted // to default REAL and the result is default COMPLEX. Otherwise, the // kind of the result is the kind of most precise REAL component, and the other // component is converted if necessary to its type. std::optional> ConstructComplex(parser::ContextualMessages &, Expr &&, Expr &&, int defaultRealKind); std::optional> ConstructComplex(parser::ContextualMessages &, std::optional> &&, std::optional> &&, int defaultRealKind); template Expr> ScalarConstantToExpr(const A &x) { using Ty = TypeOf; static_assert( std::is_same_v, std::decay_t> || !"TypeOf<> is broken"); return Expr>{Constant{x}}; } // Combine two expressions of the same specific numeric type with an operation // to produce a new expression. Implements piecewise addition and subtraction // for COMPLEX. template class OPR, typename SPECIFIC> Expr Combine(Expr &&x, Expr &&y) { static_assert(IsSpecificIntrinsicType); if constexpr (SPECIFIC::category == TypeCategory::Complex && (std::is_same_v, Add> || std::is_same_v, Subtract>)) { static constexpr int kind{SPECIFIC::kind}; using Part = Type; return AsExpr(ComplexConstructor{ AsExpr(OPR{AsExpr(ComplexComponent{false, x}), AsExpr(ComplexComponent{false, y})}), AsExpr(OPR{AsExpr(ComplexComponent{true, x}), AsExpr(ComplexComponent{true, y})})}); } else { return AsExpr(OPR{std::move(x), std::move(y)}); } } // Given two expressions of arbitrary kind in the same intrinsic type // category, convert one of them if necessary to the larger kind of the // other, then combine the resulting homogenized operands with a given // operation, returning a new expression in the same type category. template class OPR, TypeCategory CAT> Expr> PromoteAndCombine( Expr> &&x, Expr> &&y) { return std::visit( [](auto &&xy) { using Ty = ResultType; return AsCategoryExpr( Combine(std::move(xy[0]), std::move(xy[1]))); }, AsSameKindExprs(std::move(x), std::move(y))); } // Given two expressions of arbitrary type, try to combine them with a // binary numeric operation (e.g., Add), possibly with data type conversion of // one of the operands to the type of the other. Handles special cases with // typeless literal operands and with REAL/COMPLEX exponentiation to INTEGER // powers. template class OPR> std::optional> NumericOperation(parser::ContextualMessages &, Expr &&, Expr &&, int defaultRealKind); extern template std::optional> NumericOperation( parser::ContextualMessages &, Expr &&, Expr &&, int defaultRealKind); extern template std::optional> NumericOperation( parser::ContextualMessages &, Expr &&, Expr &&, int defaultRealKind); extern template std::optional> NumericOperation( parser::ContextualMessages &, Expr &&, Expr &&, int defaultRealKind); extern template std::optional> NumericOperation( parser::ContextualMessages &, Expr &&, Expr &&, int defaultRealKind); extern template std::optional> NumericOperation( parser::ContextualMessages &, Expr &&, Expr &&, int defaultRealKind); std::optional> Negation( parser::ContextualMessages &, Expr &&); // Given two expressions of arbitrary type, try to combine them with a // relational operator (e.g., .LT.), possibly with data type conversion. std::optional> Relate(parser::ContextualMessages &, RelationalOperator, Expr &&, Expr &&); template Expr> LogicalNegation( Expr> &&x) { return AsExpr(Not{std::move(x)}); } Expr LogicalNegation(Expr &&); template Expr> BinaryLogicalOperation(LogicalOperator opr, Expr> &&x, Expr> &&y) { return AsExpr(LogicalOperation{opr, std::move(x), std::move(y)}); } Expr BinaryLogicalOperation( LogicalOperator, Expr &&, Expr &&); // Convenience functions and operator overloadings for expression construction. // These interfaces are defined only for those situations that can never // emit any message. Use the more general templates (above) in other // situations. template Expr> operator-(Expr> &&x) { return AsExpr(Negate>{std::move(x)}); } template Expr> operator-( Expr> &&x) { using Part = Type; return AsExpr(ComplexConstructor{ AsExpr(Negate{AsExpr(ComplexComponent{false, x})}), AsExpr(Negate{AsExpr(ComplexComponent{true, x})})}); } template Expr> operator+(Expr> &&x, Expr> &&y) { return AsExpr(Combine>(std::move(x), std::move(y))); } template Expr> operator-(Expr> &&x, Expr> &&y) { return AsExpr(Combine>(std::move(x), std::move(y))); } template Expr> operator*(Expr> &&x, Expr> &&y) { return AsExpr(Combine>(std::move(x), std::move(y))); } template Expr> operator/(Expr> &&x, Expr> &&y) { return AsExpr(Combine>(std::move(x), std::move(y))); } template Expr> operator-(Expr> &&x) { return std::visit( [](auto &xk) { return Expr>{-std::move(xk)}; }, x.u); } template Expr> operator+( Expr> &&x, Expr> &&y) { return PromoteAndCombine(std::move(x), std::move(y)); } template Expr> operator-( Expr> &&x, Expr> &&y) { return PromoteAndCombine(std::move(x), std::move(y)); } template Expr> operator*( Expr> &&x, Expr> &&y) { return PromoteAndCombine(std::move(x), std::move(y)); } template Expr> operator/( Expr> &&x, Expr> &&y) { return PromoteAndCombine(std::move(x), std::move(y)); } // A utility for use with common::SearchTypes to create generic expressions // when an intrinsic type category for (say) a variable is known // but the kind parameter value is not. template class TEMPLATE, typename VALUE> struct TypeKindVisitor { using Result = std::optional>; using Types = CategoryTypes; TypeKindVisitor(int k, VALUE &&x) : kind{k}, value{std::move(x)} {} TypeKindVisitor(int k, const VALUE &x) : kind{k}, value{x} {} template Result Test() { if (kind == T::kind) { return AsGenericExpr(TEMPLATE{std::move(value)}); } return std::nullopt; } int kind; VALUE value; }; // GetLastSymbol() returns the rightmost symbol in an object or procedure // designator (possibly wrapped in an Expr<>), or a null pointer if // none is found. template const semantics::Symbol *GetLastSymbol(const A &) { return nullptr; } template const semantics::Symbol *GetLastSymbol(const Designator &x) { return x.GetLastSymbol(); } inline const semantics::Symbol *GetLastSymbol(const ProcedureDesignator &x) { return x.GetSymbol(); } inline const semantics::Symbol *GetLastSymbol(const ProcedureRef &x) { return GetLastSymbol(x.proc()); } template const semantics::Symbol *GetLastSymbol(const Expr &x) { return std::visit([](const auto &y) { return GetLastSymbol(y); }, x.u); } template const semantics::Symbol *GetLastSymbol(const std::optional &x) { if (x.has_value()) { return GetLastSymbol(*x); } else { return nullptr; } } // Convenience: If GetLastSymbol() succeeds on the argument, return its // set of attributes, otherwise the empty set. template semantics::Attrs GetAttrs(const A &x) { if (const semantics::Symbol * symbol{GetLastSymbol(x)}) { return symbol->attrs(); } else { return {}; } } // Predicate: IsAllocatableOrPointer() template bool IsAllocatableOrPointer(const A &x) { return GetAttrs(x).HasAny( semantics::Attrs{semantics::Attr::POINTER, semantics::Attr::ALLOCATABLE}); } // Predicate: IsProcedurePointer() template bool IsProcedurePointer(const A &) { return false; } inline bool IsProcedurePointer(const ProcedureDesignator &) { return true; } inline bool IsProcedurePointer(const ProcedureRef &) { return true; } inline bool IsProcedurePointer(const Expr &expr) { return std::visit( [](const auto &x) { return IsProcedurePointer(x); }, expr.u); } template bool IsProcedurePointer(const std::optional &x) { return x.has_value() && IsProcedurePointer(*x); } } #endif // FORTRAN_EVALUATE_TOOLS_H_