Skip to content

Commit

Permalink
libnixf: parse ExprParen ( expr )
Browse files Browse the repository at this point in the history
  • Loading branch information
inclyc committed Jan 20, 2024
1 parent 64d34ee commit 8a57728
Show file tree
Hide file tree
Showing 4 changed files with 131 additions and 2 deletions.
27 changes: 27 additions & 0 deletions libnixf/include/nixf/Parse/Nodes.h
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,17 @@ class Node {
public:
enum NodeKind {
NK_InterpolableParts,

/// '(' and ')', represented as a single node.
/// This is useful for diagnosing redundant parentheses, hinting the user to
/// remove them. We do not care about the contents/kinds of the parentheses.
NK_Brace,
NK_BeginExpr,
NK_ExprInt,
NK_ExprFloat,
NK_ExprString,
NK_ExprPath,
NK_ExprParen,
NK_EndExpr,
};

Expand Down Expand Up @@ -140,4 +146,25 @@ class ExprPath : public Expr {
}
};

class Paren : public Node {
public:
Paren(RangeTy Range) : Node(NK_Brace, Range) {}
};

class ExprParen : public Expr {
std::shared_ptr<Expr> E;
std::shared_ptr<Paren> LParen;
std::shared_ptr<Paren> RParen;

public:
ExprParen(RangeTy Range, std::shared_ptr<Expr> E,
std::shared_ptr<Paren> LParen, std::shared_ptr<Paren> RParen)
: Expr(NK_ExprParen, Range), E(std::move(E)), LParen(std::move(LParen)),
RParen(std::move(RParen)) {}

[[nodiscard]] const std::shared_ptr<Expr> &expr() const { return E; }
[[nodiscard]] const std::shared_ptr<Paren> &lparen() const { return LParen; }
[[nodiscard]] const std::shared_ptr<Paren> &rparen() const { return RParen; }
};

} // namespace nixf
41 changes: 40 additions & 1 deletion libnixf/src/Parse/Parser.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ using namespace nixf::tok;
Diagnostic &diagNullExpr(std::vector<Diagnostic> &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(Fix::mkInsertion(Loc, " expr"));
return D;
}
Expand Down Expand Up @@ -263,6 +263,43 @@ class Parser {
} // with(PS_String / PS_IndString)
}

/// '(' expr ')'
std::shared_ptr<ExprParen> parseExprParen() {
Token L = peek();
auto LParen = std::make_shared<Paren>(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(), "braced");
if (Token R = peek(); R.kind() == tok_r_paren) {
consume(); // )
auto RParen = std::make_shared<Paren>(R.range());
return std::make_shared<ExprParen>(
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(Fix::mkInsertion(LastToken->end(),
std::string(tok::spelling(tok_r_paren))));
return std::make_shared<ExprParen>(
RangeTy{
L.begin(),
LastToken->end(),
},
std::move(Expr), std::move(LParen), /*RParen=*/nullptr);
}

/// expr_simple : INT
/// | FLOAT
/// | string
Expand Down Expand Up @@ -296,6 +333,8 @@ class Parser {
return parseString(/*IsIndented=*/true);
case tok_path_fragment:
return parseExprPath();
case tok_l_paren:
return parseExprParen();
default:
return nullptr;
}
Expand Down
4 changes: 4 additions & 0 deletions libnixf/src/Parse/Token.h
Original file line number Diff line number Diff line change
Expand Up @@ -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!");
}
Expand Down
61 changes: 60 additions & 1 deletion libnixf/test/Parse/Parser.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -249,7 +249,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);
Expand Down Expand Up @@ -289,4 +289,63 @@ TEST(Parser, PathOK) {
ASSERT_EQ(Parts->fragments()[2].escaped(), "/d");
}

TEST(Parser, ParenExpr) {
auto Src = R"((1))"sv;

std::vector<Diagnostic> 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<ExprParen *>(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<Diagnostic> 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<ExprParen *>(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);
const auto &F = D.fixes()[0];
ASSERT_TRUE(F.oldRange().begin().isAt(0, 2, 2));
ASSERT_TRUE(F.oldRange().end().isAt(0, 2, 2));
ASSERT_EQ(F.newText(), ")");
}

} // namespace

0 comments on commit 8a57728

Please sign in to comment.