///|
priv struct Cursor {
source : String
index : Int
}
///|
/// A source position paired with a message, formatted by `Show`.
priv suberror ParseError {
ParseError(String)
}
///|
impl Show for ParseError with fn to_string(self) {
match self {
ParseError(message) => message
}
}
///|
fn Cursor::new(source : String) -> Cursor {
{ source, index: 0 }
}
///|
fn Cursor::is_end(self : Cursor) -> Bool {
self.index >= self.source.length()
}
///|
fn Cursor::peek(self : Cursor) -> Char? {
self.source.get_char(self.index)
}
///|
fn Cursor::code_unit_length(self : Cursor) -> Int {
self.source.length()
}
///|
fn Cursor::unsafe_char_at(self : Cursor, index : Int) -> Char {
self.source.get_char(index).unwrap()
}
///|
fn Cursor::next_char_index(self : Cursor, index : Int) -> Int {
if index >= self.code_unit_length() {
index
} else if self.source.code_unit_at(index).is_leading_surrogate() {
index + 2
} else {
index + 1
}
}
///|
fn Cursor::advance_code_units(self : Cursor, amount : Int) -> Cursor {
{ ..self, index: self.index + amount }
}
///|
fn Cursor::with_index(self : Cursor, index : Int) -> Cursor {
{ ..self, index, }
}
///|
fn Cursor::skip_whitespace(self : Cursor) -> Cursor {
let mut index = self.index
while index < self.code_unit_length() {
let ch = self.unsafe_char_at(index)
guard ch.is_whitespace() else { break }
index = index + ch.utf16_len()
}
self.with_index(index)
}
///|
/// True for the ASCII whitespace code units: tab through carriage return and space.
fn is_ascii_whitespace_code(code : Int) -> Bool {
code == 0x20 || (code >= 0x09 && code <= 0x0D)
}
///|
fn Cursor::slice_until(self : Cursor, end : Int) -> String {
self.source.sub(start=self.index, end~).to_owned()
}
///|
/// Builds a `ParseError` carrying the message prefixed with the 1-based line
/// and column of this cursor's position.
fn Cursor::error(self : Cursor, message : String) -> ParseError {
let (line, column) = line_column_at(self.source, self.index)
ParseError("parse error at line \{line}, column \{column}: \{message}")
}
///|
/// Computes the 1-based line and column of a UTF-16 code-unit offset in `source`.
fn line_column_at(source : String, offset : Int) -> (Int, Int) {
let mut line = 1
let mut column = 1
let mut index = 0
while index < offset {
guard source.get_char(index) is Some(ch) else { break }
if ch == '\n' {
line = line + 1
column = 1
} else {
column = column + 1
}
index = index + ch.utf16_len()
}
(line, column)
}