mirror of
https://github.com/carbon-language/carbon-lang.git
synced 2026-10-05 11:51:04 +01:00
Restructure handling of paren expressions (#417)
* Move nontrivial logic out of `parser.ypp` into `FieldList`, rename it to `ParenContents`, make it a class, and add tests * Use a `FieldInitializer` struct instead of `std::pair<std::string, Expression*>` to represent the fields of a tuple
This commit is contained in:
@@ -28,14 +28,31 @@ cc_library(
|
||||
"-Wno-writable-strings",
|
||||
],
|
||||
deps = [
|
||||
":paren_contents",
|
||||
"//executable_semantics:tracing_flag",
|
||||
"//executable_semantics/ast:declaration",
|
||||
"//executable_semantics/ast:expression",
|
||||
"//executable_semantics/ast:field_list",
|
||||
"//executable_semantics/interpreter",
|
||||
],
|
||||
)
|
||||
|
||||
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"],
|
||||
deps = [
|
||||
":paren_contents",
|
||||
"@llvm-project//llvm:gtest",
|
||||
"@llvm-project//llvm:gtest_main",
|
||||
],
|
||||
)
|
||||
|
||||
genrule(
|
||||
name = "syntax_bison_srcs",
|
||||
srcs = ["parser.ypp"],
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
// 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 {
|
||||
|
||||
Expression* ParenContents::AsExpression(int line_number) const {
|
||||
if (fields_.size() == 1 && fields_.front().name == "" &&
|
||||
has_trailing_comma_ == HasTrailingComma::No) {
|
||||
return fields_.front().expression;
|
||||
} else {
|
||||
return AsTuple(line_number);
|
||||
}
|
||||
}
|
||||
|
||||
Expression* ParenContents::AsTuple(int line_number) const {
|
||||
auto vec = new std::vector<std::pair<std::string, Carbon::Expression*>>();
|
||||
for (const FieldInitializer& initializer : fields_) {
|
||||
vec->push_back({initializer.name, initializer.expression});
|
||||
}
|
||||
return MakeTuple(line_number, vec);
|
||||
}
|
||||
|
||||
} // namespace Carbon
|
||||
@@ -0,0 +1,61 @@
|
||||
// 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_PAREN_CONTENTS_H_
|
||||
#define EXECUTABLE_SEMANTICS_SYNTAX_PAREN_CONTENTS_H_
|
||||
|
||||
#include <list>
|
||||
|
||||
#include "executable_semantics/ast/expression.h"
|
||||
|
||||
namespace Carbon {
|
||||
|
||||
// A FieldInitializer represents the initialization of a single tuple field.
|
||||
struct FieldInitializer {
|
||||
// The field name. An empty string indicates that this represents a
|
||||
// positional field.
|
||||
std::string name;
|
||||
|
||||
// The expression that initializes the field.
|
||||
Expression* expression;
|
||||
};
|
||||
|
||||
// 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
|
||||
// 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 };
|
||||
|
||||
// Constructs a ParenContents representing the contents of "()".
|
||||
ParenContents() : fields_({}), has_trailing_comma_(HasTrailingComma::No) {}
|
||||
|
||||
// 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) {}
|
||||
|
||||
ParenContents(const ParenContents&) = default;
|
||||
ParenContents& operator=(const ParenContents&) = default;
|
||||
|
||||
// Returns the paren expression, interpreted as a tuple.
|
||||
Expression* AsTuple(int line_number) const;
|
||||
|
||||
// Returns the paren expression, with no external constraints on what kind
|
||||
// of expression it represents.
|
||||
Expression* AsExpression(int line_number) const;
|
||||
|
||||
private:
|
||||
std::vector<FieldInitializer> fields_;
|
||||
HasTrailingComma has_trailing_comma_;
|
||||
};
|
||||
|
||||
} // namespace Carbon
|
||||
|
||||
#endif // EXECUTABLE_SEMANTICS_SYNTAX_PAREN_CONTENTS_H_
|
||||
@@ -0,0 +1,113 @@
|
||||
// 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;
|
||||
Expression* expression = contents.AsExpression(/*line_num=*/1);
|
||||
EXPECT_EQ(expression->line_num, 1);
|
||||
ASSERT_EQ(expression->tag, ExpressionKind::Tuple);
|
||||
EXPECT_EQ(expression->u.tuple.fields->size(), 0);
|
||||
}
|
||||
|
||||
TEST(ParenContentsTest, EmptyAsTuple) {
|
||||
ParenContents contents;
|
||||
Expression* tuple = contents.AsTuple(/*line_num=*/1);
|
||||
EXPECT_EQ(tuple->line_num, 1);
|
||||
ASSERT_EQ(tuple->tag, ExpressionKind::Tuple);
|
||||
EXPECT_EQ(tuple->u.tuple.fields->size(), 0);
|
||||
}
|
||||
|
||||
TEST(ParenContentsTest, UnaryNoCommaAsExpression) {
|
||||
// Equivalent to a code fragment like
|
||||
// ```
|
||||
// (
|
||||
// 42
|
||||
// )
|
||||
// ```
|
||||
ParenContents contents({{.expression = MakeInt(/*line_num=*/2, 42)}},
|
||||
ParenContents::HasTrailingComma::No);
|
||||
|
||||
Expression* expression = contents.AsExpression(/*line_num=*/1);
|
||||
EXPECT_EQ(expression->line_num, 2);
|
||||
ASSERT_EQ(expression->tag, ExpressionKind::Integer);
|
||||
}
|
||||
|
||||
TEST(ParenContentsTest, UnaryNoCommaAsTuple) {
|
||||
ParenContents contents({{.expression = MakeInt(/*line_num=*/2, 42)}},
|
||||
ParenContents::HasTrailingComma::No);
|
||||
|
||||
Expression* tuple = contents.AsTuple(/*line_num=*/1);
|
||||
EXPECT_EQ(tuple->line_num, 1);
|
||||
ASSERT_EQ(tuple->tag, ExpressionKind::Tuple);
|
||||
std::vector<std::pair<std::string, Expression*>> fields =
|
||||
*tuple->u.tuple.fields;
|
||||
ASSERT_EQ(fields.size(), 1);
|
||||
EXPECT_EQ(fields[0].second->tag, ExpressionKind::Integer);
|
||||
}
|
||||
|
||||
TEST(ParenContentsTest, UnaryWithCommaAsExpression) {
|
||||
ParenContents contents({{.expression = MakeInt(/*line_num=*/2, 42)}},
|
||||
ParenContents::HasTrailingComma::Yes);
|
||||
|
||||
Expression* expression = contents.AsExpression(/*line_num=*/1);
|
||||
EXPECT_EQ(expression->line_num, 1);
|
||||
ASSERT_EQ(expression->tag, ExpressionKind::Tuple);
|
||||
std::vector<std::pair<std::string, Expression*>> fields =
|
||||
*expression->u.tuple.fields;
|
||||
ASSERT_EQ(fields.size(), 1);
|
||||
EXPECT_EQ(fields[0].second->tag, ExpressionKind::Integer);
|
||||
}
|
||||
|
||||
TEST(ParenContentsTest, UnaryWithCommaAsTuple) {
|
||||
ParenContents contents({{.expression = MakeInt(/*line_num=*/2, 42)}},
|
||||
ParenContents::HasTrailingComma::Yes);
|
||||
|
||||
Expression* tuple = contents.AsTuple(/*line_num=*/1);
|
||||
EXPECT_EQ(tuple->line_num, 1);
|
||||
ASSERT_EQ(tuple->tag, ExpressionKind::Tuple);
|
||||
std::vector<std::pair<std::string, Expression*>> fields =
|
||||
*tuple->u.tuple.fields;
|
||||
ASSERT_EQ(fields.size(), 1);
|
||||
EXPECT_EQ(fields[0].second->tag, ExpressionKind::Integer);
|
||||
}
|
||||
|
||||
TEST(ParenContentsTest, BinaryAsExpression) {
|
||||
ParenContents contents({{.expression = MakeInt(/*line_num=*/2, 42)},
|
||||
{.expression = MakeInt(/*line_num=*/3, 42)}},
|
||||
ParenContents::HasTrailingComma::Yes);
|
||||
|
||||
Expression* expression = contents.AsExpression(/*line_num=*/1);
|
||||
EXPECT_EQ(expression->line_num, 1);
|
||||
ASSERT_EQ(expression->tag, ExpressionKind::Tuple);
|
||||
std::vector<std::pair<std::string, Expression*>> fields =
|
||||
*expression->u.tuple.fields;
|
||||
ASSERT_EQ(fields.size(), 2);
|
||||
EXPECT_EQ(fields[0].second->tag, ExpressionKind::Integer);
|
||||
EXPECT_EQ(fields[1].second->tag, ExpressionKind::Integer);
|
||||
}
|
||||
|
||||
TEST(ParenContentsTest, BinaryAsTuple) {
|
||||
ParenContents contents({{.expression = MakeInt(/*line_num=*/2, 42)},
|
||||
{.expression = MakeInt(/*line_num=*/3, 42)}},
|
||||
ParenContents::HasTrailingComma::Yes);
|
||||
|
||||
Expression* tuple = contents.AsTuple(/*line_num=*/1);
|
||||
EXPECT_EQ(tuple->line_num, 1);
|
||||
ASSERT_EQ(tuple->tag, ExpressionKind::Tuple);
|
||||
std::vector<std::pair<std::string, Expression*>> fields =
|
||||
*tuple->u.tuple.fields;
|
||||
ASSERT_EQ(fields.size(), 2);
|
||||
EXPECT_EQ(fields[0].second->tag, ExpressionKind::Integer);
|
||||
EXPECT_EQ(fields[1].second->tag, ExpressionKind::Integer);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace Carbon
|
||||
@@ -50,8 +50,8 @@
|
||||
|
||||
#include "executable_semantics/ast/abstract_syntax_tree.h"
|
||||
#include "executable_semantics/ast/declaration.h"
|
||||
#include "executable_semantics/ast/field_list.h"
|
||||
#include "executable_semantics/ast/function_definition.h"
|
||||
#include "executable_semantics/syntax/paren_contents.h"
|
||||
|
||||
namespace Carbon {
|
||||
class ParseAndLexContext;
|
||||
@@ -89,8 +89,9 @@ void yy::parser::error(
|
||||
%type <Carbon::Member*> variable_declaration
|
||||
%type <Carbon::Member*> member
|
||||
%type <std::list<Carbon::Member*>*> member_list
|
||||
%type <Carbon::FieldList*> field
|
||||
%type <Carbon::FieldList*> field_list
|
||||
%type <Carbon::FieldInitializer> field_initializer
|
||||
%type <Carbon::ParenContents> paren_contents
|
||||
%type <std::vector<Carbon::FieldInitializer>> paren_contents_without_trailing_comma
|
||||
%type <std::pair<std::string, Carbon::Expression*>*> alternative
|
||||
%type <std::list<std::pair<std::string, Carbon::Expression*>>*> alternative_list
|
||||
%type <std::pair<Carbon::Expression*, Carbon::Statement*>*> clause
|
||||
@@ -208,51 +209,40 @@ expression:
|
||||
;
|
||||
designator: "." identifier { $$ = $2; }
|
||||
;
|
||||
paren_expression: "(" field_list ")"
|
||||
{
|
||||
if ($2->fields->size() == 1 &&
|
||||
$2->fields->front().first == "" &&
|
||||
!$2->has_explicit_comma) {
|
||||
$$ = $2->fields->front().second;
|
||||
} else {
|
||||
auto vec = new std::vector<std::pair<std::string,Carbon::Expression*>>(
|
||||
$2->fields->begin(), $2->fields->end());
|
||||
$$ = Carbon::MakeTuple(yylineno, vec);
|
||||
}
|
||||
}
|
||||
paren_expression: "(" paren_contents ")"
|
||||
{ $$ = $2.AsExpression(yylineno); }
|
||||
;
|
||||
tuple: "(" field_list ")"
|
||||
{
|
||||
auto vec = new std::vector<std::pair<std::string,Carbon::Expression*>>(
|
||||
$2->fields->begin(), $2->fields->end());
|
||||
$$ = Carbon::MakeTuple(yylineno, vec);
|
||||
}
|
||||
field:
|
||||
tuple: "(" paren_contents ")"
|
||||
{ $$ = $2.AsTuple(yylineno); }
|
||||
;
|
||||
field_initializer:
|
||||
pattern
|
||||
{
|
||||
auto fields =
|
||||
new std::list<std::pair<std::string, Carbon::Expression*>>();
|
||||
fields->push_back(std::make_pair("", $1));
|
||||
$$ = Carbon::MakeFieldList(fields);
|
||||
}
|
||||
{ $$ = Carbon::FieldInitializer({"", $1}); }
|
||||
| designator "=" pattern
|
||||
{ $$ = Carbon::FieldInitializer({$1, $3}); }
|
||||
;
|
||||
paren_contents:
|
||||
// Empty
|
||||
{ $$ = Carbon::ParenContents(); }
|
||||
| paren_contents_without_trailing_comma
|
||||
{
|
||||
auto fields =
|
||||
new std::list<std::pair<std::string, Carbon::Expression*>>();
|
||||
fields->push_back(std::make_pair($1, $3));
|
||||
$$ = Carbon::MakeFieldList(fields);
|
||||
$$ = Carbon::ParenContents($1,
|
||||
Carbon::ParenContents::HasTrailingComma::No);
|
||||
}
|
||||
| paren_contents_without_trailing_comma ","
|
||||
{
|
||||
$$ = Carbon::ParenContents($1,
|
||||
Carbon::ParenContents::HasTrailingComma::Yes);
|
||||
}
|
||||
;
|
||||
field_list:
|
||||
// Empty
|
||||
paren_contents_without_trailing_comma:
|
||||
field_initializer
|
||||
{ $$ = {$1}; }
|
||||
| paren_contents_without_trailing_comma "," field_initializer
|
||||
{
|
||||
$$ = Carbon::MakeFieldList(
|
||||
new std::list<std::pair<std::string, Carbon::Expression*>>());
|
||||
$$ = $1;
|
||||
$$.push_back($3);
|
||||
}
|
||||
| field
|
||||
{ $$ = $1; }
|
||||
| field "," field_list
|
||||
{ $$ = Carbon::MakeConsField($1, $3); }
|
||||
;
|
||||
clause:
|
||||
CASE pattern DBLARROW statement
|
||||
|
||||
Reference in New Issue
Block a user