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
-16
View File
@@ -42,23 +42,7 @@ cc_library(
cc_library(
name = "paren_contents",
srcs = ["paren_contents.cpp"],
hdrs = ["paren_contents.h"],
deps = ["//executable_semantics/ast:expression"],
)
cc_test(
name = "paren_contents_test",
srcs = ["paren_contents_test.cpp"],
env = {
# TODO(#580): Remove this when leaks are fixed.
"ASAN_OPTIONS": "detect_leaks=0",
},
deps = [
":paren_contents",
"@llvm-project//llvm:gtest",
"@llvm-project//llvm:gtest_main",
],
)
genrule(
@@ -1,22 +0,0 @@
// 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/syntax/paren_contents.h"
namespace Carbon {
const Expression* ParenContents::AsExpression(int line_number) const {
if (fields_.size() == 1 && fields_.front().name == "" &&
has_trailing_comma_ == HasTrailingComma::No) {
return new Expression(*fields_.front().expression);
} else {
return AsTuple(line_number);
}
}
const Expression* ParenContents::AsTuple(int line_number) const {
return Expression::MakeTupleLiteral(line_number, fields_);
}
} // namespace Carbon
+64 -29
View File
@@ -5,47 +5,82 @@
#ifndef EXECUTABLE_SEMANTICS_SYNTAX_PAREN_CONTENTS_H_
#define EXECUTABLE_SEMANTICS_SYNTAX_PAREN_CONTENTS_H_
#include <list>
#include <optional>
#include <string>
#include <vector>
#include "executable_semantics/ast/expression.h"
#include "executable_semantics/common/error.h"
namespace Carbon {
// Represents the syntactic contents of an expression delimited by
// parentheses. Such expressions can be interpreted as either tuples or
// arbitrary expressions, depending on their context and the syntax of their
// Represents the syntactic contents of an expression or pattern delimited by
// parentheses. In those syntaxes, parentheses can be used either for grouping
// or for forming a tuple, depending on their context and the syntax of their
// contents; this class helps calling code resolve that ambiguity. Since that
// ambiguity is purely syntactic, this class should only be needed during
// parsing.
class ParenContents {
public:
// Indicates whether the paren expression's contents end with a comma.
enum class HasTrailingComma { Yes, No };
//
// `Term` is the type of the syntactic grouping being built, and the type of
// the individual syntactic units it's built from; typically it should be
// either `Expression` or `Pattern`.
template <typename Term>
struct ParenContents {
struct Element {
std::optional<std::string> name;
const Term* term;
};
// Constructs a ParenContents representing the contents of "()".
ParenContents() : fields_({}), has_trailing_comma_(HasTrailingComma::No) {}
// If this object represents a single term, with no name and no trailing
// comma, this method returns that term. This typically means the parentheses
// can be interpreted as grouping.
auto SingleTerm() const -> std::optional<const Term*>;
// Constructs a ParenContents representing the given list of fields,
// with or without a trailing comma.
ParenContents(std::vector<FieldInitializer> fields,
HasTrailingComma has_trailing_comma)
: fields_(fields), has_trailing_comma_(has_trailing_comma) {}
// Converts `elements` to std::vector<TupleElement>. TupleElement must
// have a constructor that takes a std::string and a const Term*.
//
// TODO: Find a way to deduce TupleElement from Term.
template <typename TupleElement>
auto TupleElements(int line_num) const -> std::vector<TupleElement>;
ParenContents(const ParenContents&) = default;
ParenContents& operator=(const ParenContents&) = default;
// Returns the paren expression, interpreted as a tuple.
const Expression* AsTuple(int line_number) const;
// Returns the paren expression, with no external constraints on what kind
// of expression it represents.
const Expression* AsExpression(int line_number) const;
private:
std::vector<FieldInitializer> fields_;
HasTrailingComma has_trailing_comma_;
std::vector<Element> elements;
bool has_trailing_comma;
};
// Implementation details only below here.
template <typename Term>
auto ParenContents<Term>::SingleTerm() const -> std::optional<const Term*> {
if (elements.size() == 1 && !elements.front().name.has_value() &&
!has_trailing_comma) {
return elements.front().term;
} else {
return std::nullopt;
}
}
template <typename Term>
template <typename TupleElement>
auto ParenContents<Term>::TupleElements(int line_num) const
-> std::vector<TupleElement> {
std::vector<TupleElement> result;
int i = 0;
bool seen_named_member = false;
for (auto element : elements) {
if (element.name.has_value()) {
seen_named_member = true;
result.push_back(TupleElement(*element.name, element.term));
} else {
if (seen_named_member) {
FATAL_USER_ERROR(line_num)
<< "positional members must come before named members";
}
result.push_back(TupleElement(std::to_string(i), element.term));
}
++i;
}
return result;
}
} // namespace Carbon
#endif // EXECUTABLE_SEMANTICS_SYNTAX_PAREN_CONTENTS_H_
@@ -1,114 +0,0 @@
// 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/syntax/paren_contents.h"
#include "gtest/gtest.h"
namespace Carbon {
namespace {
TEST(ParenContentsTest, EmptyAsExpression) {
ParenContents contents;
const Expression* expression = contents.AsExpression(/*line_num=*/1);
EXPECT_EQ(expression->line_num, 1);
ASSERT_EQ(expression->tag(), ExpressionKind::TupleLiteral);
EXPECT_EQ(expression->GetTupleLiteral().fields.size(), 0);
}
TEST(ParenContentsTest, EmptyAsTuple) {
ParenContents contents;
const Expression* tuple = contents.AsTuple(/*line_num=*/1);
EXPECT_EQ(tuple->line_num, 1);
ASSERT_EQ(tuple->tag(), ExpressionKind::TupleLiteral);
EXPECT_EQ(tuple->GetTupleLiteral().fields.size(), 0);
}
TEST(ParenContentsTest, UnaryNoCommaAsExpression) {
// Equivalent to a code fragment like
// ```
// (
// 42
// )
// ```
ParenContents contents(
{{.expression = Expression::MakeIntLiteral(/*line_num=*/2, 42)}},
ParenContents::HasTrailingComma::No);
const Expression* expression = contents.AsExpression(/*line_num=*/1);
EXPECT_EQ(expression->line_num, 2);
ASSERT_EQ(expression->tag(), ExpressionKind::IntLiteral);
}
TEST(ParenContentsTest, UnaryNoCommaAsTuple) {
ParenContents contents(
{{.expression = Expression::MakeIntLiteral(/*line_num=*/2, 42)}},
ParenContents::HasTrailingComma::No);
const Expression* tuple = contents.AsTuple(/*line_num=*/1);
EXPECT_EQ(tuple->line_num, 1);
ASSERT_EQ(tuple->tag(), ExpressionKind::TupleLiteral);
std::vector<FieldInitializer> fields = tuple->GetTupleLiteral().fields;
ASSERT_EQ(fields.size(), 1);
EXPECT_EQ(fields[0].expression->tag(), ExpressionKind::IntLiteral);
}
TEST(ParenContentsTest, UnaryWithCommaAsExpression) {
ParenContents contents(
{{.expression = Expression::MakeIntLiteral(/*line_num=*/2, 42)}},
ParenContents::HasTrailingComma::Yes);
const Expression* expression = contents.AsExpression(/*line_num=*/1);
EXPECT_EQ(expression->line_num, 1);
ASSERT_EQ(expression->tag(), ExpressionKind::TupleLiteral);
std::vector<FieldInitializer> fields = expression->GetTupleLiteral().fields;
ASSERT_EQ(fields.size(), 1);
EXPECT_EQ(fields[0].expression->tag(), ExpressionKind::IntLiteral);
}
TEST(ParenContentsTest, UnaryWithCommaAsTuple) {
ParenContents contents(
{{.expression = Expression::MakeIntLiteral(/*line_num=*/2, 42)}},
ParenContents::HasTrailingComma::Yes);
const Expression* tuple = contents.AsTuple(/*line_num=*/1);
EXPECT_EQ(tuple->line_num, 1);
ASSERT_EQ(tuple->tag(), ExpressionKind::TupleLiteral);
std::vector<FieldInitializer> fields = tuple->GetTupleLiteral().fields;
ASSERT_EQ(fields.size(), 1);
EXPECT_EQ(fields[0].expression->tag(), ExpressionKind::IntLiteral);
}
TEST(ParenContentsTest, BinaryAsExpression) {
ParenContents contents(
{{.expression = Expression::MakeIntLiteral(/*line_num=*/2, 42)},
{.expression = Expression::MakeIntLiteral(/*line_num=*/3, 42)}},
ParenContents::HasTrailingComma::Yes);
const Expression* expression = contents.AsExpression(/*line_num=*/1);
EXPECT_EQ(expression->line_num, 1);
ASSERT_EQ(expression->tag(), ExpressionKind::TupleLiteral);
std::vector<FieldInitializer> fields = expression->GetTupleLiteral().fields;
ASSERT_EQ(fields.size(), 2);
EXPECT_EQ(fields[0].expression->tag(), ExpressionKind::IntLiteral);
EXPECT_EQ(fields[1].expression->tag(), ExpressionKind::IntLiteral);
}
TEST(ParenContentsTest, BinaryAsTuple) {
ParenContents contents(
{{.expression = Expression::MakeIntLiteral(/*line_num=*/2, 42)},
{.expression = Expression::MakeIntLiteral(/*line_num=*/3, 42)}},
ParenContents::HasTrailingComma::Yes);
const Expression* tuple = contents.AsTuple(/*line_num=*/1);
EXPECT_EQ(tuple->line_num, 1);
ASSERT_EQ(tuple->tag(), ExpressionKind::TupleLiteral);
std::vector<FieldInitializer> fields = tuple->GetTupleLiteral().fields;
ASSERT_EQ(fields.size(), 2);
EXPECT_EQ(fields[0].expression->tag(), ExpressionKind::IntLiteral);
EXPECT_EQ(fields[1].expression->tag(), ExpressionKind::IntLiteral);
}
} // namespace
} // namespace Carbon
+140 -62
View File
@@ -49,6 +49,7 @@
#include <cstdlib>
#include <iostream>
#include <list>
#include <vector>
#include "executable_semantics/syntax/syntax_helpers.h"
#include "executable_semantics/syntax/parse_and_lex_context.h"
@@ -59,7 +60,9 @@
#include "executable_semantics/ast/abstract_syntax_tree.h"
#include "executable_semantics/ast/declaration.h"
#include "executable_semantics/ast/expression.h"
#include "executable_semantics/ast/function_definition.h"
#include "executable_semantics/ast/pattern.h"
#include "executable_semantics/syntax/paren_contents.h"
namespace Carbon {
@@ -94,21 +97,28 @@ void yy::parser::error(const location_type&, const std::string& message) {
%type <Carbon::GenericBinding> generic_binding
%type <std::vector<Carbon::GenericBinding>> deduced_params
%type <std::vector<Carbon::GenericBinding>> deduced_param_list
%type <const Carbon::Expression*> pattern
%type <const Carbon::Pattern*> pattern
%type <const Carbon::Pattern*> non_expression_pattern
%type <const Carbon::Expression*> return_type
%type <const Carbon::Expression*> paren_expression
%type <const Carbon::Expression*> tuple
%type <std::optional<std::string>> binding_lhs
%type <Carbon::Member*> variable_declaration
%type <const Carbon::BindingPattern*> variable_declaration
%type <Carbon::Member*> member
%type <std::list<Carbon::Member*>> member_list
%type <Carbon::FieldInitializer> field_initializer
%type <Carbon::ParenContents> paren_contents
%type <std::vector<Carbon::FieldInitializer>> paren_contents_without_trailing_comma
%type <Carbon::ParenContents<Carbon::Expression>::Element> paren_expression_element
%type <Carbon::ParenContents<Carbon::Expression>> paren_expression_base
%type <Carbon::ParenContents<Carbon::Expression>> paren_expression_contents
%type <const Carbon::Pattern*> paren_pattern
%type <const Carbon::TuplePattern*> tuple_pattern
%type <const Carbon::TuplePattern*> maybe_empty_tuple_pattern
%type <Carbon::ParenContents<Carbon::Pattern>> paren_pattern_base
%type <Carbon::ParenContents<Carbon::Pattern>::Element> paren_pattern_element
%type <Carbon::ParenContents<Carbon::Pattern>> paren_pattern_contents
%type <std::pair<std::string, const Carbon::Expression*>> alternative
%type <std::list<std::pair<std::string, const Carbon::Expression*>>> alternative_list
%type <std::pair<const Carbon::Expression*, const Carbon::Statement*>*> clause
%type <std::list<std::pair<const Carbon::Expression*, const Carbon::Statement*>>*> clause_list
%type <std::pair<const Carbon::Pattern*, const Carbon::Statement*>*> clause
%type <std::list<std::pair<const Carbon::Pattern*, const Carbon::Statement*>>*> clause_list
%token END_OF_FILE 0
%token AND
%token OR
@@ -192,14 +202,6 @@ void yy::parser::error(const location_type&, const std::string& message) {
input: declaration_list
{ parsed_program = $1; }
;
pattern:
expression
{ $$ = $1; }
;
binding_lhs:
identifier { $$ = $1; }
| UNDERSCORE { $$ = std::nullopt; }
;
expression:
identifier
{ $$ = Carbon::Expression::MakeIdentifierExpression(yylineno, $1); }
@@ -207,10 +209,6 @@ expression:
{ $$ = Carbon::Expression::MakeFieldAccessExpression(yylineno, $1, $2); }
| expression "[" expression "]"
{ $$ = Carbon::Expression::MakeIndexExpression(yylineno, $1, $3); }
| binding_lhs ":" expression
{
$$ = Carbon::Expression::MakeBindingExpression(yylineno, $1, $3);
}
| integer_literal
{ $$ = Carbon::Expression::MakeIntLiteral(yylineno, $1); }
| TRUE
@@ -223,8 +221,6 @@ expression:
{ $$ = Carbon::Expression::MakeBoolTypeLiteral(yylineno); }
| TYPE
{ $$ = Carbon::Expression::MakeTypeTypeLiteral(yylineno); }
| AUTO
{ $$ = Carbon::Expression::MakeAutoTypeLiteral(yylineno); }
| CONTINUATION_TYPE
{ $$ = Carbon::Expression::MakeContinuationTypeLiteral(yylineno); }
| paren_expression { $$ = $1; }
@@ -271,55 +267,133 @@ expression:
;
designator: "." identifier { $$ = $2; }
;
paren_expression: "(" paren_contents ")"
{ $$ = $2.AsExpression(yylineno); }
paren_expression: paren_expression_base
{ $$ = Carbon::ExpressionFromParenContents(yylineno, $1); }
;
tuple: "(" paren_contents ")"
{ $$ = $2.AsTuple(yylineno); }
tuple: paren_expression_base
{ $$ = Carbon::TupleExpressionFromParenContents(yylineno, $1); }
;
field_initializer:
pattern
{ $$ = Carbon::FieldInitializer({"", $1}); }
| designator "=" pattern
{ $$ = Carbon::FieldInitializer({$1, $3}); }
paren_expression_element:
expression
{ $$ = {.name = std::nullopt, .term = $1}; }
| designator "=" expression
{ $$ = {.name = $1, .term = $3}; }
;
paren_contents:
// Empty
{ $$ = Carbon::ParenContents(); }
| paren_contents_without_trailing_comma
paren_expression_base:
"(" ")"
{ $$ = {.elements = {}, .has_trailing_comma = false}; }
| "(" paren_expression_contents ")"
{ $$ = $2; }
| "(" paren_expression_contents "," ")"
{
$$ = Carbon::ParenContents($1,
Carbon::ParenContents::HasTrailingComma::No);
}
| paren_contents_without_trailing_comma ","
{
$$ = Carbon::ParenContents($1,
Carbon::ParenContents::HasTrailingComma::Yes);
$$ = $2;
$$.has_trailing_comma = true;
}
;
paren_contents_without_trailing_comma:
field_initializer
{ $$ = {$1}; }
| paren_contents_without_trailing_comma "," field_initializer
paren_expression_contents:
paren_expression_element
{ $$ = {.elements = {$1}, .has_trailing_comma = false}; }
| paren_expression_contents "," paren_expression_element
{
$$ = $1;
$$.push_back($3);
$$.elements.push_back($3);
}
;
// In many cases, using `pattern` recursively will result in ambiguities.
// When that happens, it's necessary to factor out two separate productions,
// one for when the sub-pattern is an expression, and one for when it is not.
// To facilitate this, non-terminals besides `pattern` whose names contain
// `pattern` are structured to be disjoint from `expression`, unless otherwise
// specified.
pattern:
non_expression_pattern
{ $$ = $1; }
| expression
{ $$ = new Carbon::ExpressionPattern($1); }
;
non_expression_pattern:
AUTO
{ $$ = new Carbon::AutoPattern(yylineno); }
| binding_lhs ":" pattern
{ $$ = new Carbon::BindingPattern(yylineno, $1, $3); }
| paren_pattern
{ $$ = $1; }
| expression tuple_pattern
{ $$ = new Carbon::AlternativePattern(yylineno, $1, $2); }
;
binding_lhs:
identifier { $$ = $1; }
| UNDERSCORE { $$ = std::nullopt; }
;
paren_pattern: paren_pattern_base
{ $$ = Carbon::PatternFromParenContents(yylineno, $1); }
;
paren_pattern_base:
"(" paren_pattern_contents ")"
{ $$ = $2; }
| "(" paren_pattern_contents "," ")"
{
$$ = $2;
$$.has_trailing_comma = true;
}
;
// paren_pattern is analogous to paren_expression, but in order to avoid
// ambiguities, it must be disjoint from paren_expression, meaning it must
// contain at least one non_expression_pattern. The structure of this rule
// is very different from the corresponding expression rule because is has to
// enforce that requirement.
paren_pattern_contents:
paren_pattern_element
{ $$ = {.elements = {$1}, .has_trailing_comma = false }; }
| paren_expression_contents "," paren_pattern_element
{
$$ = Carbon::ParenExpressionToParenPattern($1);
$$.elements.push_back($3);
}
| paren_pattern_contents "," paren_expression_element
{
$$ = $1;
$$.elements.push_back({.name = $3.name, .term = new Carbon::ExpressionPattern($3.term)});
}
| paren_pattern_contents "," paren_pattern_element
{
$$ = $1;
$$.elements.push_back($3);
}
;
paren_pattern_element:
non_expression_pattern
{ $$ = {.name = std::nullopt, .term = $1}; }
| designator "=" non_expression_pattern
{ $$ = {.name = $1, .term = $3}; }
;
tuple_pattern: paren_pattern_base
{ $$ = Carbon::TuplePatternFromParenContents(yylineno, $1); }
;
// Unlike most `pattern` nonterminals, this one overlaps with `expression`,
// so it should be used only when prior context (such as an introducer)
// rules out the possibility of an `expression` at this point.
maybe_empty_tuple_pattern:
"(" ")"
{ $$ = new Carbon::TuplePattern(yylineno, {}); }
| tuple_pattern
{ $$ = $1; }
;
clause:
CASE pattern DBLARROW statement
{ $$ = new std::pair<const Carbon::Expression*, const Carbon::Statement*>($2, $4); }
{ $$ = new std::pair<const Carbon::Pattern*, const Carbon::Statement*>($2, $4); }
| DEFAULT DBLARROW statement
{
auto vp = Carbon::Expression::MakeBindingExpression(
yylineno, "_", Carbon::Expression::MakeAutoTypeLiteral(yylineno));
$$ = new std::pair<const Carbon::Expression*, const Carbon::Statement*>(vp, $3);
auto vp = new Carbon::BindingPattern(
yylineno, std::nullopt, new Carbon::AutoPattern(yylineno));
$$ = new std::pair<const Carbon::Pattern*, const Carbon::Statement*>(vp, $3);
}
;
clause_list:
// Empty
{
$$ = new std::list<std::pair<const Carbon::Expression*, const Carbon::Statement*>>();
$$ = new std::list<std::pair<const Carbon::Pattern*, const Carbon::Statement*>>();
}
| clause clause_list
{ $$ = $2; $$->push_front(*$1); }
@@ -407,25 +481,30 @@ deduced_params:
{ $$ = $2; }
;
function_definition:
FN identifier deduced_params tuple return_type block
{ $$ = Carbon::FunctionDefinition(yylineno, $2, $3, $4, $5, $6); }
| FN identifier deduced_params tuple DBLARROW expression ";"
FN identifier deduced_params maybe_empty_tuple_pattern return_type block
{
$$ = Carbon::FunctionDefinition(
yylineno, $2, $3, $4, new Carbon::ExpressionPattern($5), $6);
}
| FN identifier deduced_params maybe_empty_tuple_pattern DBLARROW expression ";"
{
$$ = Carbon::FunctionDefinition(
yylineno, $2, $3, $4,
Carbon::Expression::MakeAutoTypeLiteral(yylineno),
new Carbon::AutoPattern(yylineno),
Carbon::Statement::MakeReturn(yylineno, $6));
}
;
function_declaration:
FN identifier deduced_params tuple return_type ";"
{ $$ = Carbon::FunctionDefinition(yylineno, $2, $3, $4, $5, 0); }
FN identifier deduced_params maybe_empty_tuple_pattern return_type ";"
{
$$ = Carbon::FunctionDefinition(
yylineno, $2, $3, $4, new Carbon::ExpressionPattern($5), 0); }
;
variable_declaration: identifier ":" expression
{ $$ = Carbon::Member::MakeFieldMember(yylineno, $1, $3); }
variable_declaration: identifier ":" pattern
{ $$ = new Carbon::BindingPattern(yylineno, $1, $3); }
;
member: VAR variable_declaration ";"
{ $$ = $2; }
{ $$ = Carbon::Member::MakeFieldMember(yylineno, $2); }
;
member_list:
// Empty
@@ -468,8 +547,7 @@ declaration:
}
| VAR variable_declaration "=" expression ";"
{
$$ = Carbon::Declaration::MakeVariableDeclaration(
yylineno, $2->GetFieldMember().name, $2->GetFieldMember().type, $4);
$$ = Carbon::Declaration::MakeVariableDeclaration(yylineno, $2, $4);
}
;
declaration_list: