///|
/// A source text with its line table. The table is built once; every
/// lookup is a pure function of it.
pub struct SourceText {
priv text : String
priv name : String?
/// The offset of the first unit of each line; `line_starts[0] == 0`.
priv line_starts : Array[Int32]
}
///|
/// LF and CRLF end a line; a lone CR does not.
pub fn SourceText::new(text : String, name? : String) -> SourceText {
let line_starts = [0]
for i in 0.. String {
self.text
}
///|
/// The name given at construction (a file name), if any.
pub fn SourceText::name(self : SourceText) -> String? {
self.name
}
///|
/// The offset just past the last unit.
pub fn SourceText::end(self : SourceText) -> Offset {
Offset(self.text.length())
}
///|
/// One more than the number of line breaks.
pub fn SourceText::line_count(self : SourceText) -> Int32 {
self.line_starts.length()
}
///|
/// The 0-based index of the line that holds `unit` (clamped to the text).
fn SourceText::line_index(self : SourceText, unit : Int32) -> Int32 {
let mut lo = 0
let mut hi = self.line_starts.length() - 1
while lo < hi {
let mid = lo + (hi - lo + 1) / 2
if self.line_starts[mid] <= unit {
lo = mid
} else {
hi = mid - 1
}
}
lo
}
///|
/// The line and column of `offset`. An offset past the end is the position
/// just after the last unit.
pub fn SourceText::position(self : SourceText, offset : Offset) -> Position {
let unit = if offset.0 > self.text.length() {
self.text.length()
} else {
offset.0
}
let line = self.line_index(unit)
Position::new(Line(line + 1), Column(unit - self.line_starts[line] + 1))
}
///|
/// The span of a line without its line break; `None` past the last line.
pub fn SourceText::line_span(self : SourceText, line : Line) -> Span? {
guard line.0 <= self.line_starts.length() else { return None }
let start = self.line_starts[line.0 - 1]
let mut end = if line.0 < self.line_starts.length() {
self.line_starts[line.0]
} else {
self.text.length()
}
if end > start && self.text.code_unit_at(end - 1).to_int() == '\n'.to_int() {
end -= 1
}
if end > start && self.text.code_unit_at(end - 1).to_int() == '\r'.to_int() {
end -= 1
}
Some(Offset(start).to(Offset(end)))
}
///|
/// The text of a line, without its line break.
pub fn SourceText::line_text(self : SourceText, line : Line) -> String? {
self.line_span(line).map(s => self.slice(s))
}
///|
/// The text of `span`, clamped to the text.
pub fn SourceText::slice(self : SourceText, span : Span) -> String {
let n = self.text.length()
let a = if span.start.0 > n { n } else { span.start.0 }
let b = if span.end().0 > n { n } else { span.end().0 }
self.text.unsafe_substring(start=a, end=b)
}