Просмотр исходного кода

[toolchain] Parsing support for tuple literal syntax. (#694)

For now, we permit non-empty tuples to have trailing commas, and require
a trailing comma if there's exactly one list element. The exact rule
here has not yet been decided.
Richard Smith 4 лет назад
Родитель
Сommit
b1993a6cd0

+ 3 - 0
toolchain/parser/parse_node_kind.def

@@ -43,6 +43,9 @@ CARBON_PARSE_NODE_KIND(StatementEnd)
 
 // Expressions.
 CARBON_PARSE_NODE_KIND(Literal)
+CARBON_PARSE_NODE_KIND(TupleLiteral)
+CARBON_PARSE_NODE_KIND(TupleLiteralComma)
+CARBON_PARSE_NODE_KIND(TupleLiteralEnd)
 CARBON_PARSE_NODE_KIND(NameReference)
 CARBON_PARSE_NODE_KIND(ParenExpression)
 CARBON_PARSE_NODE_KIND(ParenExpressionEnd)

+ 33 - 0
toolchain/parser/parse_tree_test.cpp

@@ -937,6 +937,39 @@ TEST_F(ParseTreeTest, Return) {
            MatchFileEnd()}));
 }
 
+TEST_F(ParseTreeTest, Tuples) {
+  TokenizedBuffer tokens = GetTokenizedBuffer(R"(
+    var x: (i32, i32) = (1, 2);
+    var y: ((), (), ());
+  )");
+  ParseTree tree = ParseTree::Parse(tokens, consumer);
+  EXPECT_FALSE(tree.HasErrors());
+
+  auto empty_tuple = MatchTupleLiteral(MatchTupleLiteralEnd());
+
+  EXPECT_THAT(
+      tree,
+      MatchParseTreeNodes(
+          {MatchVariableDeclaration(
+               MatchPatternBinding(MatchDeclaredName("x"), ":",
+                                   MatchTupleLiteral(MatchLiteral("i32"),
+                                                     MatchTupleLiteralComma(),
+                                                     MatchLiteral("i32"),
+                                                     MatchTupleLiteralEnd())),
+               MatchVariableInitializer(MatchTupleLiteral(
+                   MatchLiteral("1"), MatchTupleLiteralComma(),
+                   MatchLiteral("2"), MatchTupleLiteralEnd())),
+               MatchDeclarationEnd()),
+           MatchVariableDeclaration(
+               MatchPatternBinding(
+                   MatchDeclaredName("y"), ":",
+                   MatchTupleLiteral(empty_tuple, MatchTupleLiteralComma(),
+                                     empty_tuple, MatchTupleLiteralComma(),
+                                     empty_tuple, MatchTupleLiteralEnd())),
+               MatchDeclarationEnd()),
+           MatchFileEnd()}));
+}
+
 auto GetAndDropLine(llvm::StringRef& s) -> std::string {
   auto newline_offset = s.find_first_of('\n');
   llvm::StringRef line = s.slice(0, newline_offset);

+ 35 - 20
toolchain/parser/parser_impl.cpp

@@ -389,7 +389,8 @@ auto ParseTree::Parser::ParseCloseParen(TokenizedBuffer::Token open_paren,
 template <typename ListElementParser, typename ListCompletionHandler>
 auto ParseTree::Parser::ParseParenList(ListElementParser list_element_parser,
                                        ParseNodeKind comma_kind,
-                                       ListCompletionHandler list_handler)
+                                       ListCompletionHandler list_handler,
+                                       bool allow_trailing_comma)
     -> llvm::Optional<Node> {
   // `(` element-list[opt] `)`
   //
@@ -398,12 +399,15 @@ auto ParseTree::Parser::ParseParenList(ListElementParser list_element_parser,
   TokenizedBuffer::Token open_paren = Consume(TokenKind::OpenParen());
 
   bool has_errors = false;
+  bool any_commas = false;
+  int64_t num_elements = 0;
 
   // Parse elements, if any are specified.
   if (!NextTokenIs(TokenKind::CloseParen())) {
     while (true) {
       bool element_error = !list_element_parser();
       has_errors |= element_error;
+      ++num_elements;
 
       if (!NextTokenIsOneOf({TokenKind::CloseParen(), TokenKind::Comma()})) {
         if (!element_error) {
@@ -423,10 +427,17 @@ auto ParseTree::Parser::ParseParenList(ListElementParser list_element_parser,
       }
 
       AddLeafNode(comma_kind, Consume(TokenKind::Comma()));
+      any_commas = true;
+
+      if (allow_trailing_comma && NextTokenIs(TokenKind::CloseParen())) {
+        break;
+      }
     }
   }
 
-  return list_handler(open_paren, Consume(TokenKind::CloseParen()), has_errors);
+  bool is_single_item = num_elements == 1 && !any_commas;
+  return list_handler(open_paren, is_single_item,
+                      Consume(TokenKind::CloseParen()), has_errors);
 }
 
 auto ParseTree::Parser::ParsePattern(PatternKind kind) -> llvm::Optional<Node> {
@@ -465,8 +476,8 @@ auto ParseTree::Parser::ParseFunctionSignature() -> bool {
   auto params = ParseParenList(
       [&] { return ParseFunctionParameter(); },
       ParseNodeKind::ParameterListComma(),
-      [&](TokenizedBuffer::Token open_paren, TokenizedBuffer::Token close_paren,
-          bool has_errors) {
+      [&](TokenizedBuffer::Token open_paren, bool is_single_item,
+          TokenizedBuffer::Token close_paren, bool has_errors) {
         AddLeafNode(ParseNodeKind::ParameterListEnd(), close_paren);
         return AddNode(ParseNodeKind::ParameterList(), open_paren, start,
                        has_errors);
@@ -651,21 +662,25 @@ auto ParseTree::Parser::ParseDeclaration() -> llvm::Optional<Node> {
 }
 
 auto ParseTree::Parser::ParseParenExpression() -> llvm::Optional<Node> {
-  // `(` expression `)`
+  // parenthesized-expression ::= `(` expression `)`
+  // tuple-literal ::= `(` `)`
+  //               ::= `(` expression `,` [expression-list [`,`]] `)`
+  //
+  // Parse the union of these, `(` [expression-list [`,`]] `)`, and work out
+  // whether it's a tuple or a parenthesized expression afterwards.
   auto start = GetSubtreeStartPosition();
-  TokenizedBuffer::Token open_paren = Consume(TokenKind::OpenParen());
-
-  // TODO: If the next token is a close paren, build an empty tuple literal.
-
-  auto expr = ParseExpression();
-
-  // TODO: If the next token is a comma, build a tuple literal.
-
-  auto close_paren =
-      ParseCloseParen(open_paren, ParseNodeKind::ParenExpressionEnd());
-
-  return AddNode(ParseNodeKind::ParenExpression(), open_paren, start,
-                 /*has_error=*/!expr || !close_paren);
+  return ParseParenList(
+      [&] { return ParseExpression(); }, ParseNodeKind::TupleLiteralComma(),
+      [&](TokenizedBuffer::Token open_paren, bool is_single_item,
+          TokenizedBuffer::Token close_paren, bool has_arg_errors) {
+        AddLeafNode(is_single_item ? ParseNodeKind::ParenExpressionEnd()
+                                   : ParseNodeKind::TupleLiteralEnd(),
+                    close_paren);
+        return AddNode(is_single_item ? ParseNodeKind::ParenExpression()
+                                      : ParseNodeKind::TupleLiteral(),
+                       open_paren, start, has_arg_errors);
+      },
+      /*allow_trailing_comma=*/true);
 }
 
 auto ParseTree::Parser::ParsePrimaryExpression() -> llvm::Optional<Node> {
@@ -723,8 +738,8 @@ auto ParseTree::Parser::ParseCallExpression(SubtreeStart start, bool has_errors)
   //                 ::= expression `,` expression-list
   return ParseParenList(
       [&] { return ParseExpression(); }, ParseNodeKind::CallExpressionComma(),
-      [&](TokenizedBuffer::Token open_paren, TokenizedBuffer::Token close_paren,
-          bool has_arg_errors) {
+      [&](TokenizedBuffer::Token open_paren, bool is_single_item,
+          TokenizedBuffer::Token close_paren, bool has_arg_errors) {
         AddLeafNode(ParseNodeKind::CallExpressionEnd(), close_paren);
         return AddNode(ParseNodeKind::CallExpression(), open_paren, start,
                        has_errors || has_arg_errors);

+ 2 - 1
toolchain/parser/parser_impl.h

@@ -138,7 +138,8 @@ class ParseTree::Parser {
   template <typename ListElementParser, typename ListCompletionHandler>
   auto ParseParenList(ListElementParser list_element_parser,
                       ParseNodeKind comma_kind,
-                      ListCompletionHandler list_handler)
+                      ListCompletionHandler list_handler,
+                      bool allow_trailing_comma = false)
       -> llvm::Optional<Node>;
 
   // Parses a single function parameter declaration.