Skip to content

Commit

Permalink
libnixf: parse ExprPath
Browse files Browse the repository at this point in the history
  • Loading branch information
inclyc committed Jan 14, 2024
1 parent f9afce3 commit 0fcba2f
Show file tree
Hide file tree
Showing 5 changed files with 198 additions and 13 deletions.
10 changes: 10 additions & 0 deletions libnixf/include/nixf/Parse/Nodes.h
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,16 @@ class InterpolablePart {
: Kind(SPK_Interpolation), Interpolation(std::move(Expr)) {}

[[nodiscard]] InterpolablePartKind kind() const { return Kind; }

[[nodiscard]] const std::string &escaped() const {
assert(Kind == SPK_Escaped);
return Escaped;
}

[[nodiscard]] const std::shared_ptr<Expr> &interpolation() const {
assert(Kind == SPK_Interpolation);
return Interpolation;
}
};

class InterpolatedParts : public Node {
Expand Down
7 changes: 2 additions & 5 deletions libnixf/lib/Parse/Lexer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -299,14 +299,11 @@ Token Lexer::lexPath() {
startToken();
Tok = tok_path_end;
if (eof()) {
Tok = tok_eof;
return finishToken();
}

if (consumeOne('$')) {
if (consumePrefix("${")) {
Tok = tok_dollar_curly;
}
if (consumePrefix("${")) {
Tok = tok_dollar_curly;
return finishToken();
}

Expand Down
79 changes: 71 additions & 8 deletions libnixf/lib/Parse/Parser.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

#include "Lexer.h"

#include "Token.h"
#include "nixf/Basic/Diagnostic.h"
#include "nixf/Basic/DiagnosticEngine.h"
#include "nixf/Basic/Range.h"
Expand Down Expand Up @@ -127,16 +128,69 @@ class Parser {
///
/// interpolation : "${" expr "}"
std::shared_ptr<Expr> parseInterpolation() {
assert(peek().kind() == tok_dollar_curly);
consume();
Token TokDollarCurly = peek();
assert(TokDollarCurly.kind() == tok_dollar_curly);
consume(); // ${
assert(LastToken);
/* with(PS_Expr) */ {
auto ExprState = withState(PS_Expr);
return parseExpr();
auto Expr = parseExpr();
if (!Expr)
diagNullExpr(Diag, LastToken->end(), "interpolation");
if (peek().kind() == tok_r_curly) {
consume(); // }
} else {
// expected "}" for interpolation
Diagnostic &D =
Diag.diag(Diagnostic::DK_Expected, RangeTy(LastToken->end()));
D << std::string(tok::spelling(tok_r_curly));
D.note(Note::NK_ToMachThis, TokDollarCurly.range())
<< std::string(tok::spelling(tok_dollar_curly));
D.fix(Fix::mkInsertion(LastToken->end(),
std::string(tok::spelling(tok_r_curly))));
}
return Expr;
} // with(PS_Expr)
return nullptr;
}

/// \brief Parse paths.
///
/// path : path_fragment (path_fragment)* path_end
/// Context PS_Expr PS_Path PS_Path
///
/// The first token, path_fragment is lexed in PS_Expr context, then switch in
/// "PS_Path" context. The ending token "path_end" shall be poped with context
/// switching.
std::shared_ptr<Expr> parseExprPath() {
Token Begin = peek();
std::vector<InterpolablePart> Fragments;
assert(Begin.kind() == tok_path_fragment);
Point End;
/* with(PS_Path) */ {
auto PathState = withState(PS_Path);
do {
Token Current = peek();
Fragments.emplace_back(std::string(Current.view()));
consume();
End = Current.end();
Token Next = peek();
if (Next.kind() == tok_path_end)
break;
if (Next.kind() == tok_dollar_curly) {
if (auto Expr = parseInterpolation())
Fragments.emplace_back(std::move(Expr));
continue;
}
assert(false && "should be path_end or ${");
} while (true);
}
auto Parts = std::make_shared<InterpolatedParts>(
RangeTy{Begin.begin(), End}, std::move(Fragments));
return std::make_shared<ExprPath>(RangeTy{Begin.begin(), End},
std::move(Parts));
}

/// \brief Parse interpolable things.
///
/// They are strings, ind-strings, paths, in nix language.
Expand All @@ -147,12 +201,8 @@ class Parser {
while (true) {
switch (Token Tok = peek(0); Tok.kind()) {
case tok_dollar_curly: {
if (auto Expr = parseInterpolation()) {
if (auto Expr = parseInterpolation())
Parts.emplace_back(std::move(Expr));
} else {
assert(LastToken); // at least "${"
diagNullExpr(Diag, LastToken->end(), "string interpolation");
}
continue;
}
case tok_string_part: {
Expand Down Expand Up @@ -213,6 +263,17 @@ class Parser {
} // with(PS_String / PS_IndString)
}

/// expr_simple : INT
/// | FLOAT
/// | string
/// | indented_string
/// | path
/// | hpath
/// | uri
/// | '(' expr ')'
/// | legacy_let
/// | attrset_expr
/// | list
std::shared_ptr<Expr> parseExprSimple() {
Token Tok = peek();
switch (Tok.kind()) {
Expand All @@ -233,6 +294,8 @@ class Parser {
return parseString(/*IsIndented=*/false);
case tok_quote2: // '' - indented strings
return parseString(/*IsIndented=*/true);
case tok_path_fragment:
return parseExprPath();
default:
return nullptr;
}
Expand Down
4 changes: 4 additions & 0 deletions libnixf/lib/Parse/Token.h
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,10 @@ constexpr std::string_view spelling(TokenKind Kind) {
return "\"";
case tok_quote2:
return "''";
case tok_dollar_curly:
return "${";
case tok_r_curly:
return "}";
default:
assert(false && "Not yet implemented!");
}
Expand Down
111 changes: 111 additions & 0 deletions libnixf/lib/Parse/test/Parser.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -179,4 +179,115 @@ TEST(Parser, IndentedString) {
ASSERT_EQ(F.newText(), "''");
}

TEST(Parser, InterpolationOK) {
auto Src = R"("${1}")"sv;

DiagnosticEngine Diags;
auto AST = nixf::parse(Src, Diags);
ASSERT_TRUE(AST);
ASSERT_EQ(AST->kind(), Node::NK_ExprString);
auto Parts = static_cast<ExprString *>(AST.get())->parts();
ASSERT_EQ(Parts->fragments().size(), 1);
ASSERT_EQ(Parts->fragments()[0].kind(), InterpolablePart::SPK_Interpolation);
ASSERT_EQ(Diags.diags().size(), 0);

// Check the interpolation range
const std::shared_ptr<Expr> &I = Parts->fragments()[0].interpolation();
ASSERT_TRUE(I->range().begin().isAt(0, 3, 3));
ASSERT_TRUE(I->range().end().isAt(0, 4, 4));
}

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

DiagnosticEngine Diags;
auto AST = nixf::parse(Src, Diags);
ASSERT_TRUE(AST);
ASSERT_EQ(AST->kind(), Node::NK_ExprString);
auto Parts = static_cast<ExprString *>(AST.get())->parts();
ASSERT_EQ(Parts->fragments().size(), 1);
ASSERT_EQ(Parts->fragments()[0].kind(), InterpolablePart::SPK_Interpolation);
ASSERT_EQ(Diags.diags().size(), 1);

// Check the diagnostic.
auto &D = Diags.diags()[0];
ASSERT_TRUE(D->range().begin().isAt(0, 4, 4));
ASSERT_TRUE(D->range().end().isAt(0, 4, 4));
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, 1, 1));
ASSERT_TRUE(N->range().end().isAt(0, 3, 3));
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, 4, 4));
ASSERT_TRUE(F.oldRange().end().isAt(0, 4, 4));
ASSERT_EQ(F.newText(), "}");
}

TEST(Parser, InterpolationNullExpr) {
auto Src = R"("${}")"sv;

DiagnosticEngine Diags;
auto AST = nixf::parse(Src, Diags);
ASSERT_TRUE(AST);
ASSERT_EQ(AST->kind(), Node::NK_ExprString);
auto Parts = static_cast<ExprString *>(AST.get())->parts();
ASSERT_EQ(Parts->fragments().size(), 0);

// Check the diagnostic.
auto &D = Diags.diags()[0];
ASSERT_TRUE(D->range().begin().isAt(0, 3, 3));
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");

// Check fix-it hints.
ASSERT_EQ(D->fixes().size(), 1);
const auto &F = D->fixes()[0];
ASSERT_TRUE(F.oldRange().begin().isAt(0, 3, 3));
ASSERT_TRUE(F.oldRange().end().isAt(0, 3, 3));
ASSERT_EQ(F.newText(), " expr");
}

TEST(Parser, PathOK) {
auto Src = R"(a/b/c/${"foo"}/d)"sv;

DiagnosticEngine Diags;
auto AST = nixf::parse(Src, Diags);
ASSERT_TRUE(AST);
ASSERT_EQ(AST->kind(), Node::NK_ExprPath);
auto Parts = static_cast<ExprPath *>(AST.get())->parts();
ASSERT_EQ(Parts->fragments().size(), 3);

// Check the AST range
ASSERT_TRUE(AST->range().begin().isAt(0, 0, 0));
ASSERT_TRUE(AST->range().end().isAt(0, 16, 16));

// Check parts range. Should be the same as AST.
ASSERT_TRUE(Parts->range().begin().isAt(0, 0, 0));
ASSERT_TRUE(Parts->range().end().isAt(0, 16, 16));

// Check the first part.
ASSERT_EQ(Parts->fragments()[0].kind(), InterpolablePart::SPK_Escaped);
ASSERT_EQ(Parts->fragments()[0].escaped(), "a/b/c/");

// Check the second part.
ASSERT_EQ(Parts->fragments()[1].kind(), InterpolablePart::SPK_Interpolation);

// Check the third part.
ASSERT_EQ(Parts->fragments()[2].kind(), InterpolablePart::SPK_Escaped);
ASSERT_EQ(Parts->fragments()[2].escaped(), "/d");
}

} // namespace

0 comments on commit 0fcba2f

Please sign in to comment.