Skip to content

Commit

Permalink
libnixf: parse indented string (#295)
Browse files Browse the repository at this point in the history
  • Loading branch information
inclyc authored Jan 13, 2024
1 parent 976ac79 commit 7da3faa
Show file tree
Hide file tree
Showing 5 changed files with 104 additions and 18 deletions.
6 changes: 3 additions & 3 deletions libnixf/include/nixf/Basic/Diagnostic.h
Original file line number Diff line number Diff line change
Expand Up @@ -50,8 +50,8 @@ class Fix {
return OldRange.Begin == OldRange.End;
}

[[nodiscard]] OffsetRange getOldRange() const { return OldRange; }
[[nodiscard]] std::string_view getNewText() const { return NewText; }
[[nodiscard]] OffsetRange oldRange() const { return OldRange; }
[[nodiscard]] std::string_view newText() const { return NewText; }
};

class PartialDiagnostic {
Expand Down Expand Up @@ -154,7 +154,7 @@ class Diagnostic : public PartialDiagnostic {
return *this;
}

const std::vector<Fix> &getFixes() { return Fixes; }
const std::vector<Fix> &fixes() { return Fixes; }

OffsetRange range() const { return Range; }

Expand Down
13 changes: 13 additions & 0 deletions libnixf/include/nixf/Parse/Nodes.h
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ class Node {
NK_ExprInt,
NK_ExprFloat,
NK_ExprString,
NK_ExprPath,
NK_EndExpr,
};

Expand Down Expand Up @@ -117,4 +118,16 @@ class ExprString : public Expr {
}
};

class ExprPath : public Expr {
std::shared_ptr<InterpolatedParts> Parts;

public:
ExprPath(OffsetRange Range, std::shared_ptr<InterpolatedParts> Parts)
: Expr(NK_ExprPath, Range), Parts(std::move(Parts)) {}

[[nodiscard]] const std::shared_ptr<InterpolatedParts> &parts() const {
return Parts;
}
};

} // namespace nixf
30 changes: 17 additions & 13 deletions libnixf/lib/Parse/Parser.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -189,30 +189,32 @@ class Parser {
}
}

std::shared_ptr<Expr> parseString() {
Token DQuoteStart = peek();
assert(DQuoteStart.kind() == tok_dquote && "should be a dquote");
RB.push(DQuoteStart.begin());
std::shared_ptr<Expr> parseString(bool IsIndented) {
Token Quote = peek();
TokenKind QuoteKind = IsIndented ? tok_quote2 : tok_dquote;
std::string QuoteSpel(tok::spelling(QuoteKind));
assert(Quote.kind() == QuoteKind && "should be a quote");
RB.push(Quote.begin());
// Consume the quote and so make the look-ahead buf empty.
consume();
assert(LastToken && "LastToken should be set after consume()");
/* with(PS_String) */ {
auto StringState = withState(PS_String);
/* with(PS_String / PS_IndString) */ {
auto StringState = withState(IsIndented ? PS_IndString : PS_String);
std::shared_ptr<InterpolatedParts> Parts = parseInterpolableParts();
if (Token EndTok = peek(); EndTok.kind() == tok_dquote) {
if (Token EndTok = peek(); EndTok.kind() == QuoteKind) {
consume();
return std::make_shared<ExprString>(RB.finish(EndTok.end()),
std::move(Parts));
}
Diagnostic &D =
Diag.diag(Diagnostic::DK_Expected, OffsetRange(LastToken->end()));
D << "\" to close string literal";
D.note(Note::NK_ToMachThis, OffsetRange{DQuoteStart.begin()}) << "\"";
D.fix(Fix::mkInsertion(LastToken->end(), "\""));
D << QuoteSpel;
D.note(Note::NK_ToMachThis, Quote.range()) << QuoteSpel;
D.fix(Fix::mkInsertion(LastToken->end(), QuoteSpel));
return std::make_shared<ExprString>(RB.finish(Parts->end()),
std::move(Parts));

} // with(PS_String)
} // with(PS_String / PS_IndString)
}

std::shared_ptr<Expr> parseExprSimple() {
Expand All @@ -231,8 +233,10 @@ class Parser {
NixFloat N = std::strtof(std::string(Tok.view()).c_str(), nullptr);
return std::make_shared<ExprFloat>(Tok.range(), N);
}
case tok_dquote:
return parseString();
case tok_dquote: // " - normal strings
return parseString(/*IsIndented=*/false);
case tok_quote2: // '' - indented strings
return parseString(/*IsIndented=*/true);
default:
return nullptr;
}
Expand Down
19 changes: 19 additions & 0 deletions libnixf/lib/Parse/Token.h
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
#pragma once

#include "nixf/Basic/Range.h"

#include <cassert>
#include <string>

namespace nixf {
Expand All @@ -12,6 +14,23 @@ enum TokenKind {
#undef TOK
};

constexpr std::string_view spelling(TokenKind Kind) {
switch (Kind) {
#define TOK_KEYWORD(NAME) \
case tok_kw_##NAME: \
return #NAME;
#include "TokenKinds.inc"
#undef TOK_KEYWORD
case tok_dquote:
return "\"";
case tok_quote2:
return "''";
default:
assert(false && "Not yet implemented!");
}
__builtin_unreachable();
}

} // namespace tok

/// \brief A token. With it's kind, and the range in source code.
Expand Down
54 changes: 52 additions & 2 deletions libnixf/lib/Parse/test/Parser.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -84,17 +84,24 @@ TEST(Parser, StringMissingDQuote) {
ASSERT_EQ(D->range().End, Src.begin() + 4);
ASSERT_EQ(D->kind(), Diagnostic::DK_Expected);
ASSERT_EQ(D->args().size(), 1);
ASSERT_EQ(D->args()[0], "\" to close string literal");
ASSERT_EQ(D->args()[0], "\"");

// Check the note.
ASSERT_EQ(D->notes().size(), 1);
auto &N = D->notes()[0];
ASSERT_EQ(N->range().Begin, Src.begin() + 0);
ASSERT_EQ(N->range().End, Src.begin() + 0);
ASSERT_EQ(N->range().End, Src.begin() + 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_EQ(F.oldRange().Begin, Src.begin() + 4);
ASSERT_EQ(F.oldRange().End, Src.begin() + 4);
ASSERT_EQ(F.newText(), "\"");

ASSERT_EQ(Expr->range().view(), Src);
}

Expand All @@ -116,4 +123,47 @@ TEST(Parser, StringInterpolation) {
ASSERT_EQ(Expr->range().view(), Src);
}

TEST(Parser, IndentedString) {
auto Src = R"(''aaa
foo
bar
${''string''}
)"sv;

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

// Check the diagnostic.
ASSERT_EQ(Diags.diags().size(), 1);
auto &D = Diags.diags()[0];
ASSERT_EQ(D->range().Begin, Src.begin() + 39);
ASSERT_EQ(D->range().End, Src.begin() + 39);
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_EQ(N->range().Begin, Src.begin() + 0);
ASSERT_EQ(N->range().End, Src.begin() + 2);
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_EQ(F.oldRange().Begin, Src.begin() + 39);
ASSERT_EQ(F.oldRange().End, Src.begin() + 39);
ASSERT_EQ(F.newText(), "''");
}

} // namespace

0 comments on commit 7da3faa

Please sign in to comment.