diff --git a/parser/parse_node_kind.def b/parser/parse_node_kind.def index fd6256877599..8653681f6577 100644 --- a/parser/parse_node_kind.def +++ b/parser/parse_node_kind.def @@ -28,6 +28,10 @@ CARBON_PARSE_NODE_KIND(FileEnd) CARBON_PARSE_NODE_KIND(CodeBlockEnd) CARBON_PARSE_NODE_KIND(CodeBlock) CARBON_PARSE_NODE_KIND(ExpressionStatement) +CARBON_PARSE_NODE_KIND(IfStatement) +CARBON_PARSE_NODE_KIND(IfStatementElse) +CARBON_PARSE_NODE_KIND(Condition) +CARBON_PARSE_NODE_KIND(ConditionEnd) // Expressions. CARBON_PARSE_NODE_KIND(Literal) diff --git a/parser/parse_tree_test.cpp b/parser/parse_tree_test.cpp index e450b4ea7b99..8269ce6ea002 100644 --- a/parser/parse_tree_test.cpp +++ b/parser/parse_tree_test.cpp @@ -516,6 +516,133 @@ TEST_F(ParseTreeTest, VariableDeclarations) { MatchFileEnd()})); } +TEST_F(ParseTreeTest, IfNoElse) { + TokenizedBuffer tokens = GetTokenizedBuffer( + "fn F() {\n" + " if (a)\n" + " if (b)\n" + " if (c)\n" + " d;\n" + "}"); + ParseTree tree = ParseTree::Parse(tokens, consumer); + EXPECT_FALSE(tree.HasErrors()); + + EXPECT_THAT(tree, + MatchParseTreeNodes( + {MatchFunctionDeclaration( + MatchDeclaredName("F"), + MatchParameterList(MatchParameterListEnd()), + MatchCodeBlock( + MatchIfStatement( + MatchCondition(MatchNameReference("a"), + MatchConditionEnd()), + MatchIfStatement( + MatchCondition(MatchNameReference("b"), + MatchConditionEnd()), + MatchIfStatement( + MatchCondition(MatchNameReference("c"), + MatchConditionEnd()), + MatchExpressionStatement( + MatchNameReference("d"))))), + MatchCodeBlockEnd())), + MatchFileEnd()})); +} + +TEST_F(ParseTreeTest, IfElse) { + TokenizedBuffer tokens = GetTokenizedBuffer( + "fn F() {\n" + " if (a)\n" + " if (b)\n" + " c;\n" + " else\n" + " d;\n" + " else\n" + " e;\n" + " if (x) { G(1); }\n" + " else if (x) { G(2); }\n" + " else { G(3); }\n" + "}"); + ParseTree tree = ParseTree::Parse(tokens, consumer); + EXPECT_FALSE(tree.HasErrors()); + + EXPECT_THAT( + tree, + MatchParseTreeNodes( + {MatchFunctionDeclaration( + MatchDeclaredName("F"), + MatchParameterList(MatchParameterListEnd()), + MatchCodeBlock( + MatchIfStatement( + MatchCondition(MatchNameReference("a"), + MatchConditionEnd()), + MatchIfStatement( + MatchCondition(MatchNameReference("b"), + MatchConditionEnd()), + MatchExpressionStatement(MatchNameReference("c")), + MatchIfStatementElse(), + MatchExpressionStatement(MatchNameReference("d"))), + MatchIfStatementElse(), + MatchExpressionStatement(MatchNameReference("e"))), + MatchIfStatement( + MatchCondition(MatchNameReference("x"), + MatchConditionEnd()), + MatchCodeBlock( + MatchExpressionStatement(MatchCallExpression( + MatchNameReference("G"), MatchLiteral("1"), + MatchCallExpressionEnd())), + MatchCodeBlockEnd()), + MatchIfStatementElse(), + MatchIfStatement( + MatchCondition(MatchNameReference("x"), + MatchConditionEnd()), + MatchCodeBlock( + MatchExpressionStatement(MatchCallExpression( + MatchNameReference("G"), MatchLiteral("2"), + MatchCallExpressionEnd())), + MatchCodeBlockEnd()), + MatchIfStatementElse(), + MatchCodeBlock( + MatchExpressionStatement(MatchCallExpression( + MatchNameReference("G"), MatchLiteral("3"), + MatchCallExpressionEnd())), + MatchCodeBlockEnd()))), + MatchCodeBlockEnd())), + MatchFileEnd()})); +} + +TEST_F(ParseTreeTest, IfError) { + TokenizedBuffer tokens = GetTokenizedBuffer( + "fn F() {\n" + " if a {}\n" + " if () {}\n" + " if (b c) {}\n" + " if (d)\n" + "}"); + ParseTree tree = ParseTree::Parse(tokens, consumer); + EXPECT_TRUE(tree.HasErrors()); + + EXPECT_THAT( + tree, MatchParseTreeNodes( + {MatchFunctionDeclaration( + MatchDeclaredName("F"), + MatchParameterList(MatchParameterListEnd()), + MatchCodeBlock( + MatchIfStatement(HasError, MatchNameReference("a"), + MatchCodeBlock(MatchCodeBlockEnd())), + MatchIfStatement( + MatchCondition(HasError, MatchConditionEnd()), + MatchCodeBlock(MatchCodeBlockEnd())), + MatchIfStatement( + MatchCondition(HasError, MatchNameReference("b"), + MatchConditionEnd()), + MatchCodeBlock(MatchCodeBlockEnd())), + MatchIfStatement( + HasError, MatchCondition(MatchNameReference("d"), + MatchConditionEnd())), + MatchCodeBlockEnd())), + 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/parser/parser_impl.cpp b/parser/parser_impl.cpp index f099e98fdd42..0dfd110b2695 100644 --- a/parser/parser_impl.cpp +++ b/parser/parser_impl.cpp @@ -9,6 +9,7 @@ #include "lexer/token_kind.h" #include "lexer/tokenized_buffer.h" #include "llvm/ADT/Optional.h" +#include "llvm/Support/FormatVariadic.h" #include "llvm/Support/raw_ostream.h" #include "parser/parse_node_kind.h" #include "parser/parse_tree.h" @@ -65,10 +66,24 @@ struct ExpectedExpression : SimpleDiagnostic { static constexpr llvm::StringLiteral Message = "Expected expression."; }; +struct ExpectedParenAfter : SimpleDiagnostic { + static constexpr llvm::StringLiteral ShortName = "syntax-error"; + static constexpr const char* Message = "Expected `(` after `{0}`."; + + TokenKind introducer; + + auto Format() -> std::string { + return llvm::formatv(Message, introducer.GetFixedSpelling()).str(); + } +}; + struct ExpectedCloseParen : SimpleDiagnostic { static constexpr llvm::StringLiteral ShortName = "syntax-error"; static constexpr llvm::StringLiteral Message = "Unexpected tokens before `)`."; + + // TODO: Include the location of the matching open paren in the diagnostic. + TokenizedBuffer::Token open_paren; }; struct ExpectedSemiAfterExpression @@ -495,7 +510,8 @@ auto ParseTree::Parser::ParseParenExpression() -> llvm::Optional { if (tokens.GetKind(*position) != TokenKind::CloseParen()) { if (!has_errors) { - emitter.EmitError(*position); + emitter.EmitError(*position, + {.open_paren = open_paren}); has_errors = true; } SkipTo(tokens.GetMatchedClosingToken(open_paren)); @@ -712,11 +728,58 @@ auto ParseTree::Parser::ParseExpressionStatement() -> llvm::Optional { return llvm::None; } +auto ParseTree::Parser::ParseParenCondition(TokenKind introducer) + -> llvm::Optional { + // `(` expression `)` + auto start = StartSubtree(); + auto open_paren = ConsumeIf(TokenKind::OpenParen()); + if (!open_paren) { + emitter.EmitError(*position, + {.introducer = introducer}); + } + + auto expr = ParseExpression(); + + if (!open_paren) { + // Don't expect a matching closing paren if there wasn't an opening paren. + return llvm::None; + } + + bool has_errors = false; + if (tokens.GetKind(*position) != TokenKind::CloseParen()) { + emitter.EmitError(*position, + {.open_paren = *open_paren}); + SkipTo(tokens.GetMatchedClosingToken(*open_paren)); + has_errors = true; + } + AddLeafNode(ParseNodeKind::ConditionEnd(), Consume(TokenKind::CloseParen())); + + return AddNode(ParseNodeKind::Condition(), *open_paren, start, + /*has_errors=*/!expr || has_errors); +} + +auto ParseTree::Parser::ParseIfStatement() -> llvm::Optional { + auto start = StartSubtree(); + auto if_token = Consume(TokenKind::IfKeyword()); + auto cond = ParseParenCondition(TokenKind::IfKeyword()); + auto then_case = ParseStatement(); + bool else_has_errors = false; + if (ConsumeAndAddLeafNodeIf(TokenKind::ElseKeyword(), + ParseNodeKind::IfStatementElse())) { + else_has_errors = !ParseStatement(); + } + return AddNode(ParseNodeKind::IfStatement(), if_token, start, + /*has_errors=*/!cond || !then_case || else_has_errors); +} + auto ParseTree::Parser::ParseStatement() -> llvm::Optional { switch (tokens.GetKind(*position)) { case TokenKind::VarKeyword(): return ParseVariableDeclaration(); + case TokenKind::IfKeyword(): + return ParseIfStatement(); + case TokenKind::OpenCurlyBrace(): return ParseCodeBlock(); diff --git a/parser/parser_impl.h b/parser/parser_impl.h index ea07909e743d..90e058d46639 100644 --- a/parser/parser_impl.h +++ b/parser/parser_impl.h @@ -171,6 +171,12 @@ class ParseTree::Parser { // Parses an expression statement: an expression followed by a semicolon. auto ParseExpressionStatement() -> llvm::Optional; + // Parses the parenthesized condition in an if-statement. + auto ParseParenCondition(TokenKind introducer) -> llvm::Optional; + + // Parses an if-statement. + auto ParseIfStatement() -> llvm::Optional; + // Parses a statement. auto ParseStatement() -> llvm::Optional;