From 52e53a72b610da932982aa32c3cab99b8ee08ea7 Mon Sep 17 00:00:00 2001 From: Yingchi Long Date: Sat, 20 Jan 2024 21:30:08 +0800 Subject: [PATCH] libnixf: parse ExprParen `( expr )` --- libnixf/include/nixf/Parse/Nodes.h | 31 ++++++++++ libnixf/src/Parse/Parser.cpp | 42 ++++++++++++- libnixf/src/Parse/Token.h | 4 ++ libnixf/test/Parse/Parser.cpp | 98 +++++++++++++++++++++++++++++- 4 files changed, 173 insertions(+), 2 deletions(-) diff --git a/libnixf/include/nixf/Parse/Nodes.h b/libnixf/include/nixf/Parse/Nodes.h index 525a54de7..96464a833 100644 --- a/libnixf/include/nixf/Parse/Nodes.h +++ b/libnixf/include/nixf/Parse/Nodes.h @@ -21,11 +21,17 @@ class Node { public: enum NodeKind { NK_InterpolableParts, + + /// \brief Misc node, used for parentheses, keywords, etc. + /// \see Misc + NK_Misc, + NK_BeginExpr, NK_ExprInt, NK_ExprFloat, NK_ExprString, NK_ExprPath, + NK_ExprParen, NK_EndExpr, }; @@ -140,4 +146,29 @@ class ExprPath : public Expr { } }; +/// \brief Misc node, used for parentheses, keywords, etc. +/// +/// This is used for representing nodes that only location matters. +/// Might be useful for linting. +class Misc : public Node { +public: + Misc(RangeTy Range) : Node(NK_Misc, Range) {} +}; + +class ExprParen : public Expr { + std::shared_ptr E; + std::shared_ptr LParen; + std::shared_ptr RParen; + +public: + ExprParen(RangeTy Range, std::shared_ptr E, + std::shared_ptr LParen, std::shared_ptr RParen) + : Expr(NK_ExprParen, Range), E(std::move(E)), LParen(std::move(LParen)), + RParen(std::move(RParen)) {} + + [[nodiscard]] const std::shared_ptr &expr() const { return E; } + [[nodiscard]] const std::shared_ptr &lparen() const { return LParen; } + [[nodiscard]] const std::shared_ptr &rparen() const { return RParen; } +}; + } // namespace nixf diff --git a/libnixf/src/Parse/Parser.cpp b/libnixf/src/Parse/Parser.cpp index 4209c7a18..fa3b11610 100644 --- a/libnixf/src/Parse/Parser.cpp +++ b/libnixf/src/Parse/Parser.cpp @@ -26,7 +26,7 @@ using namespace nixf::tok; Diagnostic &diagNullExpr(std::vector &Diags, Point Loc, std::string As) { Diagnostic &D = Diags.emplace_back(Diagnostic::DK_Expected, RangeTy(Loc)); - D << ("an expression as " + std::move(As)); + D << std::move(As) + " expression"; D.fix("insert dummy expression").edit(TextEdit::mkInsertion(Loc, " expr")); return D; } @@ -263,6 +263,44 @@ class Parser { } // with(PS_String / PS_IndString) } + /// '(' expr ')' + std::shared_ptr parseExprParen() { + Token L = peek(); + auto LParen = std::make_shared(L.range()); + assert(L.kind() == tok_l_paren); + consume(); // ( + assert(LastToken && "LastToken should be set after consume()"); + auto Expr = parseExpr(); + if (!Expr) + diagNullExpr(Diags, LastToken->end(), "parenthesized"); + if (Token R = peek(); R.kind() == tok_r_paren) { + consume(); // ) + auto RParen = std::make_shared(R.range()); + return std::make_shared( + RangeTy{ + L.begin(), + R.end(), + }, + std::move(Expr), std::move(LParen), std::move(RParen)); + } + + // Missing ")" + Diagnostic &D = + Diags.emplace_back(Diagnostic::DK_Expected, RangeTy(LastToken->end())); + D << std::string(tok::spelling(tok_r_paren)); + D.note(Note::NK_ToMachThis, L.range()) + << std::string(tok::spelling(tok_l_paren)); + D.fix("insert )") + .edit(TextEdit::mkInsertion(LastToken->end(), + std::string(tok::spelling(tok_r_paren)))); + return std::make_shared( + RangeTy{ + L.begin(), + LastToken->end(), + }, + std::move(Expr), std::move(LParen), /*RParen=*/nullptr); + } + /// expr_simple : INT /// | FLOAT /// | string @@ -296,6 +334,8 @@ class Parser { return parseString(/*IsIndented=*/true); case tok_path_fragment: return parseExprPath(); + case tok_l_paren: + return parseExprParen(); default: return nullptr; } diff --git a/libnixf/src/Parse/Token.h b/libnixf/src/Parse/Token.h index 8e84c137d..c444921dd 100644 --- a/libnixf/src/Parse/Token.h +++ b/libnixf/src/Parse/Token.h @@ -30,6 +30,10 @@ constexpr std::string_view spelling(TokenKind Kind) { return "${"; case tok_r_curly: return "}"; + case tok_l_paren: + return "("; + case tok_r_paren: + return ")"; default: assert(false && "Not yet implemented!"); } diff --git a/libnixf/test/Parse/Parser.cpp b/libnixf/test/Parse/Parser.cpp index dcc8a49bc..8ae83eb89 100644 --- a/libnixf/test/Parse/Parser.cpp +++ b/libnixf/test/Parse/Parser.cpp @@ -255,7 +255,7 @@ TEST(Parser, InterpolationNullExpr) { ASSERT_TRUE(D.range().end().isAt(0, 3, 3)); ASSERT_EQ(D.kind(), Diagnostic::DK_Expected); ASSERT_EQ(D.args().size(), 1); - ASSERT_EQ(D.args()[0], "an expression as interpolation"); + ASSERT_EQ(D.args()[0], "interpolation expression"); // Check fix-it hints. ASSERT_EQ(D.fixes().size(), 1); @@ -297,4 +297,100 @@ TEST(Parser, PathOK) { ASSERT_EQ(Parts->fragments()[2].escaped(), "/d"); } +TEST(Parser, ParenExpr) { + auto Src = R"((1))"sv; + + std::vector Diags; + auto AST = nixf::parse(Src, Diags); + ASSERT_TRUE(AST); + ASSERT_EQ(AST->kind(), Node::NK_ExprParen); + ASSERT_TRUE(AST->range().begin().isAt(0, 0, 0)); + ASSERT_TRUE(AST->range().end().isAt(0, 3, 3)); + + // Also check the location of parenthesis. + auto &P = *static_cast(AST.get()); + ASSERT_TRUE(P.lparen()->begin().isAt(0, 0, 0)); + ASSERT_TRUE(P.lparen()->end().isAt(0, 1, 1)); + ASSERT_TRUE(P.rparen()->begin().isAt(0, 2, 2)); + ASSERT_TRUE(P.rparen()->end().isAt(0, 3, 3)); +} + +TEST(Parser, ParenExprMissingRParen) { + auto Src = R"((1)"sv; + + std::vector Diags; + auto AST = nixf::parse(Src, Diags); + ASSERT_TRUE(AST); + ASSERT_EQ(AST->kind(), Node::NK_ExprParen); + ASSERT_TRUE(AST->range().begin().isAt(0, 0, 0)); + ASSERT_TRUE(AST->range().end().isAt(0, 2, 2)); + + // Also check the location of parenthesis. + auto &P = *static_cast(AST.get()); + ASSERT_TRUE(P.lparen()->begin().isAt(0, 0, 0)); + ASSERT_TRUE(P.lparen()->end().isAt(0, 1, 1)); + ASSERT_EQ(P.rparen(), nullptr); + + // Check the diagnostic. + auto &D = Diags[0]; + ASSERT_TRUE(D.range().begin().isAt(0, 2, 2)); + ASSERT_TRUE(D.range().end().isAt(0, 2, 2)); + ASSERT_EQ(D.kind(), Diagnostic::DK_Expected); + ASSERT_EQ(D.args().size(), 1); + ASSERT_EQ(D.args()[0], ")"); + + // Check the note. + ASSERT_EQ(D.notes().size(), 1); + auto &N = D.notes()[0]; + ASSERT_TRUE(N.range().begin().isAt(0, 0, 0)); + ASSERT_TRUE(N.range().end().isAt(0, 1, 1)); + ASSERT_EQ(N.kind(), Note::NK_ToMachThis); + ASSERT_EQ(N.args().size(), 1); + ASSERT_EQ(N.args()[0], "("); + + // Check fix-it hints. + ASSERT_EQ(D.fixes().size(), 1); + ASSERT_EQ(D.fixes()[0].edits().size(), 1); + ASSERT_EQ(D.fixes()[0].message(), "insert )"); + const auto &F = D.fixes()[0].edits()[0]; + ASSERT_TRUE(F.oldRange().begin().isAt(0, 2, 2)); + ASSERT_TRUE(F.oldRange().end().isAt(0, 2, 2)); + ASSERT_EQ(F.newText(), ")"); +} + +TEST(Parser, ParenNullExpr) { + auto Src = R"(())"sv; + + std::vector Diags; + auto AST = nixf::parse(Src, Diags); + ASSERT_TRUE(AST); + ASSERT_EQ(AST->kind(), Node::NK_ExprParen); + ASSERT_TRUE(AST->range().begin().isAt(0, 0, 0)); + ASSERT_TRUE(AST->range().end().isAt(0, 2, 2)); + + // Also check the location of parenthesis. + auto &P = *static_cast(AST.get()); + ASSERT_TRUE(P.lparen()->begin().isAt(0, 0, 0)); + ASSERT_TRUE(P.lparen()->end().isAt(0, 1, 1)); + ASSERT_TRUE(P.rparen()->begin().isAt(0, 1, 1)); + ASSERT_TRUE(P.rparen()->end().isAt(0, 2, 2)); + + // Check the diagnostic. + auto &D = Diags[0]; + ASSERT_TRUE(D.range().begin().isAt(0, 1, 1)); + ASSERT_TRUE(D.range().end().isAt(0, 1, 1)); + ASSERT_EQ(D.kind(), Diagnostic::DK_Expected); + ASSERT_EQ(D.args().size(), 1); + ASSERT_EQ(D.args()[0], "parenthesized expression"); + + // Check fix-it hints. + ASSERT_EQ(D.fixes().size(), 1); + ASSERT_EQ(D.fixes()[0].edits().size(), 1); + ASSERT_EQ(D.fixes()[0].message(), "insert dummy expression"); + const auto &F = D.fixes()[0].edits()[0]; + ASSERT_TRUE(F.oldRange().begin().isAt(0, 1, 1)); + ASSERT_TRUE(F.oldRange().end().isAt(0, 1, 1)); + ASSERT_EQ(F.newText(), " expr"); +} + } // namespace