Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions libnixf/include/nixf/Basic/Nodes/Basic.h
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
#pragma once

#include "nixf/Basic/Nodes/Comment.h"
#include "nixf/Basic/Range.h"

#include <boost/container/small_vector.hpp>

#include <cassert>
#include <string>
#include <vector>

namespace nixf {

Expand All @@ -25,6 +27,8 @@ class Node {
private:
NodeKind Kind;
LexerCursorRange Range;
std::vector<CommentPtr> LeadingComments;
std::vector<CommentPtr> TrailingComments;

protected:
explicit Node(NodeKind Kind, LexerCursorRange Range)
Expand Down Expand Up @@ -65,6 +69,22 @@ class Node {
auto Length = rCur().offset() - Begin;
return Src.substr(Begin, Length);
}

void addLeadingComment(CommentPtr Comment) {
LeadingComments.push_back(std::move(Comment));
}

void addTrailingComment(CommentPtr Comment) {
TrailingComments.push_back(std::move(Comment));
}

[[nodiscard]] const std::vector<CommentPtr> &leadingComments() const {
return LeadingComments;
}

[[nodiscard]] const std::vector<CommentPtr> &trailingComments() const {
return TrailingComments;
}
};

class Expr : public Node {
Expand Down
41 changes: 41 additions & 0 deletions libnixf/include/nixf/Basic/Nodes/Comment.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
#pragma once

#include "nixf/Basic/Range.h"

#include <cstdint>
#include <memory>
#include <string_view>

namespace nixf {

/// \brief Represents a comment in source code
class Comment {
public:
enum CommentKind : std::uint8_t {
CK_LineComment, // # comment
CK_BlockComment, // /* comment */
};

private:
CommentKind Kind;
LexerCursorRange Range;
std::string_view Text;

public:
Comment(CommentKind Kind, LexerCursorRange Range, std::string_view Text)
: Kind(Kind), Range(Range), Text(Text) {}

[[nodiscard]] CommentKind kind() const { return Kind; }
[[nodiscard]] LexerCursorRange range() const { return Range; }
[[nodiscard]] std::string_view text() const { return Text; }

/// \brief Check if this is a directive comment (e.g., # nixf-ignore: ...)
[[nodiscard]] bool isDirective() const {
return Text.find("nixf-ignore:") != std::string_view::npos ||
Text.find("nixf-disable:") != std::string_view::npos;
}
};

using CommentPtr = std::shared_ptr<Comment>;

} // namespace nixf
16 changes: 15 additions & 1 deletion libnixf/src/Parse/Lexer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,7 @@ bool Lexer::consumeWhitespaces() {
bool Lexer::consumeComments() {
if (eof())
return false;
LexerCursor Begin = cur();
if (std::optional<LexerCursorRange> BeginRange = consumePrefix("/*")) {
// Consume block comments until we meet '*/'
while (true) {
Expand All @@ -126,17 +127,30 @@ bool Lexer::consumeComments() {
LexerCursorRange{cur()});
Diag.note(NK::NK_BCommentBegin, *BeginRange);
Diag.fix("insert */").edit(TextEdit::mkInsertion(cur(), "*/"));
LexerCursorRange Range{Begin, cur()};
auto Text = Src.substr(Begin.Offset, cur().Offset - Begin.Offset);
Comments.push_back(
std::make_shared<Comment>(Comment::CK_BlockComment, Range, Text));
return true;
}
if (consumePrefix("*/"))
if (consumePrefix("*/")) {
// We found the ending '*/'
LexerCursorRange Range{Begin, cur()};
auto Text = Src.substr(Begin.Offset, cur().Offset - Begin.Offset);
Comments.push_back(
std::make_shared<Comment>(Comment::CK_BlockComment, Range, Text));
return true;
}
consume(); // Consume a character (block comment body).
}
} else if (consumePrefix("#")) {
// Single line comments, consume blocks until we meet EOF or '\n' or '\r'
while (true) {
if (eof() || consumeEOL()) {
LexerCursorRange Range{Begin, cur()};
auto Text = Src.substr(Begin.Offset, cur().Offset - Begin.Offset);
Comments.push_back(
std::make_shared<Comment>(Comment::CK_LineComment, Range, Text));
return true;
}
consume(); // Consume a character (single line comment body).
Expand Down
11 changes: 11 additions & 0 deletions libnixf/src/Parse/Lexer.h
Original file line number Diff line number Diff line change
Expand Up @@ -10,17 +10,20 @@
#include "Token.h"

#include "nixf/Basic/Diagnostic.h"
#include "nixf/Basic/Nodes/Comment.h"
#include "nixf/Basic/Range.h"

#include <cassert>
#include <optional>
#include <string_view>
#include <vector>

namespace nixf {

class Lexer {
const std::string_view Src;
std::vector<Diagnostic> &Diags;
std::vector<CommentPtr> Comments;

LexerCursor Cur;

Expand Down Expand Up @@ -130,6 +133,14 @@ class Lexer {

[[nodiscard]] const LexerCursor &cur() const { return Cur; }

/// \brief Get all collected comments
[[nodiscard]] const std::vector<CommentPtr> &comments() const {
return Comments;
}

/// \brief Clear collected comments
void clearComments() { Comments.clear(); }

Token lex();
Token lexString();
Token lexIndString();
Expand Down
129 changes: 129 additions & 0 deletions libnixf/src/Parse/ParseSupport.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,10 @@ std::shared_ptr<Node> nixf::parse(std::string_view Src,

std::shared_ptr<Expr> nixf::Parser::parse() {
auto Expr = parseExpr();
collectComments();
if (Expr)
registerNode(Expr.get());
attachAllComments();
if (Token Tok = peek(); Tok.kind() != tok::tok_eof) {
// TODO: maybe we'd like to have multiple expressions in a single file.
// Report an error.
Expand Down Expand Up @@ -118,3 +122,128 @@ Parser::ExpectResult Parser::expect(TokenKind Kind) {
.edit(TextEdit::mkInsertion(Insert, std::string(tok::spelling(Kind))));
return {&D};
}

void Parser::collectComments() {
const auto &LexerComments = Lex.comments();
for (const auto &C : LexerComments) {
PendingComments.push_back(C);
}
Lex.clearComments();
}

void Parser::registerNode(Node *N) {
if (!N)
return;
ParsedNodes.push_back({N, N->lCur(), N->rCur()});
// Recursively register children
for (Node *Child : N->children()) {
registerNode(Child);
}
}

void Parser::attachAllComments() {
if (ParsedNodes.empty() || PendingComments.empty())
return;

// Sort nodes and comments by position
std::sort(ParsedNodes.begin(), ParsedNodes.end(),
[](const NodeInfo &A, const NodeInfo &B) {
return A.StartCur.offset() < B.StartCur.offset();
});

std::sort(PendingComments.begin(), PendingComments.end(),
[](const CommentPtr &A, const CommentPtr &B) {
return A->range().lCur().offset() < B->range().lCur().offset();
});

// Mark which comments have been used
std::vector<bool> CommentUsed(PendingComments.size(), false);

// First pass: attach trailing comments
for (size_t I = 0; I < ParsedNodes.size(); ++I) {
Node *CurrentNode = ParsedNodes[I].N;
LexerCursor NodeEnd = ParsedNodes[I].EndCur;

// Find the start of the next node (if exists)
LexerCursor NextNodeStart =
(I + 1 < ParsedNodes.size())
? ParsedNodes[I + 1].StartCur
: LexerCursor::unsafeCreate(INT64_MAX, INT64_MAX, SIZE_MAX);

// Look for comments after the current node
for (size_t J = 0; J < PendingComments.size(); ++J) {
if (CommentUsed[J])
continue;

const auto &Comment = PendingComments[J];
LexerCursor CommentStart = Comment->range().lCur();
LexerCursor CommentEnd = Comment->range().rCur();

// Comment must be after the current node
if (CommentStart.offset() <= NodeEnd.offset())
continue;

// If comment is after the next node, stop
if (CommentStart.offset() >= NextNodeStart.offset())
break;

// Trailing comment rules:
// On the same line as the node, or before the next line starts
bool IsSameLine = (CommentStart.line() == NodeEnd.line());
bool IsBeforeNextLine = (CommentEnd.line() == NodeEnd.line());

if (IsSameLine || IsBeforeNextLine) {
CurrentNode->addTrailingComment(Comment);
CommentUsed[J] = true;
} else {
// Not on the same line, this is not trailing
break;
}
}
}

// Second pass: attach leading comments
for (size_t I = 0; I < ParsedNodes.size(); ++I) {
Node *CurrentNode = ParsedNodes[I].N;
LexerCursor NodeStart = ParsedNodes[I].StartCur;

// Find the boundary from the previous node (including its trailing
// comments)
LexerCursor PrevBoundary = LexerCursor::unsafeCreate(0, 0, 0);

if (I > 0) {
Node *PrevNode = ParsedNodes[I - 1].N;
LexerCursor PrevNodeEnd = ParsedNodes[I - 1].EndCur;

// Previous boundary = node end + trailing comments region
if (!PrevNode->trailingComments().empty()) {
// If there are trailing comments, boundary is after the last one
PrevBoundary = PrevNode->trailingComments().back()->range().rCur();
} else {
// Otherwise, it's the node's end position
PrevBoundary = PrevNodeEnd;
}
}

// Find comments in the range [PrevBoundary, NodeStart)
for (size_t J = 0; J < PendingComments.size(); ++J) {
if (CommentUsed[J])
continue;

const auto &Comment = PendingComments[J];
LexerCursor CommentStart = Comment->range().lCur();
LexerCursor CommentEnd = Comment->range().rCur();

// Comment must be after the previous boundary and before current node
if (CommentStart.offset() <= PrevBoundary.offset())
continue;

if (CommentEnd.offset() > NodeStart.offset())
continue;

// This is a leading comment
CurrentNode->addLeadingComment(Comment);
CommentUsed[J] = true;
}
}
}
18 changes: 18 additions & 0 deletions libnixf/src/Parse/Parser.h
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,15 @@ class Parser {
/// Sync tokens will not be consumed as "unknown".
std::multiset<TokenKind> SyncTokens;

struct NodeInfo {
Node *N;
LexerCursor StartCur;
LexerCursor EndCur;
};

std::vector<CommentPtr> PendingComments;
std::vector<NodeInfo> ParsedNodes;

class StateRAII {
Parser &P;

Expand Down Expand Up @@ -336,6 +345,15 @@ class Parser {

/// Top-level parsing.
std::shared_ptr<Expr> parse();

/// \brief Collect comments from lexer
void collectComments();

/// \brief Register a parsed node for later comment attachment
void registerNode(Node *N);

/// \brief Attach all pending comments to registered nodes
void attachAllComments();
};

} // namespace nixf
Loading
Loading