diff --git a/libnixf/include/nixf/Basic/Diagnostic.h b/libnixf/include/nixf/Basic/Diagnostic.h index 52892f19a..b7ed05268 100644 --- a/libnixf/include/nixf/Basic/Diagnostic.h +++ b/libnixf/include/nixf/Basic/Diagnostic.h @@ -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 { @@ -154,7 +154,7 @@ class Diagnostic : public PartialDiagnostic { return *this; } - const std::vector &getFixes() { return Fixes; } + const std::vector &fixes() { return Fixes; } OffsetRange range() const { return Range; } diff --git a/libnixf/include/nixf/Parse/Nodes.h b/libnixf/include/nixf/Parse/Nodes.h index 0634107ab..24eca8460 100644 --- a/libnixf/include/nixf/Parse/Nodes.h +++ b/libnixf/include/nixf/Parse/Nodes.h @@ -25,6 +25,7 @@ class Node { NK_ExprInt, NK_ExprFloat, NK_ExprString, + NK_ExprPath, NK_EndExpr, }; @@ -117,4 +118,16 @@ class ExprString : public Expr { } }; +class ExprPath : public Expr { + std::shared_ptr Parts; + +public: + ExprPath(OffsetRange Range, std::shared_ptr Parts) + : Expr(NK_ExprPath, Range), Parts(std::move(Parts)) {} + + [[nodiscard]] const std::shared_ptr &parts() const { + return Parts; + } +}; + } // namespace nixf diff --git a/libnixf/lib/Parse/Parser.cpp b/libnixf/lib/Parse/Parser.cpp index b59abedbb..457fa15d7 100644 --- a/libnixf/lib/Parse/Parser.cpp +++ b/libnixf/lib/Parse/Parser.cpp @@ -189,30 +189,32 @@ class Parser { } } - std::shared_ptr parseString() { - Token DQuoteStart = peek(); - assert(DQuoteStart.kind() == tok_dquote && "should be a dquote"); - RB.push(DQuoteStart.begin()); + std::shared_ptr 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 Parts = parseInterpolableParts(); - if (Token EndTok = peek(); EndTok.kind() == tok_dquote) { + if (Token EndTok = peek(); EndTok.kind() == QuoteKind) { consume(); return std::make_shared(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(RB.finish(Parts->end()), std::move(Parts)); - } // with(PS_String) + } // with(PS_String / PS_IndString) } std::shared_ptr parseExprSimple() { @@ -231,8 +233,10 @@ class Parser { NixFloat N = std::strtof(std::string(Tok.view()).c_str(), nullptr); return std::make_shared(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; } diff --git a/libnixf/lib/Parse/Token.h b/libnixf/lib/Parse/Token.h index 26265d971..19adaa672 100644 --- a/libnixf/lib/Parse/Token.h +++ b/libnixf/lib/Parse/Token.h @@ -1,6 +1,8 @@ #pragma once #include "nixf/Basic/Range.h" + +#include #include namespace nixf { @@ -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. diff --git a/libnixf/lib/Parse/test/Parser.cpp b/libnixf/lib/Parse/test/Parser.cpp index 48d49cf67..b7aae3381 100644 --- a/libnixf/lib/Parse/test/Parser.cpp +++ b/libnixf/lib/Parse/test/Parser.cpp @@ -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); } @@ -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(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