// Comment and blank-line collection.
//
// Comments are deliberately NOT part of the AST. The lexer diverts them into
// this side table, keyed by the byte offset of the last real token seen, and
// the printer later re-attaches them to whichever AST node owns that offset.
// That is what makes byte-exact reprinting of commented source possible without
// every grammar rule having to thread comment fields around.
//
// Ported from wax/src/lib-utils/trivia.ml. This is the COLLECTION half, which
// is all the lexer needs; the association half (`associate`, which matches
// entries to AST spans) arrives with the printer in phase 3.
///|
/// Whether a piece of trivia began a line or trailed a token on one.
///
/// The printer needs the distinction: a comment on its own line stays on its
/// own line, while one trailing `foo; // why` has to be held back and emitted
/// after the separator rather than before it.
pub(all) enum TriviaPos {
LineStart
Inline
} derive(Eq, Debug)
///|
pub(all) enum TriviaKind {
LineComment
BlockComment
/// A `(@...)` annotation. Not produced by the Wax lexer -- Wax spells
/// annotations as attributes, which are real syntax -- but the reference
/// shares this table with its WAT front end, and keeping the case makes the
/// two ports line up.
Annotation
} derive(Eq, Debug)
///|
pub(all) enum Trivia {
Item(content~ : String, kind~ : TriviaKind, loc~ : @basic.Location)
/// A blank line, which the printer preserves: run-of-blank-lines structure is
/// part of how the source reads, so dropping it would change the output.
BlankLine
} derive(Eq, Debug)
///|
pub(all) struct Entry {
/// Byte offset of the end of the last real token before this trivia. This is
/// the join key the printer uses to decide which node owns the comment.
anchor : Int
trivia : Trivia
position : TriviaPos
} derive(Eq, Debug)
///|
/// Collection state, threaded through lexing.
///
/// The reference makes the grammar a Menhir functor over this context so every
/// semantic action can register spans. MoonBit has no functors, so phase 2 will
/// hold it in a package-level Ref instead — see parser/parser.mbty.
pub struct Context {
entries : Array[Entry]
mut at_start_of_line : Bool
mut prev_token_end : Int
locations : Array[@basic.Location]
}
///|
pub fn Context::new() -> Context {
{ entries: [], at_start_of_line: true, prev_token_end: 0, locations: [] }
}
///|
/// Record a comment (or annotation) spanning `loc`.
///
/// A line comment runs to the end of its line, so it leaves the lexer at the
/// start of the next one; a block comment does not.
pub fn Context::report_item(
self : Context,
kind : TriviaKind,
loc : @basic.Location,
content : String,
) -> Unit {
self.entries.push({
anchor: self.prev_token_end,
trivia: Item(content~, kind~, loc~),
position: if self.at_start_of_line {
LineStart
} else {
Inline
},
})
self.at_start_of_line = kind is LineComment
}
///|
/// Record a newline.
///
/// A newline while already at the start of a line means the line just ended was
/// empty, which is the only way a blank line is detected.
pub fn Context::report_newline(self : Context) -> Unit {
if self.at_start_of_line {
self.entries.push({
anchor: self.prev_token_end,
trivia: BlankLine,
position: LineStart,
})
}
self.at_start_of_line = true
}
///|
/// Record that a real token ending at byte offset `pos` was consumed.
pub fn Context::report_token(self : Context, pos : Int) -> Unit {
self.at_start_of_line = false
self.prev_token_end = pos
}
///|
/// Record that an AST node spans `loc`, so trivia can be attached to it later.
pub fn Context::record_pos(self : Context, loc : @basic.Location) -> Unit {
self.locations.push(loc)
}
///|
/// The collected trivia, in source order.
///
/// The reference accumulates onto the front of a list and reverses on use;
/// pushing onto an array keeps source order directly.
pub fn Context::entries(self : Context) -> ArrayView[Entry] {
self.entries[:]
}
///|
pub fn Context::locations(self : Context) -> ArrayView[@basic.Location] {
self.locations[:]
}