Skip to content

Commit

Permalink
libnixf: parse ExprAttrs (basic, without inherit support) (#314)
Browse files Browse the repository at this point in the history
  • Loading branch information
inclyc authored Jan 23, 2024
2 parents 5b33874 + 345f48d commit b0d28d2
Show file tree
Hide file tree
Showing 5 changed files with 444 additions and 1 deletion.
1 change: 1 addition & 0 deletions libnixf/include/nixf/Basic/DiagnosticKinds.inc
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ DIAG("lex-float-no-exp", FloatNoExp, Fatal,
DIAG("lex-float-leading-zero", FloatLeadingZero, Warning,
"float begins with extra zeros `{}` is nixf extension")
DIAG("parse-expected", Expected, Error, "expected {}")
DIAG("parse-attrpath-extra-dot", AttrPathExtraDot, Error, "extra `.` at the end of attrpath")
DIAG("parse-unexpected-between", UnexpectedBetween, Error, "unexpected {} between {} and {}")
DIAG("parse-unexpected", UnexpectedText, Error, "unexpected text {}")
DIAG("parse-missing-sep-formals", MissingSepFormals, Error, "missing seperator `,` between two lambda formals")
Expand Down
114 changes: 114 additions & 0 deletions libnixf/include/nixf/Parse/Nodes.h
Original file line number Diff line number Diff line change
Expand Up @@ -26,12 +26,19 @@ class Node {
/// \see Misc
NK_Misc,

NK_Identifer,
NK_AttrName,
NK_AttrPath,
NK_Binding,
NK_Binds,

NK_BeginExpr,
NK_ExprInt,
NK_ExprFloat,
NK_ExprString,
NK_ExprPath,
NK_ExprParen,
NK_ExprAttrs,
NK_EndExpr,
};

Expand Down Expand Up @@ -171,4 +178,111 @@ class ExprParen : public Expr {
[[nodiscard]] const std::shared_ptr<Misc> &rparen() const { return RParen; }
};

/// \brief Identifier. Variable names, attribute names, etc.
class Identifier : public Node {
std::string Name;

public:
Identifier(RangeTy Range, std::string Name)
: Node(NK_Identifer, Range), Name(std::move(Name)) {}
[[nodiscard]] const std::string &name() const { return Name; }
};

class AttrName : public Node {
public:
enum AttrNameKind { ANK_ID, ANK_String, ANK_Interpolation };

private:
AttrNameKind Kind;
std::shared_ptr<Identifier> ID;
std::shared_ptr<ExprString> String;
std::shared_ptr<Expr> Interpolation;

public:
[[nodiscard]] AttrNameKind kind() const { return Kind; }

AttrName(std::shared_ptr<Identifier> ID, RangeTy Range)
: Node(NK_AttrName, Range), Kind(ANK_ID) {
this->ID = std::move(ID);
}

AttrName(std::shared_ptr<ExprString> String, RangeTy Range)
: Node(NK_AttrName, Range), Kind(ANK_String) {
this->String = std::move(String);
}

AttrName(std::shared_ptr<Expr> Interpolation, RangeTy Range)
: Node(NK_AttrName, Range), Kind(ANK_Interpolation) {
this->Interpolation = std::move(Interpolation);
}

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

[[nodiscard]] const std::shared_ptr<Identifier> &id() const {
assert(Kind == ANK_ID);
return ID;
}

[[nodiscard]] const std::shared_ptr<ExprString> &string() const {
assert(Kind == ANK_String);
return String;
}
};

class AttrPath : public Node {
std::vector<std::shared_ptr<AttrName>> Names;

public:
AttrPath(RangeTy Range, std::vector<std::shared_ptr<AttrName>> Names)
: Node(NK_AttrPath, Range), Names(std::move(Names)) {}

[[nodiscard]] const std::vector<std::shared_ptr<AttrName>> &names() const {
return Names;
}
};

class Binding : public Node {
std::shared_ptr<AttrPath> Path;
std::shared_ptr<Expr> Value;

public:
Binding(RangeTy Range, std::shared_ptr<AttrPath> Path,
std::shared_ptr<Expr> Value)
: Node(NK_Binding, Range), Path(std::move(Path)),
Value(std::move(Value)) {}

[[nodiscard]] const std::shared_ptr<AttrPath> &path() const { return Path; }
[[nodiscard]] const std::shared_ptr<Expr> &value() const { return Value; }
};

class Binds : public Node {
std::vector<std::shared_ptr<Node>> Bindings;

public:
Binds(RangeTy Range, std::vector<std::shared_ptr<Node>> Bindings)
: Node(NK_Binds, Range), Bindings(std::move(Bindings)) {}

[[nodiscard]] const std::vector<std::shared_ptr<Node>> &bindings() const {
return Bindings;
}
};

class ExprAttrs : public Expr {
std::shared_ptr<Binds> Body;
std::shared_ptr<Misc> Rec;

public:
ExprAttrs(RangeTy Range, std::shared_ptr<Binds> Body,
std::shared_ptr<Misc> Rec)
: Expr(NK_ExprAttrs, Range), Body(std::move(Body)), Rec(std::move(Rec)) {}

[[nodiscard]] const std::shared_ptr<Binds> &binds() const { return Body; }
[[nodiscard]] const std::shared_ptr<Misc> &rec() const { return Rec; }

[[nodiscard]] bool isRecursive() const { return Rec != nullptr; }
};

} // namespace nixf
171 changes: 170 additions & 1 deletion libnixf/src/Parse/Parser.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -227,7 +227,7 @@ class Parser {
}
}

