Factor out a Pattern sum type from Expression (#685)

`Pattern` is intended to pilot some changes I would like to apply to all our sum types:
- The alternatives are expressed as derived classes rather than members of a `std::variant`.
- The alternatives are classes in the [style guide sense](https://google.github.io/styleguide/cppguide.html#Structs_vs._Classes), meaning they can have invariants, but can't have public data members.
- Creating an object is expressed using a constructor rather than a factory function.
- Accessing an alternative is expressed as a cast (using LLVM's RTTI system) rather than `std::get` or a `Get` method.

Co-authored-by: Jon Meow <46229924+jonmeow@users.noreply.github.com>
This commit is contained in:
Geoff Romer
2021-07-30 12:24:12 -07:00
committed by GitHub
co-authored by Jon Meow
parent b08f6bb0f1
commit 6ac3adfa53
31 changed files with 1302 additions and 575 deletions
+45
View File
@@ -16,6 +16,7 @@ cc_library(
deps = [
":function_definition",
":member",
":pattern",
":struct_definition",
"//common:ostream",
"//executable_semantics/interpreter:address",
@@ -33,10 +34,26 @@ cc_library(
"//common:ostream",
"//executable_semantics/common:arena",
"//executable_semantics/common:error",
"//executable_semantics/syntax:paren_contents",
"@llvm-project//llvm:Support",
],
)
cc_test(
name = "expression_test",
srcs = ["expression_test.cpp"],
env = {
# TODO(#580): Remove this when leaks are fixed.
"ASAN_OPTIONS": "detect_leaks=0",
},
deps = [
":expression",
"//executable_semantics/syntax:paren_contents",
"@llvm-project//llvm:gtest",
"@llvm-project//llvm:gtest_main",
],
)
cc_library(
name = "function_definition",
srcs = ["function_definition.cpp"],
@@ -52,19 +69,47 @@ cc_library(
name = "member",
srcs = ["member.cpp"],
hdrs = ["member.h"],
deps = [
":pattern",
"//common:ostream",
],
)
cc_library(
name = "pattern",
srcs = ["pattern.cpp"],
hdrs = ["pattern.h"],
deps = [
":expression",
"//common:ostream",
"//executable_semantics/common:error",
"@llvm-project//llvm:Support",
],
)
cc_test(
name = "pattern_test",
srcs = ["pattern_test.cpp"],
env = {
# TODO(#580): Remove this when leaks are fixed.
"ASAN_OPTIONS": "detect_leaks=0",
},
deps = [
":pattern",
"//executable_semantics/syntax:paren_contents",
"@llvm-project//llvm:Support",
"@llvm-project//llvm:gtest",
"@llvm-project//llvm:gtest_main",
],
)
cc_library(
name = "statement",
srcs = ["statement.cpp"],
hdrs = ["statement.h"],
deps = [
":expression",
":pattern",
"//common:check",
"//common:ostream",
"@llvm-project//llvm:Support",
+4 -6
View File
@@ -35,14 +35,13 @@ auto Declaration::MakeChoiceDeclaration(
return d;
}
auto Declaration::MakeVariableDeclaration(int source_location, std::string name,
const Expression* type,
auto Declaration::MakeVariableDeclaration(int source_location,
const BindingPattern* binding,
const Expression* initializer)
-> const Declaration {
Declaration d;
d.value = VariableDeclaration({.source_location = source_location,
.name = std::move(name),
.type = type,
.binding = binding,
.initializer = initializer});
return d;
}
@@ -91,8 +90,7 @@ void Declaration::Print(llvm::raw_ostream& out) const {
case DeclarationKind::VariableDeclaration: {
const auto& var = GetVariableDeclaration();
out << "var " << *var.type << " : " << var.name << " = "
<< *var.initializer << "\n";
out << "var " << *var.binding << " = " << *var.initializer << "\n";
break;
}
}
+7 -4
View File
@@ -11,6 +11,7 @@
#include "common/ostream.h"
#include "executable_semantics/ast/function_definition.h"
#include "executable_semantics/ast/member.h"
#include "executable_semantics/ast/pattern.h"
#include "executable_semantics/ast/struct_definition.h"
#include "executable_semantics/interpreter/address.h"
#include "executable_semantics/interpreter/dictionary.h"
@@ -58,8 +59,10 @@ struct ChoiceDeclaration {
struct VariableDeclaration {
static constexpr DeclarationKind Kind = DeclarationKind::VariableDeclaration;
int source_location;
std::string name;
const Expression* type;
// TODO: split this into a non-optional name and a type, initialized by
// a constructor that takes a BindingPattern and handles errors like a
// missing name.
const BindingPattern* binding;
const Expression* initializer;
};
@@ -74,8 +77,8 @@ class Declaration {
int line_num, std::string name,
std::list<std::pair<std::string, const Expression*>> alternatives)
-> const Declaration;
static auto MakeVariableDeclaration(int source_location, std::string name,
const Expression* type,
static auto MakeVariableDeclaration(int source_location,
const BindingPattern* binding,
const Expression* initializer)
-> const Declaration;
+21 -49
View File
@@ -4,6 +4,8 @@
#include "executable_semantics/ast/expression.h"
#include <optional>
#include "executable_semantics/common/arena.h"
#include "executable_semantics/common/error.h"
#include "llvm/ADT/StringExtras.h"
@@ -11,6 +13,24 @@
namespace Carbon {
auto ExpressionFromParenContents(
int line_num, const ParenContents<Expression>& paren_contents)
-> const Expression* {
std::optional<const Expression*> single_term = paren_contents.SingleTerm();
if (single_term.has_value()) {
return *single_term;
} else {
return TupleExpressionFromParenContents(line_num, paren_contents);
}
}
auto TupleExpressionFromParenContents(
int line_num, const ParenContents<Expression>& paren_contents)
-> const Expression* {
return Expression::MakeTupleLiteral(
line_num, paren_contents.TupleElements<FieldInitializer>(line_num));
}
auto Expression::GetIdentifierExpression() const
-> const IdentifierExpression& {
return std::get<IdentifierExpression>(value);
@@ -25,10 +45,6 @@ auto Expression::GetIndexExpression() const -> const IndexExpression& {
return std::get<IndexExpression>(value);
}
auto Expression::GetBindingExpression() const -> const BindingExpression& {
return std::get<BindingExpression>(value);
}
auto Expression::GetIntLiteral() const -> int {
return std::get<IntLiteral>(value).value;
}
@@ -75,13 +91,6 @@ auto Expression::MakeBoolTypeLiteral(int line_num) -> const Expression* {
return t;
}
auto Expression::MakeAutoTypeLiteral(int line_num) -> const Expression* {
auto* t = global_arena->New<Expression>();
t->line_num = line_num;
t->value = AutoTypeLiteral();
return t;
}
// Returns a Continuation type AST node at the given source location.
auto Expression::MakeContinuationTypeLiteral(int line_num)
-> const Expression* {
@@ -108,16 +117,6 @@ auto Expression::MakeIdentifierExpression(int line_num, std::string var)
return v;
}
auto Expression::MakeBindingExpression(int line_num,
std::optional<std::string> var,
const Expression* type)
-> const Expression* {
auto* v = global_arena->New<Expression>();
v->line_num = line_num;
v->value = BindingExpression({.name = std::move(var), .type = type});
return v;
}
auto Expression::MakeIntLiteral(int line_num, int i) -> const Expression* {
auto* e = global_arena->New<Expression>();
e->line_num = line_num;
@@ -166,21 +165,7 @@ auto Expression::MakeTupleLiteral(int line_num,
-> const Expression* {
auto* e = global_arena->New<Expression>();
e->line_num = line_num;
int i = 0;
bool seen_named_member = false;
for (auto& arg : args) {
if (arg.name == "") {
if (seen_named_member) {
FATAL_USER_ERROR(line_num)
<< "positional members must come before named members";
}
arg.name = std::to_string(i);
++i;
} else {
seen_named_member = true;
}
}
e->value = TupleLiteral({.fields = args});
e->value = TupleLiteral({.fields = std::move(args)});
return e;
}
@@ -269,16 +254,6 @@ void Expression::Print(llvm::raw_ostream& out) const {
case ExpressionKind::IdentifierExpression:
out << GetIdentifierExpression().name;
break;
case ExpressionKind::BindingExpression: {
const BindingExpression& binding = GetBindingExpression();
if (binding.name.has_value()) {
out << *binding.name;
} else {
out << "_";
}
out << ": " << *binding.type;
break;
}
case ExpressionKind::CallExpression:
out << *GetCallExpression().function;
if (GetCallExpression().argument->tag() == ExpressionKind::TupleLiteral) {
@@ -296,9 +271,6 @@ void Expression::Print(llvm::raw_ostream& out) const {
case ExpressionKind::TypeTypeLiteral:
out << "Type";
break;
case ExpressionKind::AutoTypeLiteral:
out << "auto";
break;
case ExpressionKind::ContinuationTypeLiteral:
out << "Continuation";
break;
+22 -23
View File
@@ -11,15 +11,33 @@
#include <vector>
#include "common/ostream.h"
#include "executable_semantics/syntax/paren_contents.h"
#include "llvm/Support/Compiler.h"
namespace Carbon {
struct Expression;
class Pattern;
// Converts paren_contents to an Expression, interpreting the parentheses as
// grouping if their contents permit that interpretation, or as forming a
// tuple otherwise.
auto ExpressionFromParenContents(
int line_num, const ParenContents<Expression>& paren_contents)
-> const Expression*;
// Converts paren_contents to an Expression, interpreting the parentheses as
// forming a tuple.
auto TupleExpressionFromParenContents(
int line_num, const ParenContents<Expression>& paren_contents)
-> const Expression*;
// A FieldInitializer represents the initialization of a single tuple field.
struct FieldInitializer {
// The field name. For a positional field, this may be empty.
FieldInitializer(std::string name, const Expression* expression)
: name(std::move(name)), expression(expression) {}
// The field name. Cannot be empty.
std::string name;
// The expression that initializes the field.
@@ -27,7 +45,6 @@ struct FieldInitializer {
};
enum class ExpressionKind {
AutoTypeLiteral,
BoolTypeLiteral,
BoolLiteral,
CallExpression,
@@ -37,7 +54,6 @@ enum class ExpressionKind {
IntTypeLiteral,
ContinuationTypeLiteral, // The type of a continuation value.
IntLiteral,
BindingExpression,
PrimitiveOperatorExpression,
TupleLiteral,
TypeTypeLiteral,
@@ -76,13 +92,6 @@ struct IndexExpression {
const Expression* offset;
};
struct BindingExpression {
static constexpr ExpressionKind Kind = ExpressionKind::BindingExpression;
// nullopt represents the `_` placeholder.
std::optional<std::string> name;
const Expression* type;
};
struct IntLiteral {
static constexpr ExpressionKind Kind = ExpressionKind::IntLiteral;
int value;
@@ -117,10 +126,6 @@ struct FunctionTypeLiteral {
const Expression* return_type;
};
struct AutoTypeLiteral {
static constexpr ExpressionKind Kind = ExpressionKind::AutoTypeLiteral;
};
struct BoolTypeLiteral {
static constexpr ExpressionKind Kind = ExpressionKind::BoolTypeLiteral;
};
@@ -141,10 +146,6 @@ struct TypeTypeLiteral {
struct Expression {
static auto MakeIdentifierExpression(int line_num, std::string var)
-> const Expression*;
static auto MakeBindingExpression(int line_num,
std::optional<std::string> var,
const Expression* type)
-> const Expression*;
static auto MakeIntLiteral(int line_num, int i) -> const Expression*;
static auto MakeBoolLiteral(int line_num, bool b) -> const Expression*;
static auto MakePrimitiveOperatorExpression(
@@ -164,13 +165,11 @@ struct Expression {
static auto MakeFunctionTypeLiteral(int line_num, const Expression* param,
const Expression* ret)
-> const Expression*;
static auto MakeAutoTypeLiteral(int line_num) -> const Expression*;
static auto MakeContinuationTypeLiteral(int line_num) -> const Expression*;
auto GetIdentifierExpression() const -> const IdentifierExpression&;
auto GetFieldAccessExpression() const -> const FieldAccessExpression&;
auto GetIndexExpression() const -> const IndexExpression&;
auto GetBindingExpression() const -> const BindingExpression&;
auto GetIntLiteral() const -> int;
auto GetBoolLiteral() const -> bool;
auto GetTupleLiteral() const -> const TupleLiteral&;
@@ -190,10 +189,10 @@ struct Expression {
private:
std::variant<IdentifierExpression, FieldAccessExpression, IndexExpression,
BindingExpression, IntLiteral, BoolLiteral, TupleLiteral,
IntLiteral, BoolLiteral, TupleLiteral,
PrimitiveOperatorExpression, CallExpression, FunctionTypeLiteral,
AutoTypeLiteral, BoolTypeLiteral, IntTypeLiteral,
ContinuationTypeLiteral, TypeTypeLiteral>
BoolTypeLiteral, IntTypeLiteral, ContinuationTypeLiteral,
TypeTypeLiteral>
value;
};
@@ -0,0 +1,137 @@
// 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 "executable_semantics/ast/expression.h"
#include <string>
#include "executable_semantics/syntax/paren_contents.h"
#include "gmock/gmock.h"
#include "gtest/gtest.h"
namespace Carbon {
namespace {
using testing::ElementsAre;
using testing::IsEmpty;
// Matches a FieldInitializer named `name` whose `expression` is an
// `IntLiteral`
MATCHER_P(IntFieldNamed, name, "") {
return arg.name == std::string(name) &&
arg.expression->tag() == ExpressionKind::IntLiteral;
}
TEST(ExpressionTest, EmptyAsExpression) {
ParenContents<Expression> contents = {.elements = {},
.has_trailing_comma = false};
const Expression* expression =
ExpressionFromParenContents(/*line_num=*/1, contents);
EXPECT_EQ(expression->line_num, 1);
ASSERT_EQ(expression->tag(), ExpressionKind::TupleLiteral);
EXPECT_THAT(expression->GetTupleLiteral().fields, IsEmpty());
}
TEST(ExpressionTest, EmptyAsTuple) {
ParenContents<Expression> contents = {.elements = {},
.has_trailing_comma = false};
const Expression* tuple =
TupleExpressionFromParenContents(/*line_num=*/1, contents);
EXPECT_EQ(tuple->line_num, 1);
ASSERT_EQ(tuple->tag(), ExpressionKind::TupleLiteral);
EXPECT_THAT(tuple->GetTupleLiteral().fields, IsEmpty());
}
TEST(ExpressionTest, UnaryNoCommaAsExpression) {
// Equivalent to a code fragment like
// ```
// (
// 42
// )
// ```
ParenContents<Expression> contents = {
.elements = {{.name = std::nullopt,
.term = Expression::MakeIntLiteral(/*line_num=*/2, 42)}},
.has_trailing_comma = false};
const Expression* expression =
ExpressionFromParenContents(/*line_num=*/1, contents);
EXPECT_EQ(expression->line_num, 2);
ASSERT_EQ(expression->tag(), ExpressionKind::IntLiteral);
}
TEST(ExpressionTest, UnaryNoCommaAsTuple) {
ParenContents<Expression> contents = {
.elements = {{.name = std::nullopt,
.term = Expression::MakeIntLiteral(/*line_num=*/2, 42)}},
.has_trailing_comma = false};
const Expression* tuple =
TupleExpressionFromParenContents(/*line_num=*/1, contents);
EXPECT_EQ(tuple->line_num, 1);
ASSERT_EQ(tuple->tag(), ExpressionKind::TupleLiteral);
EXPECT_THAT(tuple->GetTupleLiteral().fields, ElementsAre(IntFieldNamed("0")));
}
TEST(ExpressionTest, UnaryWithCommaAsExpression) {
ParenContents<Expression> contents = {
.elements = {{.name = std::nullopt,
.term = Expression::MakeIntLiteral(/*line_num=*/2, 42)}},
.has_trailing_comma = true};
const Expression* expression =
ExpressionFromParenContents(/*line_num=*/1, contents);
EXPECT_EQ(expression->line_num, 1);
ASSERT_EQ(expression->tag(), ExpressionKind::TupleLiteral);
EXPECT_THAT(expression->GetTupleLiteral().fields,
ElementsAre(IntFieldNamed("0")));
}
TEST(ExpressionTest, UnaryWithCommaAsTuple) {
ParenContents<Expression> contents = {
.elements = {{.name = std::nullopt,
.term = Expression::MakeIntLiteral(/*line_num=*/2, 42)}},
.has_trailing_comma = true};
const Expression* tuple =
TupleExpressionFromParenContents(/*line_num=*/1, contents);
EXPECT_EQ(tuple->line_num, 1);
ASSERT_EQ(tuple->tag(), ExpressionKind::TupleLiteral);
EXPECT_THAT(tuple->GetTupleLiteral().fields, ElementsAre(IntFieldNamed("0")));
}
TEST(ExpressionTest, BinaryAsExpression) {
ParenContents<Expression> contents = {
.elements = {{.name = std::nullopt,
.term = Expression::MakeIntLiteral(/*line_num=*/2, 42)},
{.name = std::nullopt,
.term = Expression::MakeIntLiteral(/*line_num=*/3, 42)}},
.has_trailing_comma = true};
const Expression* expression =
ExpressionFromParenContents(/*line_num=*/1, contents);
EXPECT_EQ(expression->line_num, 1);
ASSERT_EQ(expression->tag(), ExpressionKind::TupleLiteral);
EXPECT_THAT(expression->GetTupleLiteral().fields,
ElementsAre(IntFieldNamed("0"), IntFieldNamed("1")));
}
TEST(ExpressionTest, BinaryAsTuple) {
ParenContents<Expression> contents = {
.elements = {{.name = std::nullopt,
.term = Expression::MakeIntLiteral(/*line_num=*/2, 42)},
{.name = std::nullopt,
.term = Expression::MakeIntLiteral(/*line_num=*/3, 42)}},
.has_trailing_comma = true};
const Expression* tuple =
TupleExpressionFromParenContents(/*line_num=*/1, contents);
EXPECT_EQ(tuple->line_num, 1);
ASSERT_EQ(tuple->tag(), ExpressionKind::TupleLiteral);
EXPECT_THAT(tuple->GetTupleLiteral().fields,
ElementsAre(IntFieldNamed("0"), IntFieldNamed("1")));
}
} // namespace
} // namespace Carbon
@@ -7,6 +7,7 @@
#include "common/ostream.h"
#include "executable_semantics/ast/expression.h"
#include "executable_semantics/ast/pattern.h"
#include "executable_semantics/ast/statement.h"
#include "llvm/Support/Compiler.h"
@@ -23,8 +24,8 @@ struct FunctionDefinition {
FunctionDefinition() = default;
FunctionDefinition(int line_num, std::string name,
std::vector<GenericBinding> deduced_params,
const Expression* param_pattern,
const Expression* return_type, const Statement* body)
const TuplePattern* param_pattern,
const Pattern* return_type, const Statement* body)
: line_num(line_num),
name(std::move(name)),
deduced_parameters(deduced_params),
@@ -39,8 +40,8 @@ struct FunctionDefinition {
int line_num;
std::string name;
std::vector<GenericBinding> deduced_parameters;
const Expression* param_pattern;
const Expression* return_type;
const TuplePattern* param_pattern;
const Pattern* return_type;
const Statement* body;
};
+4 -4
View File
@@ -6,11 +6,11 @@
namespace Carbon {
auto Member::MakeFieldMember(int line_num, std::string name,
const Expression* type) -> Member* {
auto Member::MakeFieldMember(int line_num, const BindingPattern* binding)
-> Member* {
auto m = new Member();
m->line_num = line_num;
m->value = FieldMember({.name = std::move(name), .type = type});
m->value = FieldMember({.binding = binding});
return m;
}
@@ -22,7 +22,7 @@ void Member::Print(llvm::raw_ostream& out) const {
switch (tag()) {
case MemberKind::FieldMember:
const auto& field = GetFieldMember();
out << "var " << field.name << " : " << *field.type << ";\n";
out << "var " << field.binding << ";\n";
break;
}
}
+7 -4
View File
@@ -9,6 +9,7 @@
#include "common/ostream.h"
#include "executable_semantics/ast/expression.h"
#include "executable_semantics/ast/pattern.h"
#include "llvm/Support/Compiler.h"
namespace Carbon {
@@ -17,13 +18,15 @@ enum class MemberKind { FieldMember };
struct FieldMember {
static constexpr MemberKind Kind = MemberKind::FieldMember;
std::string name;
const Expression* type;
// TODO: split this into a non-optional name and a type, initialized by
// a constructor that takes a BindingPattern and handles errors like a
// missing name.
const BindingPattern* binding;
};
struct Member {
static auto MakeFieldMember(int line_num, std::string name,
const Expression* type) -> Member*;
static auto MakeFieldMember(int line_num, const BindingPattern* binding)
-> Member*;
auto GetFieldMember() const -> const FieldMember&;
+106
View File
@@ -0,0 +1,106 @@
// 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 "executable_semantics/ast/pattern.h"
#include <string>
#include "common/ostream.h"
#include "executable_semantics/ast/expression.h"
#include "executable_semantics/common/error.h"
#include "llvm/ADT/StringExtras.h"
#include "llvm/Support/Casting.h"
namespace Carbon {
using llvm::cast;
void Pattern::Print(llvm::raw_ostream& out) const {
switch (Tag()) {
case Kind::AutoPattern:
out << "auto";
break;
case Kind::BindingPattern: {
const auto& binding = cast<BindingPattern>(*this);
if (binding.Name().has_value()) {
out << *binding.Name();
} else {
out << "_";
}
out << ": " << *binding.Type();
break;
}
case Kind::TuplePattern: {
const auto& tuple = cast<TuplePattern>(*this);
out << "(";
llvm::ListSeparator sep;
for (const TuplePattern::Field& field : tuple.Fields()) {
out << sep << field.name << " = " << field.pattern;
}
out << ")";
break;
}
case Kind::AlternativePattern: {
const auto& alternative = cast<AlternativePattern>(*this);
out << alternative.ChoiceType() << "." << alternative.AlternativeName()
<< alternative.Arguments();
break;
}
case Kind::ExpressionPattern:
out << cast<ExpressionPattern>(*this).Expression();
break;
}
}
TuplePattern::TuplePattern(const Expression* tuple_literal)
: Pattern(Kind::TuplePattern, tuple_literal->line_num) {
const auto& tuple = tuple_literal->GetTupleLiteral();
for (const FieldInitializer& init : tuple.fields) {
fields.push_back(Field(init.name, new ExpressionPattern(init.expression)));
}
}
auto PatternFromParenContents(int line_num,
const ParenContents<Pattern>& paren_contents)
-> const Pattern* {
std::optional<const Pattern*> single_term = paren_contents.SingleTerm();
if (single_term.has_value()) {
return *single_term;
} else {
return TuplePatternFromParenContents(line_num, paren_contents);
}
}
auto TuplePatternFromParenContents(int line_num,
const ParenContents<Pattern>& paren_contents)
-> const TuplePattern* {
return new TuplePattern(
line_num, paren_contents.TupleElements<TuplePattern::Field>(line_num));
}
AlternativePattern::AlternativePattern(int line_num,
const Expression* alternative,
const TuplePattern* arguments)
: Pattern(Kind::AlternativePattern, line_num), arguments(arguments) {
if (alternative->tag() != ExpressionKind::FieldAccessExpression) {
FATAL_USER_ERROR(alternative->line_num)
<< "Alternative pattern must have the form of a field access.";
}
const auto& field_access = alternative->GetFieldAccessExpression();
choice_type = field_access.aggregate;
alternative_name = field_access.field;
}
auto ParenExpressionToParenPattern(const ParenContents<Expression>& contents)
-> ParenContents<Pattern> {
ParenContents<Pattern> result = {
.elements = {}, .has_trailing_comma = contents.has_trailing_comma};
for (const auto& element : contents.elements) {
result.elements.push_back(
{.name = element.name, .term = new ExpressionPattern(element.term)});
}
return result;
}
} // namespace Carbon
+199
View File
@@ -0,0 +1,199 @@
// 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_AST_PATTERN_H_
#define EXECUTABLE_SEMANTICS_AST_PATTERN_H_
#include <optional>
#include <string>
#include <vector>
#include "common/ostream.h"
#include "executable_semantics/ast/expression.h"
namespace Carbon {
// Abstract base class of all AST nodes representing patterns.
//
// Pattern and its derived classes support LLVM-style RTTI, including
// llvm::isa, llvm::cast, and llvm::dyn_cast. To support this, every
// class derived from Pattern must provide a `classof` operation, and
// every concrete derived class must have a corresponding enumerator
// in `Kind`; see https://llvm.org/docs/HowToSetUpLLVMStyleRTTI.html for
// details.
class Pattern {
public:
enum class Kind {
AutoPattern,
BindingPattern,
TuplePattern,
AlternativePattern,
ExpressionPattern,
};
Pattern(const Pattern&) = delete;
Pattern& operator=(const Pattern&) = delete;
// Returns the enumerator corresponding to the most-derived type of this
// object.
auto Tag() const -> Kind { return tag; }
auto LineNumber() const -> int { return line_num; }
void Print(llvm::raw_ostream& out) const;
protected:
// Constructs a Pattern representing syntax at the given line number.
// `tag` must be the enumerator corresponding to the most-derived type being
// constructed.
Pattern(Kind tag, int line_num) : tag(tag), line_num(line_num) {}
private:
const Kind tag;
int line_num;
};
// A pattern consisting of the `auto` keyword.
class AutoPattern : public Pattern {
public:
explicit AutoPattern(int line_num) : Pattern(Kind::AutoPattern, line_num) {}
static auto classof(const Pattern* pattern) -> bool {
return pattern->Tag() == Kind::AutoPattern;
}
};
// A pattern that matches a value of a specified type, and optionally binds
// a name to it.
class BindingPattern : public Pattern {
public:
BindingPattern(int line_num, std::optional<std::string> name,
const Pattern* type)
: Pattern(Kind::BindingPattern, line_num),
name(std::move(name)),
type(type) {}
static auto classof(const Pattern* pattern) -> bool {
return pattern->Tag() == Kind::BindingPattern;
}
// The name this pattern binds, if any.
auto Name() const -> const std::optional<std::string>& { return name; }
// The pattern specifying the type of values that this pattern matches.
auto Type() const -> const Pattern* { return type; }
private:
std::optional<std::string> name;
const Pattern* type;
};
// A pattern that matches a tuple value field-wise.
class TuplePattern : public Pattern {
public:
// Represents a portion of a tuple pattern corresponding to a single field.
struct Field {
Field(std::string name, const Pattern* pattern)
: name(std::move(name)), pattern(pattern) {}
// The field name. Cannot be empty
std::string name;
// The pattern the field must match.
const Pattern* pattern;
};
TuplePattern(int line_num, std::vector<Field> fields)
: Pattern(Kind::TuplePattern, line_num), fields(std::move(fields)) {}
// Converts tuple_literal to a TuplePattern, by wrapping each field in an
// ExpressionPattern.
//
// REQUIRES: tuple_literal->Tag() == ExpressionKind::TupleLiteral
explicit TuplePattern(const Expression* tuple_literal);
static auto classof(const Pattern* pattern) -> bool {
return pattern->Tag() == Kind::TuplePattern;
}
auto Fields() const -> const std::vector<Field>& { return fields; }
private:
std::vector<Field> fields;
};
// Converts paren_contents to a Pattern, interpreting the parentheses as
// grouping if their contents permit that interpretation, or as forming a
// tuple otherwise.
auto PatternFromParenContents(int line_num,
const ParenContents<Pattern>& paren_contents)
-> const Pattern*;
// Converts paren_contents to a TuplePattern, interpreting the parentheses as
// forming a tuple.
auto TuplePatternFromParenContents(int line_num,
const ParenContents<Pattern>& paren_contents)
-> const TuplePattern*;
// Converts `contents` to ParenContents<Pattern> by replacing each Expression
// with an ExpressionPattern.
auto ParenExpressionToParenPattern(const ParenContents<Expression>& contents)
-> ParenContents<Pattern>;
// A pattern that matches an alternative of a choice type.
class AlternativePattern : public Pattern {
public:
// Constructs an AlternativePattern that matches a value of the type
// specified by choice_type if it represents an alternative named
// alternative_name, and its arguments match `arguments`.
AlternativePattern(int line_num, const Expression* choice_type,
std::string alternative_name,
const TuplePattern* arguments)
: Pattern(Kind::AlternativePattern, line_num),
choice_type(choice_type),
alternative_name(std::move(alternative_name)),
arguments(arguments) {}
// Constructs an AlternativePattern that matches the alternative specified
// by `alternative`, if its arguments match `arguments`.
AlternativePattern(int line_num, const Expression* alternative,
const TuplePattern* arguments);
static auto classof(const Pattern* pattern) -> bool {
return pattern->Tag() == Kind::AlternativePattern;
}
auto ChoiceType() const -> const Expression* { return choice_type; }
auto AlternativeName() const -> const std::string& {
return alternative_name;
}
auto Arguments() const -> const TuplePattern* { return arguments; }
private:
const Expression* choice_type;
std::string alternative_name;
const TuplePattern* arguments;
};
// A pattern that matches a value if it is equal to the value of a given
// expression.
class ExpressionPattern : public Pattern {
public:
ExpressionPattern(const Expression* expression)
: Pattern(Kind::ExpressionPattern, expression->line_num),
expression(expression) {}
static auto classof(const Pattern* pattern) -> bool {
return pattern->Tag() == Kind::ExpressionPattern;
}
auto Expression() const -> const Expression* { return expression; }
private:
const Carbon::Expression* expression;
};
} // namespace Carbon
#endif // EXECUTABLE_SEMANTICS_AST_PATTERN_H_
+130
View File
@@ -0,0 +1,130 @@
// 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 "executable_semantics/ast/pattern.h"
#include "executable_semantics/ast/expression.h"
#include "executable_semantics/syntax/paren_contents.h"
#include "gmock/gmock.h"
#include "gtest/gtest.h"
#include "llvm/Support/Casting.h"
namespace Carbon {
namespace {
using llvm::cast;
using llvm::isa;
using testing::ElementsAre;
using testing::IsEmpty;
// Matches a TuplePattern::Field named `name` whose `pattern` is an
// `AutoPattern`.
MATCHER_P(AutoFieldNamed, name, "") {
return arg.name == std::string(name) && isa<AutoPattern>(arg.pattern);
}
TEST(PatternTest, EmptyAsPattern) {
ParenContents<Pattern> contents = {.elements = {},
.has_trailing_comma = false};
const Pattern* pattern = PatternFromParenContents(/*line_num=*/1, contents);
EXPECT_EQ(pattern->LineNumber(), 1);
ASSERT_TRUE(isa<TuplePattern>(pattern));
EXPECT_THAT(cast<TuplePattern>(pattern)->Fields(), IsEmpty());
}
TEST(PatternTest, EmptyAsTuplePattern) {
ParenContents<Pattern> contents = {.elements = {},
.has_trailing_comma = false};
const TuplePattern* tuple =
TuplePatternFromParenContents(/*line_num=*/1, contents);
EXPECT_EQ(tuple->LineNumber(), 1);
EXPECT_THAT(tuple->Fields(), IsEmpty());
}
TEST(PatternTest, UnaryNoCommaAsPattern) {
// Equivalent to a code fragment like
// ```
// (
// auto
// )
// ```
ParenContents<Pattern> contents = {
.elements = {{.name = std::nullopt,
.term = new AutoPattern(/*line_num=*/2)}},
.has_trailing_comma = false};
const Pattern* pattern = PatternFromParenContents(/*line_num=*/1, contents);
EXPECT_EQ(pattern->LineNumber(), 2);
ASSERT_TRUE(isa<AutoPattern>(pattern));
}
TEST(PatternTest, UnaryNoCommaAsTuplePattern) {
ParenContents<Pattern> contents = {
.elements = {{.name = std::nullopt,
.term = new AutoPattern(/*line_num=*/2)}},
.has_trailing_comma = false};
const TuplePattern* tuple =
TuplePatternFromParenContents(/*line_num=*/1, contents);
EXPECT_EQ(tuple->LineNumber(), 1);
EXPECT_THAT(tuple->Fields(), ElementsAre(AutoFieldNamed("0")));
}
TEST(PatternTest, UnaryWithCommaAsPattern) {
ParenContents<Pattern> contents = {
.elements = {{.name = std::nullopt,
.term = new AutoPattern(/*line_num=*/2)}},
.has_trailing_comma = true};
const Pattern* pattern = PatternFromParenContents(/*line_num=*/1, contents);
EXPECT_EQ(pattern->LineNumber(), 1);
ASSERT_TRUE(isa<TuplePattern>(pattern));
EXPECT_THAT(cast<TuplePattern>(pattern)->Fields(),
ElementsAre(AutoFieldNamed("0")));
}
TEST(PatternTest, UnaryWithCommaAsTuplePattern) {
ParenContents<Pattern> contents = {
.elements = {{.name = std::nullopt,
.term = new AutoPattern(/*line_num=*/2)}},
.has_trailing_comma = true};
const TuplePattern* tuple =
TuplePatternFromParenContents(/*line_num=*/1, contents);
EXPECT_EQ(tuple->LineNumber(), 1);
EXPECT_THAT(tuple->Fields(), ElementsAre(AutoFieldNamed("0")));
}
TEST(PatternTest, BinaryAsPattern) {
ParenContents<Pattern> contents = {
.elements = {{.name = std::nullopt,
.term = new AutoPattern(/*line_num=*/2)},
{.name = std::nullopt,
.term = new AutoPattern(/*line_num=*/3)}},
.has_trailing_comma = true};
const Pattern* pattern = PatternFromParenContents(/*line_num=*/1, contents);
EXPECT_EQ(pattern->LineNumber(), 1);
ASSERT_TRUE(isa<TuplePattern>(pattern));
EXPECT_THAT(cast<TuplePattern>(pattern)->Fields(),
ElementsAre(AutoFieldNamed("0"), AutoFieldNamed("1")));
}
TEST(PatternTest, BinaryAsTuplePattern) {
ParenContents<Pattern> contents = {
.elements = {{.name = std::nullopt,
.term = new AutoPattern(/*line_num=*/2)},
{.name = std::nullopt,
.term = new AutoPattern(/*line_num=*/3)}},
.has_trailing_comma = true};
const TuplePattern* tuple =
TuplePatternFromParenContents(/*line_num=*/1, contents);
EXPECT_EQ(tuple->LineNumber(), 1);
EXPECT_THAT(tuple->Fields(),
ElementsAre(AutoFieldNamed("0"), AutoFieldNamed("1")));
}
} // namespace
} // namespace Carbon
+2 -2
View File
@@ -76,7 +76,7 @@ auto Statement::MakeAssign(int line_num, const Expression* lhs,
return s;
}
auto Statement::MakeVariableDefinition(int line_num, const Expression* pat,
auto Statement::MakeVariableDefinition(int line_num, const Pattern* pat,
const Expression* init)
-> const Statement* {
auto* s = new Statement();
@@ -142,7 +142,7 @@ auto Statement::MakeBlock(int line_num, const Statement* stmt)
auto Statement::MakeMatch(
int line_num, const Expression* exp,
std::list<std::pair<const Expression*, const Statement*>>* clauses)
std::list<std::pair<const Pattern*, const Statement*>>* clauses)
-> const Statement* {
auto* s = new Statement();
s->line_num = line_num;
+5 -4
View File
@@ -9,6 +9,7 @@
#include "common/ostream.h"
#include "executable_semantics/ast/expression.h"
#include "executable_semantics/ast/pattern.h"
#include "llvm/Support/Compiler.h"
namespace Carbon {
@@ -45,7 +46,7 @@ struct Assign {
struct VariableDefinition {
static constexpr StatementKind Kind = StatementKind::VariableDefinition;
const Expression* pat;
const Pattern* pat;
const Expression* init;
};
@@ -89,7 +90,7 @@ struct Continue {
struct Match {
static constexpr StatementKind Kind = StatementKind::Match;
const Expression* exp;
std::list<std::pair<const Expression*, const Statement*>>* clauses;
std::list<std::pair<const Pattern*, const Statement*>>* clauses;
};
struct Continuation {
@@ -113,7 +114,7 @@ struct Statement {
-> const Statement*;
static auto MakeAssign(int line_num, const Expression* lhs,
const Expression* rhs) -> const Statement*;
static auto MakeVariableDefinition(int line_num, const Expression* pat,
static auto MakeVariableDefinition(int line_num, const Pattern* pat,
const Expression* init)
-> const Statement*;
static auto MakeIf(int line_num, const Expression* cond,
@@ -129,7 +130,7 @@ struct Statement {
static auto MakeContinue(int line_num) -> const Statement*;
static auto MakeMatch(
int line_num, const Expression* exp,
std::list<std::pair<const Expression*, const Statement*>>* clauses)
std::list<std::pair<const Pattern*, const Statement*>>* clauses)
-> const Statement*;
// Returns an AST node for a continuation statement give its line number and
// contituent parts.