diff --git a/executable_semantics/README.md b/executable_semantics/README.md index b29062c2fa36..bf2000ff3c5c 100644 --- a/executable_semantics/README.md +++ b/executable_semantics/README.md @@ -28,7 +28,12 @@ The parser is implemented using the flex and bison parser generator tools. - [`syntax.ypp`](syntax/syntax.ypp) the grammar The parser translates program text into an abstract syntax tree (AST), defined -in the [ast](ast/) subdirectory. +in the [ast](ast/) subdirectory. The `UnimplementedExpression` node type can be +used to define new expression syntaxes without defining their semantics, and the +same techniques can be applied to other kinds of AST nodes as needed. See the +handling of the `UNIMPL_EXAMPLE` token for an example of how this is done, and +see [`unimplemented_example_test.cpp`](syntax/unimplemented_example_test.cpp) +for an example of how to test it. The [type checker](interpreter/typecheck.h) defines what it means for an AST to be a valid program. The type checker prints an error and exits if the AST is diff --git a/executable_semantics/ast/BUILD b/executable_semantics/ast/BUILD index 25b7f0ca9fb0..c7ac2271d764 100644 --- a/executable_semantics/ast/BUILD +++ b/executable_semantics/ast/BUILD @@ -44,6 +44,7 @@ cc_library( ], hdrs = ["ast_test_matchers.h"], deps = [ + ":ast", ":ast_node", ":declaration", ":expression", diff --git a/executable_semantics/ast/ast_node.h b/executable_semantics/ast/ast_node.h index 49876f2fefdd..e2a5343c5e65 100644 --- a/executable_semantics/ast/ast_node.h +++ b/executable_semantics/ast/ast_node.h @@ -45,6 +45,9 @@ class AstNode { auto operator=(AstNode&&) -> AstNode& = delete; virtual ~AstNode() = 0; + virtual void Print(llvm::raw_ostream& out) const = 0; + LLVM_DUMP_METHOD void Dump() const { Print(llvm::errs()); } + // Returns an enumerator specifying the concrete type of this node. // // Abstract subclasses of AstNode will provide their own `kind()` method diff --git a/executable_semantics/ast/ast_rtti.txt b/executable_semantics/ast/ast_rtti.txt index f532bb19e9cc..2798cd135d56 100644 --- a/executable_semantics/ast/ast_rtti.txt +++ b/executable_semantics/ast/ast_rtti.txt @@ -50,5 +50,6 @@ abstract class Expression : AstNode; class TypeTypeLiteral : Expression; class IdentifierExpression : Expression; class IntrinsicExpression : Expression; + class UnimplementedExpression : Expression; abstract class Member : AstNode; class FieldMember : Member, NamedEntity; diff --git a/executable_semantics/ast/ast_test_matchers.h b/executable_semantics/ast/ast_test_matchers.h index b4a4dab8b884..53113989f065 100644 --- a/executable_semantics/ast/ast_test_matchers.h +++ b/executable_semantics/ast/ast_test_matchers.h @@ -25,15 +25,13 @@ namespace Carbon { // Matches a Block node whose .statements() match `matcher`. inline auto BlockContentsAre( - ::testing::Matcher>> matcher) - -> TestingInternal::BlockContentsMatcher { + ::testing::Matcher>> matcher) { return TestingInternal::BlockContentsMatcher(std::move(matcher)); } // Matches a literal with the given value. // TODO: add overload for string literals -inline auto MatchesLiteral(int value) - -> TestingInternal::MatchesIntLiteralMatcher { +inline auto MatchesLiteral(int value) { return TestingInternal::MatchesIntLiteralMatcher(value); } @@ -41,55 +39,48 @@ inline auto MatchesLiteral(int value) // operands that match `lhs` and `rhs` (respectively). The name of the function // indicates what value of `.op()` they match. inline auto MatchesMul(::testing::Matcher lhs, - ::testing::Matcher rhs) - -> TestingInternal::BinaryOperatorExpressionMatcher { + ::testing::Matcher rhs) { return TestingInternal::BinaryOperatorExpressionMatcher( Operator::Mul, std::move(lhs), std::move(rhs)); } inline auto MatchesAdd(::testing::Matcher lhs, - ::testing::Matcher rhs) - -> TestingInternal::BinaryOperatorExpressionMatcher { + ::testing::Matcher rhs) { return TestingInternal::BinaryOperatorExpressionMatcher( Operator::Add, std::move(lhs), std::move(rhs)); } inline auto MatchesAnd(::testing::Matcher lhs, - ::testing::Matcher rhs) - -> TestingInternal::BinaryOperatorExpressionMatcher { + ::testing::Matcher rhs) { return TestingInternal::BinaryOperatorExpressionMatcher( Operator::And, std::move(lhs), std::move(rhs)); } inline auto MatchesEq(::testing::Matcher lhs, - ::testing::Matcher rhs) - -> TestingInternal::BinaryOperatorExpressionMatcher { + ::testing::Matcher rhs) { return TestingInternal::BinaryOperatorExpressionMatcher( Operator::Eq, std::move(lhs), std::move(rhs)); } inline auto MatchesOr(::testing::Matcher lhs, - ::testing::Matcher rhs) - -> TestingInternal::BinaryOperatorExpressionMatcher { + ::testing::Matcher rhs) { return TestingInternal::BinaryOperatorExpressionMatcher( Operator::Or, std::move(lhs), std::move(rhs)); } inline auto MatchesSub(::testing::Matcher lhs, - ::testing::Matcher rhs) - -> TestingInternal::BinaryOperatorExpressionMatcher { + ::testing::Matcher rhs) { return TestingInternal::BinaryOperatorExpressionMatcher( Operator::Sub, std::move(lhs), std::move(rhs)); } // Matches a return statement with no operand. -inline auto MatchesEmptyReturn() -> TestingInternal::MatchesReturnMatcher { +inline auto MatchesEmptyReturn() { return TestingInternal::MatchesReturnMatcher(); } // Matches a return statement with an explicit operand that matches `matcher`. -inline auto MatchesReturn(::testing::Matcher matcher) - -> TestingInternal::MatchesReturnMatcher { +inline auto MatchesReturn(::testing::Matcher matcher) { return TestingInternal::MatchesReturnMatcher(matcher); } @@ -113,11 +104,29 @@ inline auto MatchesReturn(::testing::Matcher matcher) // TODO: Add method for matching only if the declaration has no body. // TODO: Add methods for matching parameters, deduced parameters, // and return term. -inline auto MatchesFunctionDeclaration() - -> TestingInternal::MatchesFunctionDeclarationMatcher { +inline auto MatchesFunctionDeclaration() { return TestingInternal::MatchesFunctionDeclarationMatcher(); } +// Matches an UnimplementedExpression with the given label, whose children +// match `children_matcher`. +inline auto MatchesUnimplementedExpression( + std::string label, + ::testing::Matcher>> + children_matcher) { + return TestingInternal::MatchesUnimplementedExpressionMatcher( + std::move(label), std::move(children_matcher)); +} + +// Matches an `AST` whose declarations match the given matcher. Unlike other +// matchers in this file, this matcher does not match pointers. +inline auto ASTDeclarations( + ::testing::Matcher>> + declarations_matcher) { + return TestingInternal::ASTDeclarationsMatcher( + std::move(declarations_matcher)); +} + } // namespace Carbon #endif // EXECUTABLE_SEMANTICS_AST_AST_TEST_MATCHERS_H_ diff --git a/executable_semantics/ast/ast_test_matchers_internal.cpp b/executable_semantics/ast/ast_test_matchers_internal.cpp index 49a575d92e4a..8cf86a158b0f 100644 --- a/executable_semantics/ast/ast_test_matchers_internal.cpp +++ b/executable_semantics/ast/ast_test_matchers_internal.cpp @@ -10,7 +10,9 @@ namespace Carbon { namespace TestingInternal { -auto BlockContentsMatcher::MatchAndExplain( +AstNodeMatcherBase::~AstNodeMatcherBase() = default; + +auto BlockContentsMatcher::MatchAndExplainImpl( Nonnull node, ::testing::MatchResultListener* out) const -> bool { const auto* block = llvm::dyn_cast(node); @@ -22,7 +24,7 @@ auto BlockContentsMatcher::MatchAndExplain( return matcher_.MatchAndExplain(block->statements(), out); } -auto MatchesIntLiteralMatcher::MatchAndExplain( +auto MatchesIntLiteralMatcher::MatchAndExplainImpl( const AstNode* node, ::testing::MatchResultListener* listener) const -> bool { const auto* literal = llvm::dyn_cast(node); @@ -35,7 +37,7 @@ auto MatchesIntLiteralMatcher::MatchAndExplain( return matched; } -auto BinaryOperatorExpressionMatcher::MatchAndExplain( +auto BinaryOperatorExpressionMatcher::MatchAndExplainImpl( Nonnull node, ::testing::MatchResultListener* out) const -> bool { const auto* op = llvm::dyn_cast(node); @@ -70,7 +72,7 @@ void BinaryOperatorExpressionMatcher::DescribeToImpl(std::ostream* out, rhs_.DescribeTo(out); } -auto MatchesReturnMatcher::MatchAndExplain( +auto MatchesReturnMatcher::MatchAndExplainImpl( const AstNode* node, ::testing::MatchResultListener* listener) const -> bool { const auto* ret = llvm::dyn_cast(node); @@ -135,7 +137,7 @@ class RawListenerOstream : public llvm::raw_ostream { }; } // namespace -auto MatchesFunctionDeclarationMatcher::MatchAndExplain( +auto MatchesFunctionDeclarationMatcher::MatchAndExplainImpl( const AstNode* node, ::testing::MatchResultListener* listener) const -> bool { RawListenerOstream out(listener); @@ -185,5 +187,28 @@ void MatchesFunctionDeclarationMatcher::DescribeToImpl(std::ostream* out, } } +auto MatchesUnimplementedExpressionMatcher::MatchAndExplainImpl( + const AstNode* node, ::testing::MatchResultListener* listener) const + -> bool { + const auto* unimplemented = llvm::dyn_cast(node); + if (unimplemented == nullptr) { + *listener << "is not an UnimplementedExpression"; + return false; + } + if (unimplemented->label() != label_) { + *listener << "is not labeled " << label_; + return false; + } + *listener << "is an unimplemented " << label_ << " node whose children "; + return children_matcher_.MatchAndExplain(unimplemented->children(), listener); +} + +void MatchesUnimplementedExpressionMatcher::DescribeToImpl(std::ostream* out, + bool negated) const { + *out << "is " << (negated ? "not " : "") << "an unimplemented " << label_ + << " node whose children "; + children_matcher_.DescribeTo(out); +} + } // namespace TestingInternal } // namespace Carbon diff --git a/executable_semantics/ast/ast_test_matchers_internal.h b/executable_semantics/ast/ast_test_matchers_internal.h index eb8e60683f3e..ddcd6ba030c7 100644 --- a/executable_semantics/ast/ast_test_matchers_internal.h +++ b/executable_semantics/ast/ast_test_matchers_internal.h @@ -12,6 +12,7 @@ #include +#include "executable_semantics/ast/ast.h" #include "executable_semantics/ast/ast_node.h" #include "executable_semantics/ast/declaration.h" #include "executable_semantics/ast/expression.h" @@ -21,46 +22,15 @@ namespace Carbon { namespace TestingInternal { -// Matches a Block based on its contents. -class BlockContentsMatcher { +// Abstract GoogleMock matcher which matches AstNodes, and is agnostic to +// whether they are passed by pointer or reference. Derived classes specify what +// kinds of AstNodes they match by overriding DescribeToImpl and +// MatchAndExplainImpl. +class AstNodeMatcherBase { public: using is_gtest_matcher = void; - // Constructs a matcher which matches a Block node whose .statements() matches - // `matcher`. - explicit BlockContentsMatcher( - ::testing::Matcher>> matcher) - : matcher_(std::move(matcher)) {} - - void DescribeTo(std::ostream* out) const { - *out << "is a Block whose statements collection "; - matcher_.DescribeTo(out); - } - - void DescribeNegationTo(std::ostream* out) const { - *out << "is not a Block whose statements collection "; - matcher_.DescribeTo(out); - } - - auto MatchAndExplain(const AstNode& node, - ::testing::MatchResultListener* out) const -> bool { - return MatchAndExplain(&node, out); - } - - auto MatchAndExplain(Nonnull node, - ::testing::MatchResultListener* out) const -> bool; - - private: - testing::Matcher>> matcher_; -}; - -// Matches an IntLiteral. -class MatchesIntLiteralMatcher { - public: - using is_gtest_matcher = void; - - // Constructs a matcher which matches an IntLiteral whose value() is `value`. - explicit MatchesIntLiteralMatcher(int value) : value_(value) {} + virtual ~AstNodeMatcherBase(); void DescribeTo(std::ostream* out) const { DescribeToImpl(out, /*negated=*/false); @@ -71,26 +41,72 @@ class MatchesIntLiteralMatcher { } auto MatchAndExplain(const AstNode& node, - ::testing::MatchResultListener* listener) const -> bool { - return MatchAndExplain(&node, listener); + ::testing::MatchResultListener* out) const -> bool { + return MatchAndExplainImpl(&node, out); } - auto MatchAndExplain(const AstNode* node, - ::testing::MatchResultListener* listener) const -> bool; + auto MatchAndExplain(Nonnull node, + ::testing::MatchResultListener* out) const -> bool { + return MatchAndExplainImpl(node, out); + } private: - void DescribeToImpl(std::ostream* out, bool negated) const { + // The implementation of this method must satisfy the contract of + // `DescribeTo(out)` (as specified by GoogleMock) if `negated` is false, + // or the contract of `DescribeNegationTo(out)` if `negated` is true. + virtual void DescribeToImpl(std::ostream* out, bool negated) const = 0; + + // The implementation of this method must satisfy the contract of + // `MatchAndExplain(node, out)`, as specified by GoogleMock. + virtual auto MatchAndExplainImpl(Nonnull node, + ::testing::MatchResultListener* out) const + -> bool = 0; +}; + +// Matches a Block based on its contents. +class BlockContentsMatcher : public AstNodeMatcherBase { + public: + // Constructs a matcher which matches a Block node whose .statements() matches + // `matcher`. + explicit BlockContentsMatcher( + ::testing::Matcher>> matcher) + : matcher_(std::move(matcher)) {} + + private: + void DescribeToImpl(std::ostream* out, bool negated) const override { + *out << "is " << (negated ? "not " : "") + << "a Block whose statements collection "; + matcher_.DescribeTo(out); + } + + auto MatchAndExplainImpl(Nonnull node, + ::testing::MatchResultListener* out) const + -> bool override; + + testing::Matcher>> matcher_; +}; + +// Matches an IntLiteral. +class MatchesIntLiteralMatcher : public AstNodeMatcherBase { + public: + // Constructs a matcher which matches an IntLiteral whose value() is `value`. + explicit MatchesIntLiteralMatcher(int value) : value_(value) {} + + private: + void DescribeToImpl(std::ostream* out, bool negated) const override { *out << "is " << (negated ? "not " : "") << "a literal " << value_; } + auto MatchAndExplainImpl(const AstNode* node, + ::testing::MatchResultListener* listener) const + -> bool override; + int value_; }; // Matches a PrimitiveOperatorExpression that has two operands. -class BinaryOperatorExpressionMatcher { +class BinaryOperatorExpressionMatcher : public AstNodeMatcherBase { public: - using is_gtest_matcher = void; - // Constructs a matcher which matches a PrimitiveOperatorExpression whose // operator is `op`, and which has two operands that match `lhs` and `rhs` // respectively. @@ -99,24 +115,12 @@ class BinaryOperatorExpressionMatcher { ::testing::Matcher rhs) : op_(op), lhs_(std::move(lhs)), rhs_(std::move(rhs)) {} - void DescribeTo(std::ostream* out) const { - DescribeToImpl(out, /*negated=*/false); - } - - void DescribeNegationTo(std::ostream* out) const { - DescribeToImpl(out, /*negated=*/true); - } - - auto MatchAndExplain(const AstNode& node, - ::testing::MatchResultListener* out) const -> bool { - return MatchAndExplain(&node, out); - } - - auto MatchAndExplain(Nonnull node, - ::testing::MatchResultListener* out) const -> bool; - private: - void DescribeToImpl(std::ostream* out, bool negated) const; + void DescribeToImpl(std::ostream* out, bool negated) const override; + + auto MatchAndExplainImpl(Nonnull node, + ::testing::MatchResultListener* out) const + -> bool override; Operator op_; ::testing::Matcher lhs_; @@ -124,10 +128,8 @@ class BinaryOperatorExpressionMatcher { }; // Matches a Return node. -class MatchesReturnMatcher { +class MatchesReturnMatcher : public AstNodeMatcherBase { public: - using is_gtest_matcher = void; - // Constructs a matcher which matches a Return statement that has no operand. explicit MatchesReturnMatcher() = default; @@ -136,34 +138,20 @@ class MatchesReturnMatcher { explicit MatchesReturnMatcher(::testing::Matcher matcher) : matcher_(std::move(matcher)) {} - void DescribeTo(std::ostream* out) const { - DescribeToImpl(out, /*negated=*/false); - } - - void DescribeNegationTo(std::ostream* out) const { - DescribeToImpl(out, /*negated=*/true); - } - - auto MatchAndExplain(const AstNode& node, - ::testing::MatchResultListener* listener) const -> bool { - return MatchAndExplain(&node, listener); - } - - auto MatchAndExplain(const AstNode* node, - ::testing::MatchResultListener* listener) const -> bool; - private: - void DescribeToImpl(std::ostream* out, bool negated) const; + void DescribeToImpl(std::ostream* out, bool negated) const override; + + auto MatchAndExplainImpl(const AstNode* node, + ::testing::MatchResultListener* listener) const + -> bool override; std::optional<::testing::Matcher> matcher_; }; // Matches a FunctionDeclaration. See documentation for // MatchesFunctionDeclaration in ast_test_matchers.h. -class MatchesFunctionDeclarationMatcher { +class MatchesFunctionDeclarationMatcher : public AstNodeMatcherBase { public: - using is_gtest_matcher = void; - MatchesFunctionDeclarationMatcher() = default; auto WithName(::testing::Matcher name_matcher) @@ -178,29 +166,71 @@ class MatchesFunctionDeclarationMatcher { return *this; } - void DescribeTo(std::ostream* out) const { - DescribeToImpl(out, /*negated=*/false); - } - - void DescribeNegationTo(std::ostream* out) const { - DescribeToImpl(out, /*negated=*/true); - } - - auto MatchAndExplain(const AstNode& node, - ::testing::MatchResultListener* listener) const -> bool { - return MatchAndExplain(&node, listener); - } - - auto MatchAndExplain(const AstNode* node, - ::testing::MatchResultListener* listener) const -> bool; - private: - void DescribeToImpl(std::ostream* out, bool negated) const; + void DescribeToImpl(std::ostream* out, bool negated) const override; + auto MatchAndExplainImpl(const AstNode* node, + ::testing::MatchResultListener* listener) const + -> bool override; std::optional<::testing::Matcher> name_matcher_; std::optional<::testing::Matcher> body_matcher_; }; +// Matches an UnimplementedExpression. +class MatchesUnimplementedExpressionMatcher : public AstNodeMatcherBase { + public: + // Constructs a matcher which matches an UnimplementedExpression that has the + // given label, and whose children match children_matcher. + MatchesUnimplementedExpressionMatcher( + std::string label, + ::testing::Matcher>> + children_matcher) + : label_(std::move(label)), + children_matcher_(std::move(children_matcher)) {} + + private: + void DescribeToImpl(std::ostream* out, bool negated) const override; + + auto MatchAndExplainImpl(Nonnull node, + ::testing::MatchResultListener* listener) const + -> bool override; + + std::string label_; + ::testing::Matcher>> children_matcher_; +}; + +// Matches an `AST`. +class ASTDeclarationsMatcher { + public: + using is_gtest_matcher = void; + + // Constructs a matcher which matches an `AST` whose `declarations` member + // matches `declarations_matcher` + explicit ASTDeclarationsMatcher( + ::testing::Matcher>> + declarations_matcher) + : declarations_matcher_(std::move(declarations_matcher)) {} + + void DescribeTo(std::ostream* out) const { + *out << "AST declarations "; + declarations_matcher_.DescribeTo(out); + } + + void DescribeNegationTo(std::ostream* out) const { + *out << "AST declarations "; + declarations_matcher_.DescribeNegationTo(out); + } + + auto MatchAndExplain(const AST& ast, + ::testing::MatchResultListener* listener) const -> bool { + *listener << "whose declarations "; + return declarations_matcher_.MatchAndExplain(ast.declarations, listener); + } + + private: + ::testing::Matcher>> declarations_matcher_; +}; + } // namespace TestingInternal } // namespace Carbon diff --git a/executable_semantics/ast/ast_test_matchers_test.cpp b/executable_semantics/ast/ast_test_matchers_test.cpp index 4f1f45baff25..6ad803a70235 100644 --- a/executable_semantics/ast/ast_test_matchers_test.cpp +++ b/executable_semantics/ast/ast_test_matchers_test.cpp @@ -17,6 +17,7 @@ namespace Carbon { namespace { using ::testing::_; +using ::testing::ElementsAre; using ::testing::IsEmpty; using ::testing::Not; @@ -124,5 +125,38 @@ TEST(MatchesFunctionDeclarationTest, BasicUsage) { EXPECT_THAT(body, Not(MatchesFunctionDeclaration())); } +TEST(MatchesUnimplementedExpressionTest, BasicUsage) { + IntLiteral two(DummyLoc, 2); + IntLiteral three(DummyLoc, 3); + UnimplementedExpression unimplemented(DummyLoc, "DummyLabel", &two, &three); + + EXPECT_THAT(unimplemented, MatchesUnimplementedExpression( + "DummyLabel", ElementsAre(MatchesLiteral(2), + MatchesLiteral(3)))); + EXPECT_THAT( + &unimplemented, + MatchesUnimplementedExpression( + "DummyLabel", ElementsAre(MatchesLiteral(2), MatchesLiteral(3)))); + EXPECT_THAT( + unimplemented, + Not(MatchesUnimplementedExpression( + "WrongLabel", ElementsAre(MatchesLiteral(2), MatchesLiteral(3))))); + EXPECT_THAT(unimplemented, + Not(MatchesUnimplementedExpression("DummyLabel", IsEmpty()))); + EXPECT_THAT(two, + Not(MatchesUnimplementedExpression("DummyLabel", IsEmpty()))); +} + +TEST(ASTDeclarationsTest, BasicUsage) { + TuplePattern params(DummyLoc, {}); + Block body(DummyLoc, {}); + FunctionDeclaration decl(DummyLoc, "Foo", {}, ¶ms, + ReturnTerm::Omitted(DummyLoc), &body); + AST ast = {.declarations = {&decl}}; + + EXPECT_THAT(ast, ASTDeclarations(ElementsAre(MatchesFunctionDeclaration()))); + EXPECT_THAT(ast, Not(ASTDeclarations(IsEmpty()))); +} + } // namespace } // namespace Carbon diff --git a/executable_semantics/ast/declaration.cpp b/executable_semantics/ast/declaration.cpp index 9d949f4f161d..429d8718f057 100644 --- a/executable_semantics/ast/declaration.cpp +++ b/executable_semantics/ast/declaration.cpp @@ -4,6 +4,7 @@ #include "executable_semantics/ast/declaration.h" +#include "llvm/ADT/StringExtras.h" #include "llvm/Support/Casting.h" namespace Carbon { @@ -32,7 +33,7 @@ void Declaration::Print(llvm::raw_ostream& out) const { const auto& choice = cast(*this); out << "choice " << choice.name() << " {\n"; for (Nonnull alt : choice.alternatives()) { - out << "alt " << alt->name() << " " << alt->signature() << ";\n"; + out << *alt << ";\n"; } out << "}\n"; break; @@ -46,6 +47,10 @@ void Declaration::Print(llvm::raw_ostream& out) const { } } +void GenericBinding::Print(llvm::raw_ostream& out) const { + out << name() << ":! " << type(); +} + void ReturnTerm::Print(llvm::raw_ostream& out) const { switch (kind_) { case ReturnKind::Omitted: @@ -63,14 +68,9 @@ void FunctionDeclaration::PrintDepth(int depth, llvm::raw_ostream& out) const { out << "fn " << name_ << " "; if (!deduced_parameters_.empty()) { out << "["; - unsigned int i = 0; + llvm::ListSeparator sep; for (Nonnull deduced : deduced_parameters_) { - if (i != 0) { - out << ", "; - } - out << deduced->name() << ":! "; - deduced->type().Print(out); - ++i; + out << sep << *deduced; } out << "]"; } @@ -84,4 +84,8 @@ void FunctionDeclaration::PrintDepth(int depth, llvm::raw_ostream& out) const { } } +void AlternativeSignature::Print(llvm::raw_ostream& out) const { + out << "alt " << name() << " " << signature(); +} + } // namespace Carbon diff --git a/executable_semantics/ast/declaration.h b/executable_semantics/ast/declaration.h index f099e0221efc..37a23cc1a2aa 100644 --- a/executable_semantics/ast/declaration.h +++ b/executable_semantics/ast/declaration.h @@ -38,8 +38,7 @@ class Declaration : public virtual AstNode, public NamedEntity { Declaration(const Member&) = delete; auto operator=(const Member&) -> Declaration& = delete; - void Print(llvm::raw_ostream& out) const; - LLVM_DUMP_METHOD void Dump() const { Print(llvm::errs()); } + void Print(llvm::raw_ostream& out) const override; static auto classof(const AstNode* node) -> bool { return InheritsFromDeclaration(node->kind()); @@ -84,6 +83,8 @@ struct GenericBinding : public virtual AstNode, public NamedEntity { name_(std::move(name)), type_(type) {} + void Print(llvm::raw_ostream& out) const override; + static auto classof(const AstNode* node) -> bool { return InheritsFromGenericBinding(node->kind()); } @@ -253,6 +254,8 @@ class AlternativeSignature : public virtual AstNode, public NamedEntity { name_(std::move(name)), signature_(signature) {} + void Print(llvm::raw_ostream& out) const override; + static auto classof(const AstNode* node) -> bool { return InheritsFromAlternativeSignature(node->kind()); } diff --git a/executable_semantics/ast/expression.cpp b/executable_semantics/ast/expression.cpp index f2e9c9e36a08..59d3d09c2002 100644 --- a/executable_semantics/ast/expression.cpp +++ b/executable_semantics/ast/expression.cpp @@ -184,6 +184,17 @@ void Expression::Print(llvm::raw_ostream& out) const { out << "print"; } out << ")"; + break; + case ExpressionKind::UnimplementedExpression: { + const auto& unimplemented = cast(*this); + out << "UnimplementedExpression<" << unimplemented.label() << ">("; + llvm::ListSeparator sep; + for (Nonnull child : unimplemented.children()) { + out << sep << *child; + } + out << ")"; + break; + } } } diff --git a/executable_semantics/ast/expression.h b/executable_semantics/ast/expression.h index f7e71e25111f..37c2d15e6436 100644 --- a/executable_semantics/ast/expression.h +++ b/executable_semantics/ast/expression.h @@ -36,8 +36,7 @@ class Expression : public virtual AstNode { ~Expression() override = 0; - void Print(llvm::raw_ostream& out) const; - LLVM_DUMP_METHOD void Dump() const { Print(llvm::errs()); } + void Print(llvm::raw_ostream& out) const override; static auto classof(const AstNode* node) { return InheritsFromExpression(node->kind()); @@ -451,6 +450,45 @@ class IntrinsicExpression : public Expression { Nonnull args_; }; +// An expression whose semantics have not been implemented. This can be used +// as a placeholder during development, in order to implement and test parsing +// of a new expression syntax without having to implement its semantics. +class UnimplementedExpression : public Expression { + public: + // Constructs an UnimplementedExpression with the given label and the given + // children, which must all be convertible to Nonnull. The label + // should correspond roughly to the name of the class that will eventually + // replace this usage of UnimplementedExpression. + template + UnimplementedExpression(SourceLocation source_loc, std::string label, + Children... children) + : AstNode(AstNodeKind::UnimplementedExpression, source_loc), + label_(std::move(label)) { + AddChildren(children...); + } + + static auto classof(const AstNode* node) -> bool { + return InheritsFromUnimplementedExpression(node->kind()); + } + + auto label() const -> std::string_view { return label_; } + auto children() const -> llvm::ArrayRef> { + return children_; + } + + private: + void AddChildren() {} + + template + void AddChildren(Nonnull child, Children... children) { + children_.push_back(child); + AddChildren(children...); + } + + std::string label_; + std::vector> children_; +}; + // Converts paren_contents to an Expression, interpreting the parentheses as // grouping if their contents permit that interpretation, or as forming a // tuple otherwise. diff --git a/executable_semantics/ast/member.h b/executable_semantics/ast/member.h index 090e5c2af0e6..5ad8ab94bc3f 100644 --- a/executable_semantics/ast/member.h +++ b/executable_semantics/ast/member.h @@ -30,8 +30,7 @@ class Member : public virtual AstNode, public NamedEntity { Member(const Member&) = delete; auto operator=(const Member&) -> Member& = delete; - void Print(llvm::raw_ostream& out) const; - LLVM_DUMP_METHOD void Dump() const { Print(llvm::errs()); } + void Print(llvm::raw_ostream& out) const override; static auto classof(const AstNode* node) -> bool { return InheritsFromMember(node->kind()); diff --git a/executable_semantics/ast/pattern.h b/executable_semantics/ast/pattern.h index b841baa1d9b6..7ef8d8d9a6ba 100644 --- a/executable_semantics/ast/pattern.h +++ b/executable_semantics/ast/pattern.h @@ -36,8 +36,7 @@ class Pattern : public virtual AstNode { ~Pattern() override = 0; - void Print(llvm::raw_ostream& out) const; - LLVM_DUMP_METHOD void Dump() const { Print(llvm::errs()); } + void Print(llvm::raw_ostream& out) const override; static auto classof(const AstNode* node) -> bool { return InheritsFromPattern(node->kind()); diff --git a/executable_semantics/ast/statement.h b/executable_semantics/ast/statement.h index 96d06d5e3fe9..6f452a007aa7 100644 --- a/executable_semantics/ast/statement.h +++ b/executable_semantics/ast/statement.h @@ -25,9 +25,8 @@ class Statement : public virtual AstNode { public: ~Statement() override = 0; - void Print(llvm::raw_ostream& out) const { PrintDepth(-1, out); } + void Print(llvm::raw_ostream& out) const override { PrintDepth(-1, out); } void PrintDepth(int depth, llvm::raw_ostream& out) const; - LLVM_DUMP_METHOD void Dump() const { Print(llvm::errs()); } static auto classof(const AstNode* node) { return InheritsFromStatement(node->kind()); diff --git a/executable_semantics/interpreter/interpreter.cpp b/executable_semantics/interpreter/interpreter.cpp index a9d200508e78..254a6cc95ca0 100644 --- a/executable_semantics/interpreter/interpreter.cpp +++ b/executable_semantics/interpreter/interpreter.cpp @@ -358,6 +358,8 @@ void Interpreter::StepLvalue() { case ExpressionKind::StringTypeLiteral: case ExpressionKind::IntrinsicExpression: FATAL() << "Can't treat expression as lvalue: " << exp; + case ExpressionKind::UnimplementedExpression: + FATAL() << "Unimplemented: " << exp; } } @@ -651,6 +653,8 @@ void Interpreter::StepExp() { CHECK(act.pos() == 0); return todo_.FinishAction(arena_->New()); } + case ExpressionKind::UnimplementedExpression: + FATAL() << "Unimplemented: " << exp; } // switch (exp->kind) } diff --git a/executable_semantics/interpreter/type_checker.cpp b/executable_semantics/interpreter/type_checker.cpp index 568746893a28..ce2fea5b9e1f 100644 --- a/executable_semantics/interpreter/type_checker.cpp +++ b/executable_semantics/interpreter/type_checker.cpp @@ -750,6 +750,8 @@ auto TypeChecker::TypeCheckExp(Nonnull e, TypeEnv types, e->set_value_category(Expression::ValueCategory::Let); SetStaticType(e, arena_->New()); return TCResult(types); + case ExpressionKind::UnimplementedExpression: + FATAL() << "Unimplemented: " << *e; } } diff --git a/executable_semantics/syntax/BUILD b/executable_semantics/syntax/BUILD index c14e1cdaa059..7a2e2e021976 100644 --- a/executable_semantics/syntax/BUILD +++ b/executable_semantics/syntax/BUILD @@ -22,6 +22,17 @@ cc_test( ], ) +cc_library( + name = "parse_test_matchers", + testonly = 1, + srcs = ["parse_test_matchers_internal.h"], + hdrs = ["parse_test_matchers.h"], + deps = [ + ":syntax", + "@com_google_googletest//:gtest", + ], +) + cc_library( name = "syntax", srcs = [ @@ -108,3 +119,14 @@ mypy_test( include_imports = True, deps = [":format_grammar_lib"], ) + +cc_test( + name = "unimplemented_example_test", + srcs = ["unimplemented_example_test.cpp"], + deps = [ + ":parse_test_matchers", + ":syntax", + "//executable_semantics/ast:ast_test_matchers", + "@com_google_googletest//:gtest_main", + ], +) diff --git a/executable_semantics/syntax/lexer.lpp b/executable_semantics/syntax/lexer.lpp index 1ab6f56e8b7a..087d97dc8d68 100644 --- a/executable_semantics/syntax/lexer.lpp +++ b/executable_semantics/syntax/lexer.lpp @@ -77,6 +77,7 @@ STRING "String" TRUE "true" TYPE "Type" UNDERSCORE "_" +UNIMPL_EXAMPLE "__unimplemented_example_infix" VAR "var" WHILE "while" /* table-end */ @@ -169,6 +170,7 @@ string_literal \"([^\\\"\n\v\f\r]|\\.)*\" {TRUE} { return SIMPLE_TOKEN(TRUE); } {TYPE} { return SIMPLE_TOKEN(TYPE); } {UNDERSCORE} { return SIMPLE_TOKEN(UNDERSCORE); } +{UNIMPL_EXAMPLE} { return SIMPLE_TOKEN(UNIMPL_EXAMPLE); } {VAR} { return SIMPLE_TOKEN(VAR); } {WHILE} { return SIMPLE_TOKEN(WHILE); } /* table-end */ diff --git a/executable_semantics/syntax/parse_test_matchers.h b/executable_semantics/syntax/parse_test_matchers.h new file mode 100644 index 000000000000..eb94cfed2164 --- /dev/null +++ b/executable_semantics/syntax/parse_test_matchers.h @@ -0,0 +1,23 @@ +// Part of the Carbon Language project, under the Apache License v2.0 with LLVM +// Exceptions. See /LICENSE for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#ifndef EXECUTABLE_SEMANTICS_SYNTAX_PARSE_TEST_MATCHERS_H_ +#define EXECUTABLE_SEMANTICS_SYNTAX_PARSE_TEST_MATCHERS_H_ + +#include +#include + +#include "executable_semantics/syntax/parse_test_matchers_internal.h" + +namespace Carbon { + +// Matches the return value of `Parse()` if it represents a successful parse +// whose output matches the given matcher. +inline auto ParsedAs(::testing::Matcher ast_matcher) { + return TestingInternal::ParsedAsMatcher(std::move(ast_matcher)); +} + +} // namespace Carbon + +#endif // EXECUTABLE_SEMANTICS_SYNTAX_PARSE_TEST_MATCHERS_H_ diff --git a/executable_semantics/syntax/parse_test_matchers_internal.h b/executable_semantics/syntax/parse_test_matchers_internal.h new file mode 100644 index 000000000000..3970378bd301 --- /dev/null +++ b/executable_semantics/syntax/parse_test_matchers_internal.h @@ -0,0 +1,59 @@ +// Part of the Carbon Language project, under the Apache License v2.0 with LLVM +// Exceptions. See /LICENSE for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#ifndef EXECUTABLE_SEMANTICS_SYNTAX_PARSE_TEST_MATCHERS_INTERNAL_H_ +#define EXECUTABLE_SEMANTICS_SYNTAX_PARSE_TEST_MATCHERS_INTERNAL_H_ + +#include +#include + +#include +#include + +#include "executable_semantics/syntax/parse.h" + +namespace Carbon { +namespace TestingInternal { + +// Implementation of ParsedAs(). See there for detailed documentation. +class ParsedAsMatcher { + public: + using is_gtest_matcher = void; + + explicit ParsedAsMatcher(::testing::Matcher ast_matcher) + : ast_matcher_(std::move(ast_matcher)) {} + + void DescribeTo(std::ostream* out) const { + DescribeToImpl(out, /*negated=*/false); + } + + void DescribeNegationTo(std::ostream* out) const { + DescribeToImpl(out, /*negated=*/true); + } + + auto MatchAndExplain(const std::variant& result, + ::testing::MatchResultListener* listener) const -> bool { + if (std::holds_alternative(result)) { + *listener << "holds error code " << std::get(result); + return false; + } else { + *listener << "is a successful parse whose "; + return ast_matcher_.MatchAndExplain(std::get(result), listener); + } + } + + private: + void DescribeToImpl(std::ostream* out, bool negated) const { + *out << "is " << (negated ? "not " : "") + << "a successful parse result whose "; + ast_matcher_.DescribeTo(out); + } + + ::testing::Matcher ast_matcher_; +}; + +} // namespace TestingInternal +} // namespace Carbon + +#endif // EXECUTABLE_SEMANTICS_SYNTAX_PARSE_TEST_MATCHERS_INTERNAL_H_ diff --git a/executable_semantics/syntax/parser.ypp b/executable_semantics/syntax/parser.ypp index f79862c1b31e..d534977b018e 100644 --- a/executable_semantics/syntax/parser.ypp +++ b/executable_semantics/syntax/parser.ypp @@ -190,6 +190,7 @@ TRUE TYPE UNDERSCORE + UNIMPL_EXAMPLE VAR WHILE // table-end @@ -223,6 +224,7 @@ // same precedence as POSTFIX_STAR. %precedence POSTFIX_STAR UNARY_STAR %left PERIOD ARROW +%nonassoc UNIMPL_EXAMPLE %precedence LEFT_PARENTHESIS RIGHT_PARENTHESIS @@ -381,6 +383,11 @@ expression: } | FN_TYPE tuple ARROW expression { $$ = arena->New(context.source_loc(), $2, $4); } +| expression UNIMPL_EXAMPLE expression + { + $$ = arena->New(context.source_loc(), + "ExampleInfix", $1, $3); + } ; designator: PERIOD identifier { $$ = $2; } ; diff --git a/executable_semantics/syntax/unimplemented_example_test.cpp b/executable_semantics/syntax/unimplemented_example_test.cpp new file mode 100644 index 000000000000..64f8397931bb --- /dev/null +++ b/executable_semantics/syntax/unimplemented_example_test.cpp @@ -0,0 +1,36 @@ +// Part of the Carbon Language project, under the Apache License v2.0 with LLVM +// Exceptions. See /LICENSE for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#include +#include + +#include "executable_semantics/ast/ast_test_matchers.h" +#include "executable_semantics/syntax/parse.h" +#include "executable_semantics/syntax/parse_test_matchers.h" + +namespace Carbon { +namespace { + +using ::testing::ElementsAre; + +TEST(UnimplementedExampleTest, VerifyPrecedence) { + static constexpr std::string_view Program = R"( + package ExecutableSemanticsTest api; + fn Main() -> i32 { + return 1 __unimplemented_example_infix 2 + 3; + } + )"; + Arena arena; + EXPECT_THAT(ParseFromString(&arena, "dummy.carbon", Program, false), + ParsedAs(ASTDeclarations( + ElementsAre(MatchesFunctionDeclaration().WithBody( + BlockContentsAre(ElementsAre(MatchesReturn(MatchesAdd( + MatchesUnimplementedExpression( + "ExampleInfix", ElementsAre(MatchesLiteral(1), + MatchesLiteral(2))), + MatchesLiteral(3)))))))))); +} + +} // namespace +} // namespace Carbon