///|
/// A position in a source file.
///
/// Four numbers, in three different units, and keeping them apart is the first
/// thing this port has to get right:
///
/// * `line` and `pos` are what Racket's `port-next-location` reports, in CODE
/// POINTS. They are what `raw-srcloc` carries, and what the oracle compares,
/// so they cannot be approximated by anything cheaper.
/// * `idx` is a UTF-16 code-unit index. It exists to slice a MoonBit `String`
/// and to hand a span to `error-report`, and it is never reported.
/// * `col` is the INDENTATION column, which is a partial order rather than a
/// number — see `@column.Column`. It is not `pos` minus the line start:
/// tabs and grapheme clusters both make it something else.
pub(all) struct Pos {
/// 1-based.
line : Int
/// 1-based code points from the start of the source.
pos : Int
/// 0-based UTF-16 code units from the start of the source. Slicing only.
idx : Int
/// 0-based CODE POINTS from the start of the line — Racket's port column,
/// and what its `syntax-column` reports. Not the indentation column below:
/// a tab advances this by one and `col` to a new run, and de-indenting a
/// block of `@` text is measured in this one.
col0 : Int
/// The indentation column.
col : @column.Column
} derive(Eq)
///|
pub let start : Pos = { line: 1, pos: 1, idx: 0, col0: 0, col: @column.zero, }
///|
/// A half-open range of a source file.
pub(all) struct Span {
start : Pos
end : Pos
} derive(Eq)
///|
/// An empty span at `p`, for an error that has no extent — an unexpected end of
/// input, or a missing closer.
pub fn Span::at(p : Pos) -> Span {
{ start: p, end: p, }
}
///|
/// The smallest span covering both.
pub fn Span::merge(self : Span, other : Span) -> Span {
let start = if self.start.idx <= other.start.idx {
self.start
} else {
other.start
}
let end = if self.end.idx >= other.end.idx { self.end } else { other.end }
{ start, end, }
}
///|
/// Length in UTF-16 code units — the unit `error-report` spans are in.
pub fn Span::units(self : Span) -> Int {
self.end.idx - self.start.idx
}
///|
/// `line:col`, 1-based on both, for a message.
pub fn Pos::to_display(self : Pos) -> String {
"\{self.line}:\{self.col.to_display()}"
}