diff --git a/toolchain/parser/parse_node_kind.def b/toolchain/parser/parse_node_kind.def index 23446ed687b3..44d143115c7d 100644 --- a/toolchain/parser/parse_node_kind.def +++ b/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) diff --git a/toolchain/parser/parse_tree_test.cpp b/toolchain/parser/parse_tree_test.cpp index bde7fb8bbb6f..98d89d7935a8 100644 --- a/toolchain/parser/parse_tree_test.cpp +++ b/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); diff --git a/toolchain/parser/parser_impl.cpp b/toolchain/parser/parser_impl.cpp index 27bec54f66c1..77cc0d0e1b5a 100644 --- a/toolchain/parser/parser_impl.cpp +++ b/toolchain/parser/parser_impl.cpp @@ -389,7 +389,8 @@ auto ParseTree::Parser::ParseCloseParen(TokenizedBuffer::Token open_paren, template auto ParseTree::Parser::ParseParenList(ListElementParser list_element_parser, ParseNodeKind comma_kind, - ListCompletionHandler list_handler) + ListCompletionHandler list_handler, + bool allow_trailing_comma) -> llvm::Optional { // `(` 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 { @@ -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 { } auto ParseTree::Parser::ParseParenExpression() -> llvm::Optional { - // `(` 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 { @@ -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); diff --git a/toolchain/parser/parser_impl.h b/toolchain/parser/parser_impl.h index 1046a32b9cdb..5ad214982b30 100644 --- a/toolchain/parser/parser_impl.h +++ b/toolchain/parser/parser_impl.h @@ -138,7 +138,8 @@ class ParseTree::Parser { template auto ParseParenList(ListElementParser list_element_parser, ParseNodeKind comma_kind, - ListCompletionHandler list_handler) + ListCompletionHandler list_handler, + bool allow_trailing_comma = false) -> llvm::Optional; // Parses a single function parameter declaration.