mirror of
https://github.com/carbon-language/carbon-lang.git
synced 2026-09-24 22:02:23 +01:00
Support parsing and testing unimplemented expressions (#957)
Co-authored-by: Jon Meow <46229924+jonmeow@users.noreply.github.com>
This commit is contained in:
committed by
GitHub
co-authored by
Jon Meow
parent
be0c1e9da6
commit
dc5e62fc7a
@@ -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
|
||||
|
||||
@@ -44,6 +44,7 @@ cc_library(
|
||||
],
|
||||
hdrs = ["ast_test_matchers.h"],
|
||||
deps = [
|
||||
":ast",
|
||||
":ast_node",
|
||||
":declaration",
|
||||
":expression",
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -25,15 +25,13 @@ namespace Carbon {
|
||||
|
||||
// Matches a Block node whose .statements() match `matcher`.
|
||||
inline auto BlockContentsAre(
|
||||
::testing::Matcher<llvm::ArrayRef<Nonnull<const Statement*>>> matcher)
|
||||
-> TestingInternal::BlockContentsMatcher {
|
||||
::testing::Matcher<llvm::ArrayRef<Nonnull<const Statement*>>> 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<AstNode> lhs,
|
||||
::testing::Matcher<AstNode> rhs)
|
||||
-> TestingInternal::BinaryOperatorExpressionMatcher {
|
||||
::testing::Matcher<AstNode> rhs) {
|
||||
return TestingInternal::BinaryOperatorExpressionMatcher(
|
||||
Operator::Mul, std::move(lhs), std::move(rhs));
|
||||
}
|
||||
|
||||
inline auto MatchesAdd(::testing::Matcher<AstNode> lhs,
|
||||
::testing::Matcher<AstNode> rhs)
|
||||
-> TestingInternal::BinaryOperatorExpressionMatcher {
|
||||
::testing::Matcher<AstNode> rhs) {
|
||||
return TestingInternal::BinaryOperatorExpressionMatcher(
|
||||
Operator::Add, std::move(lhs), std::move(rhs));
|
||||
}
|
||||
|
||||
inline auto MatchesAnd(::testing::Matcher<AstNode> lhs,
|
||||
::testing::Matcher<AstNode> rhs)
|
||||
-> TestingInternal::BinaryOperatorExpressionMatcher {
|
||||
::testing::Matcher<AstNode> rhs) {
|
||||
return TestingInternal::BinaryOperatorExpressionMatcher(
|
||||
Operator::And, std::move(lhs), std::move(rhs));
|
||||
}
|
||||
|
||||
inline auto MatchesEq(::testing::Matcher<AstNode> lhs,
|
||||
::testing::Matcher<AstNode> rhs)
|
||||
-> TestingInternal::BinaryOperatorExpressionMatcher {
|
||||
::testing::Matcher<AstNode> rhs) {
|
||||
return TestingInternal::BinaryOperatorExpressionMatcher(
|
||||
Operator::Eq, std::move(lhs), std::move(rhs));
|
||||
}
|
||||
|
||||
inline auto MatchesOr(::testing::Matcher<AstNode> lhs,
|
||||
::testing::Matcher<AstNode> rhs)
|
||||
-> TestingInternal::BinaryOperatorExpressionMatcher {
|
||||
::testing::Matcher<AstNode> rhs) {
|
||||
return TestingInternal::BinaryOperatorExpressionMatcher(
|
||||
Operator::Or, std::move(lhs), std::move(rhs));
|
||||
}
|
||||
|
||||
inline auto MatchesSub(::testing::Matcher<AstNode> lhs,
|
||||
::testing::Matcher<AstNode> rhs)
|
||||
-> TestingInternal::BinaryOperatorExpressionMatcher {
|
||||
::testing::Matcher<AstNode> 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<AstNode> matcher)
|
||||
-> TestingInternal::MatchesReturnMatcher {
|
||||
inline auto MatchesReturn(::testing::Matcher<AstNode> matcher) {
|
||||
return TestingInternal::MatchesReturnMatcher(matcher);
|
||||
}
|
||||
|
||||
@@ -113,11 +104,29 @@ inline auto MatchesReturn(::testing::Matcher<AstNode> 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<llvm::ArrayRef<Nonnull<const AstNode*>>>
|
||||
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<std::vector<Nonnull<Declaration*>>>
|
||||
declarations_matcher) {
|
||||
return TestingInternal::ASTDeclarationsMatcher(
|
||||
std::move(declarations_matcher));
|
||||
}
|
||||
|
||||
} // namespace Carbon
|
||||
|
||||
#endif // EXECUTABLE_SEMANTICS_AST_AST_TEST_MATCHERS_H_
|
||||
|
||||
@@ -10,7 +10,9 @@
|
||||
namespace Carbon {
|
||||
namespace TestingInternal {
|
||||
|
||||
auto BlockContentsMatcher::MatchAndExplain(
|
||||
AstNodeMatcherBase::~AstNodeMatcherBase() = default;
|
||||
|
||||
auto BlockContentsMatcher::MatchAndExplainImpl(
|
||||
Nonnull<const AstNode*> node, ::testing::MatchResultListener* out) const
|
||||
-> bool {
|
||||
const auto* block = llvm::dyn_cast<Block>(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<IntLiteral>(node);
|
||||
@@ -35,7 +37,7 @@ auto MatchesIntLiteralMatcher::MatchAndExplain(
|
||||
return matched;
|
||||
}
|
||||
|
||||
auto BinaryOperatorExpressionMatcher::MatchAndExplain(
|
||||
auto BinaryOperatorExpressionMatcher::MatchAndExplainImpl(
|
||||
Nonnull<const AstNode*> node, ::testing::MatchResultListener* out) const
|
||||
-> bool {
|
||||
const auto* op = llvm::dyn_cast<PrimitiveOperatorExpression>(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<Return>(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<UnimplementedExpression>(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
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
|
||||
#include <ostream>
|
||||
|
||||
#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<llvm::ArrayRef<Nonnull<const Statement*>>> 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<const AstNode*> node,
|
||||
::testing::MatchResultListener* out) const -> bool;
|
||||
|
||||
private:
|
||||
testing::Matcher<llvm::ArrayRef<Nonnull<const Statement*>>> 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<const AstNode*> 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<const AstNode*> 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<llvm::ArrayRef<Nonnull<const Statement*>>> 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<const AstNode*> node,
|
||||
::testing::MatchResultListener* out) const
|
||||
-> bool override;
|
||||
|
||||
testing::Matcher<llvm::ArrayRef<Nonnull<const Statement*>>> 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<AstNode> 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<const AstNode*> 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<const AstNode*> node,
|
||||
::testing::MatchResultListener* out) const
|
||||
-> bool override;
|
||||
|
||||
Operator op_;
|
||||
::testing::Matcher<AstNode> 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<AstNode> 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<AstNode>> 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<std::string> 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<std::string>> name_matcher_;
|
||||
std::optional<::testing::Matcher<AstNode>> 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<llvm::ArrayRef<Nonnull<const AstNode*>>>
|
||||
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<const AstNode*> node,
|
||||
::testing::MatchResultListener* listener) const
|
||||
-> bool override;
|
||||
|
||||
std::string label_;
|
||||
::testing::Matcher<llvm::ArrayRef<Nonnull<const AstNode*>>> 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<std::vector<Nonnull<Declaration*>>>
|
||||
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<std::vector<Nonnull<Declaration*>>> declarations_matcher_;
|
||||
};
|
||||
|
||||
} // namespace TestingInternal
|
||||
} // namespace Carbon
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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<ChoiceDeclaration>(*this);
|
||||
out << "choice " << choice.name() << " {\n";
|
||||
for (Nonnull<const AlternativeSignature*> 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<const GenericBinding*> 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
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
@@ -184,6 +184,17 @@ void Expression::Print(llvm::raw_ostream& out) const {
|
||||
out << "print";
|
||||
}
|
||||
out << ")";
|
||||
break;
|
||||
case ExpressionKind::UnimplementedExpression: {
|
||||
const auto& unimplemented = cast<UnimplementedExpression>(*this);
|
||||
out << "UnimplementedExpression<" << unimplemented.label() << ">(";
|
||||
llvm::ListSeparator sep;
|
||||
for (Nonnull<const AstNode*> child : unimplemented.children()) {
|
||||
out << sep << *child;
|
||||
}
|
||||
out << ")";
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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<TupleLiteral*> 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<AstNode*>. The label
|
||||
// should correspond roughly to the name of the class that will eventually
|
||||
// replace this usage of UnimplementedExpression.
|
||||
template <typename... Children>
|
||||
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<Nonnull<const AstNode*>> {
|
||||
return children_;
|
||||
}
|
||||
|
||||
private:
|
||||
void AddChildren() {}
|
||||
|
||||
template <typename... Children>
|
||||
void AddChildren(Nonnull<AstNode*> child, Children... children) {
|
||||
children_.push_back(child);
|
||||
AddChildren(children...);
|
||||
}
|
||||
|
||||
std::string label_;
|
||||
std::vector<Nonnull<AstNode*>> children_;
|
||||
};
|
||||
|
||||
// Converts paren_contents to an Expression, interpreting the parentheses as
|
||||
// grouping if their contents permit that interpretation, or as forming a
|
||||
// tuple otherwise.
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -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<StringType>());
|
||||
}
|
||||
case ExpressionKind::UnimplementedExpression:
|
||||
FATAL() << "Unimplemented: " << exp;
|
||||
} // switch (exp->kind)
|
||||
}
|
||||
|
||||
|
||||
@@ -750,6 +750,8 @@ auto TypeChecker::TypeCheckExp(Nonnull<Expression*> e, TypeEnv types,
|
||||
e->set_value_category(Expression::ValueCategory::Let);
|
||||
SetStaticType(e, arena_->New<TypeType>());
|
||||
return TCResult(types);
|
||||
case ExpressionKind::UnimplementedExpression:
|
||||
FATAL() << "Unimplemented: " << *e;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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",
|
||||
],
|
||||
)
|
||||
|
||||
@@ -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 */
|
||||
|
||||
@@ -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 <gmock/gmock.h>
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#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> ast_matcher) {
|
||||
return TestingInternal::ParsedAsMatcher(std::move(ast_matcher));
|
||||
}
|
||||
|
||||
} // namespace Carbon
|
||||
|
||||
#endif // EXECUTABLE_SEMANTICS_SYNTAX_PARSE_TEST_MATCHERS_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 <gmock/gmock.h>
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <ostream>
|
||||
#include <variant>
|
||||
|
||||
#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> 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<AST, SyntaxErrorCode>& result,
|
||||
::testing::MatchResultListener* listener) const -> bool {
|
||||
if (std::holds_alternative<SyntaxErrorCode>(result)) {
|
||||
*listener << "holds error code " << std::get<SyntaxErrorCode>(result);
|
||||
return false;
|
||||
} else {
|
||||
*listener << "is a successful parse whose ";
|
||||
return ast_matcher_.MatchAndExplain(std::get<AST>(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> ast_matcher_;
|
||||
};
|
||||
|
||||
} // namespace TestingInternal
|
||||
} // namespace Carbon
|
||||
|
||||
#endif // EXECUTABLE_SEMANTICS_SYNTAX_PARSE_TEST_MATCHERS_INTERNAL_H_
|
||||
@@ -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<FunctionTypeLiteral>(context.source_loc(), $2, $4); }
|
||||
| expression UNIMPL_EXAMPLE expression
|
||||
{
|
||||
$$ = arena->New<UnimplementedExpression>(context.source_loc(),
|
||||
"ExampleInfix", $1, $3);
|
||||
}
|
||||
;
|
||||
designator: PERIOD identifier { $$ = $2; }
|
||||
;
|
||||
|
||||
@@ -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 <gmock/gmock.h>
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#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
|
||||
Reference in New Issue
Block a user