Support parsing and testing unimplemented expressions (#957)

Co-authored-by: Jon Meow <46229924+jonmeow@users.noreply.github.com>
This commit is contained in:
Geoff Romer
2021-11-30 12:29:15 -08:00
committed by GitHub
co-authored by Jon Meow
parent be0c1e9da6
commit dc5e62fc7a
23 changed files with 463 additions and 147 deletions
+6 -1
View File
@@ -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
+1
View File
@@ -44,6 +44,7 @@ cc_library(
],
hdrs = ["ast_test_matchers.h"],
deps = [
":ast",
":ast_node",
":declaration",
":expression",
+3
View File
@@ -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
+1
View File
@@ -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;
+30 -21
View File
@@ -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", {}, &params,
ReturnTerm::Omitted(DummyLoc), &body);
AST ast = {.declarations = {&decl}};
EXPECT_THAT(ast, ASTDeclarations(ElementsAre(MatchesFunctionDeclaration())));
EXPECT_THAT(ast, Not(ASTDeclarations(IsEmpty())));
}
} // namespace
} // namespace Carbon
+12 -8
View File
@@ -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
+5 -2
View File
@@ -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());
}
+11
View File
@@ -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;
}
}
}
+40 -2
View File
@@ -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.
+1 -2
View File
@@ -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());
+1 -2
View File
@@ -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());
+1 -2
View File
@@ -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
View File
@@ -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",
],
)
+2
View File
@@ -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_
+7
View File
@@ -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