// 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. #include "../../lib/evaluate/intrinsics.h" #include "testing.h" #include "../../lib/evaluate/expression.h" #include "../../lib/evaluate/tools.h" #include "../../lib/parser/provenance.h" #include #include #include #include namespace Fortran::evaluate { class CookedStrings { public: CookedStrings() {} explicit CookedStrings(const std::initializer_list &ss) { for (const auto &s : ss) { Save(s); } Marshal(); } void Save(const std::string &s) { offsets_[s] = cooked_.Put(s); cooked_.PutProvenance(cooked_.allSources().AddCompilerInsertion(s)); } void Marshal() { cooked_.Marshal(); } parser::CharBlock operator()(const std::string &s) { return {cooked_.data().data() + offsets_[s], s.size()}; } parser::ContextualMessages Messages(parser::Messages &buffer) { return parser::ContextualMessages{cooked_.data(), &buffer}; } void Emit(std::ostream &o, const parser::Messages &messages) { messages.Emit(o, cooked_); } private: parser::CookedSource cooked_; std::map offsets_; }; template auto Const(A &&x) -> Constant> { return Constant>{std::move(x)}; } template struct NamedArg { std::string keyword; A value; }; template static NamedArg Named(std::string kw, A &&x) { return {kw, std::move(x)}; } struct TestCall { TestCall(const IntrinsicProcTable &t, std::string n) : table{t}, name{n} {} template TestCall &Push(A &&x) { args.emplace_back(AsGenericExpr(std::move(x))); keywords.push_back(""); return *this; } template TestCall &Push(NamedArg &&x) { args.emplace_back(AsGenericExpr(std::move(x.value))); keywords.push_back(x.keyword); strings.Save(x.keyword); return *this; } template TestCall &Push(A &&x, As &&... xs) { Push(std::move(x)); return Push(std::move(xs)...); } void Marshal() { strings.Save(name); strings.Marshal(); std::size_t j{0}; for (auto &kw : keywords) { if (!kw.empty()) { args[j]->keyword = strings(kw); } ++j; } } void DoCall(std::optional resultType = std::nullopt, int rank = 0, bool isElemental = false) { Marshal(); parser::CharBlock fName{strings(name)}; std::cout << "function: " << fName.ToString(); char sep{'('}; for (const auto &a : args) { std::cout << sep; sep = ','; a->AsFortran(std::cout); } if (sep == '(') { std::cout << '('; } std::cout << ")\n"; CallCharacteristics call{fName}; auto messages{strings.Messages(buffer)}; std::optional si{table.Probe(call, args, &messages)}; if (resultType.has_value()) { TEST(si.has_value()); TEST(buffer.empty()); TEST(*resultType == si->specificIntrinsic.type); MATCH(rank, si->specificIntrinsic.rank); MATCH(isElemental, si->specificIntrinsic.attrs.test(semantics::Attr::ELEMENTAL)); } else { TEST(!si.has_value()); TEST(!buffer.empty() || name == "bad"); } strings.Emit(std::cout, buffer); } const IntrinsicProcTable &table; CookedStrings strings; parser::Messages buffer; ActualArguments args; std::string name; std::vector keywords; }; void TestIntrinsics() { semantics::IntrinsicTypeDefaultKinds defaults; MATCH(4, defaults.GetDefaultKind(TypeCategory::Integer)); MATCH(4, defaults.GetDefaultKind(TypeCategory::Real)); IntrinsicProcTable table{IntrinsicProcTable::Configure(defaults)}; table.Dump(std::cout); using Int1 = Type; using Int4 = Type; using Int8 = Type; using Real4 = Type; using Real8 = Type; using Complex4 = Type; using Complex8 = Type; using Char = Type; using Log4 = Type; TestCall{table, "bad"} .Push(Const(Scalar{})) .DoCall(); // bad intrinsic name TestCall{table, "abs"} .Push(Named("a", Const(Scalar{}))) .DoCall(Int4::dynamicType); TestCall{table, "abs"}.Push(Const(Scalar{})).DoCall(Int4::dynamicType); TestCall{table, "abs"} .Push(Named("bad", Const(Scalar{}))) .DoCall(); // bad keyword TestCall{table, "abs"}.DoCall(); // insufficient args TestCall{table, "abs"} .Push(Const(Scalar{})) .Push(Const(Scalar{})) .DoCall(); // too many args TestCall{table, "abs"} .Push(Const(Scalar{})) .Push(Named("a", Const(Scalar{}))) .DoCall(); TestCall{table, "abs"} .Push(Named("a", Const(Scalar{}))) .Push(Const(Scalar{})) .DoCall(); TestCall{table, "abs"}.Push(Const(Scalar{})).DoCall(Int1::dynamicType); TestCall{table, "abs"}.Push(Const(Scalar{})).DoCall(Int4::dynamicType); TestCall{table, "abs"}.Push(Const(Scalar{})).DoCall(Int8::dynamicType); TestCall{table, "abs"} .Push(Const(Scalar{})) .DoCall(Real4::dynamicType); TestCall{table, "abs"} .Push(Const(Scalar{})) .DoCall(Real8::dynamicType); TestCall{table, "abs"} .Push(Const(Scalar{})) .DoCall(Real4::dynamicType); TestCall{table, "abs"} .Push(Const(Scalar{})) .DoCall(Real8::dynamicType); TestCall{table, "abs"}.Push(Const(Scalar{})).DoCall(); TestCall{table, "abs"}.Push(Const(Scalar{})).DoCall(); TestCall maxCall{table, "max"}, max0Call{table, "max0"}, max1Call{table, "max1"}; TestCall amin0Call{table, "amin0"}, amin1Call{table, "amin1"}; for (int j{0}; j < 10; ++j) { maxCall.Push(Const(Scalar{})); max0Call.Push(Const(Scalar{})); max1Call.Push(Const(Scalar{})); amin0Call.Push(Const(Scalar{})); amin1Call.Push(Const(Scalar{})); } maxCall.DoCall(Real4::dynamicType); max0Call.DoCall(); max1Call.DoCall(Int4::dynamicType); amin0Call.DoCall(Real4::dynamicType); amin1Call.DoCall(); // TODO: test other intrinsics } } int main() { Fortran::evaluate::TestIntrinsics(); return testing::Complete(); }