std::shared_ptr<Expr> parseString(bool IsIndented) {
std::shared_ptr<ExprString> parseString(bool IsIndented) {
Token Quote = peek();
TokenKind QuoteKind = IsIndented ? tok_quote2 : tok_dquote;
std::string QuoteSpel(tok::spelling(QuoteKind));
Expand Down Expand Up @@ -301,6 +301,172 @@ class Parser {
std::move(Expr), std::move(LParen), /*RParen=*/nullptr);
}

// attrname : ID
// | string
// | interpolation
std::shared_ptr<AttrName> parseAttrName() {
switch (Token Tok = peek(); Tok.kind()) {
case tok_kw_or:
Diags.emplace_back(Diagnostic::DK_OrIdentifier, Tok.range());
[[fallthrough]];
case tok_id: {
consume();
auto ID =
std::make_shared<Identifier>(Tok.range(), std::string(Tok.view()));
return std::make_shared<AttrName>(std::move(ID), Tok.range());
}
case tok_dquote: {
std::shared_ptr<ExprString> String = parseString(/*IsIndented=*/false);
return std::make_shared<AttrName>(std::move(String), Tok.range());
}
case tok_dollar_curly: {
std::shared_ptr<Expr> Expr = parseInterpolation();
return std::make_shared<AttrName>(std::move(Expr), Tok.range());
}
default:
return nullptr;
}
}

// attrpath : attrname ('.' attrname)*
std::shared_ptr<AttrPath> parseAttrPath() {
auto First = parseAttrName();
if (!First)
return nullptr;
assert(LastToken && "LastToken should be set after valid attrname");
std::vector<std::shared_ptr<AttrName>> AttrNames;
AttrNames.emplace_back(std::move(First));
Point Begin = peek().begin();
while (true) {
if (Token Tok = peek(); Tok.kind() == tok_dot) {
consume();
auto Next = parseAttrName();
if (!Next) {
// extra ".", consider remove it.
Diagnostic &D =
Diags.emplace_back(Diagnostic::DK_AttrPathExtraDot, Tok.range());
D.fix("remove extra .").edit(TextEdit::mkRemoval(Tok.range()));
D.fix("insert dummy attrname")
.edit(TextEdit::mkInsertion(Tok.range().end(), R"("dummy")"));
}
AttrNames.emplace_back(std::move(Next));
continue;
}
break;
}
return std::make_shared<AttrPath>(
RangeTy{
Begin,
LastToken->end(),
},
std::move(AttrNames));
}

// binding : attrpath '=' expr ';'
std::shared_ptr<Binding> parseBinding() {
auto Path = parseAttrPath();
if (!Path)
return nullptr;
assert(LastToken && "LastToken should be set after valid attrpath");
if (Token Tok = peek(); Tok.kind() == tok_eq) {
consume();
} else {
// expected "=" for binding
Diagnostic &D = Diags.emplace_back(Diagnostic::DK_Expected,
RangeTy(LastToken->end()));
D << std::string(tok::spelling(tok_eq));
D.fix("insert =").edit(TextEdit::mkInsertion(LastToken->end(), "="));
}
auto Expr = parseExpr();
if (!Expr)
diagNullExpr(Diags, LastToken->end(), "binding");
if (Token Tok = peek(); Tok.kind() == tok_semi_colon) {
consume();
} else {
// TODO: reset the cursor for error recovery.
// (https://github.com/nix-community/nixd/blob/2b0ca8cef0d13823132a52b6cd6f6d7372482664/libnixf/lib/Parse/Parser.cpp#L337)
// expected ";" for binding
Diagnostic &D = Diags.emplace_back(Diagnostic::DK_Expected,
RangeTy(LastToken->end()));
D << std::string(tok::spelling(tok_semi_colon));
D.fix("insert ;").edit(TextEdit::mkInsertion(LastToken->end(), ";"));
}
return std::make_shared<Binding>(
RangeTy{
Path->begin(),
LastToken->end(),
},
std::move(Path), std::move(Expr));
}

// binds : ( binding | inherit )*
std::shared_ptr<Binds> parseBinds() {
// TODO: curently we don't support inherit
auto First = parseBinding();
if (!First)
return nullptr;
assert(LastToken && "LastToken should be set after valid binding");
std::vector<std::shared_ptr<Node>> Bindings;
Bindings.emplace_back(std::move(First));
Point Begin = peek().begin();
while (true) {
if (auto Next = parseBinding()) {
Bindings.emplace_back(std::move(Next));
continue;
}
break;
}
return std::make_shared<Binds>(
RangeTy{
Begin,
LastToken->end(),
},
std::move(Bindings));
}

// attrset_expr : REC? '{' binds '}'
//
// Note: peek `tok_kw_rec` or `tok_l_curly` before calling this function.
std::shared_ptr<ExprAttrs> parseExprAttrs() {
std::shared_ptr<Misc> Rec;

// "to match this ..."
// if "{" is missing, then use "rec", otherwise use "{"
Token Matcher = peek();
Point Begin = peek().begin(); // rec or {
if (Token Tok = peek(); Tok.kind() == tok_kw_rec) {
consume();
Rec = std::make_shared<Misc>(Tok.range());
}
if (Token Tok = peek(); Tok.kind() == tok_l_curly) {
// "{" is found, use it as matcher.
Matcher = Tok;
consume();
} else {
// expected "{" for attrset
assert(LastToken && "LastToken should be set after valid rec");
Diagnostic &D = Diags.emplace_back(Diagnostic::DK_Expected,
RangeTy(LastToken->end()));
D << std::string(tok::spelling(tok_l_curly));
D.fix("insert {").edit(TextEdit::mkInsertion(LastToken->end(), "{"));
}
assert(LastToken && "LastToken should be set after valid { or rec");
auto Binds = parseBinds();
if (Token Tok = peek(); Tok.kind() == tok_r_curly) {
consume();
} else {
// expected "}" for attrset
Diagnostic &D = Diags.emplace_back(Diagnostic::DK_Expected,
RangeTy(LastToken->range()));
D << std::string(tok::spelling(tok_r_curly));
D.note(Note::NK_ToMachThis, Matcher.range())
<< std::string(tok::spelling(Matcher.kind()));
D.fix("insert }").edit(TextEdit::mkInsertion(LastToken->end(), "}"));
}
return std::make_shared<ExprAttrs>(RangeTy{Begin, LastToken->end()},
std::move(Binds), std::move(Rec));
}

/// expr_simple : INT
/// | FLOAT
/// | string
Expand Down Expand Up @@ -336,6 +502,9 @@ class Parser {
return parseExprPath();
case tok_l_paren:
return parseExprParen();
case tok_kw_rec:
case tok_l_curly:
return parseExprAttrs();
default:
return nullptr;
}
Expand Down
6 changes: 6 additions & 0 deletions libnixf/src/Parse/Token.h
Original file line number Diff line number Diff line change
Expand Up @@ -28,12 +28,18 @@ constexpr std::string_view spelling(TokenKind Kind) {
return "''";
case tok_dollar_curly:
return "${";
case tok_l_curly:
return "{";
case tok_r_curly:
return "}";
case tok_l_paren:
return "(";
case tok_r_paren:
return ")";
case tok_eq:
return "=";
case tok_semi_colon:
return ";";
default:
assert(false && "Not yet implemented!");
}
Expand Down
Loading

0 comments on commit b0d28d2

Please sign in to comment.