///|
/// A read-only character cursor.
///
/// Every position is a character index into the original input. Sub-cursors share
/// the same character array, so error positions never drift when parsing in pieces.
priv struct Cursor {
chars : Array[Char]
mut pos : Int32
limit : Int32
}
///|
fn Cursor::new(s : String) -> Cursor {
let chars = s.to_array()
{ chars, pos: 0, limit: chars.length(), }
}
///|
fn Cursor::sub(self : Cursor, start : Int32, end : Int32) -> Cursor {
{ chars: self.chars, pos: start, limit: end, }
}
///|
fn Cursor::eof(self : Cursor) -> Bool {
self.pos >= self.limit
}
///|
fn Cursor::peek(self : Cursor) -> Char? {
if self.pos < self.limit {
Some(self.chars[self.pos])
} else {
None
}
}
///|
fn Cursor::bump(self : Cursor) -> Unit {
if self.pos < self.limit {
self.pos = self.pos + 1
}
}
///|
fn Cursor::eat(self : Cursor, c : Char) -> Bool {
match self.peek() {
Some(x) =>
if x == c {
self.bump()
true
} else {
false
}
None => false
}
}
///|
fn Cursor::skip_spaces(self : Cursor) -> Unit {
while true {
match self.peek() {
Some(c) => if is_space(c) { self.bump() } else { break }
None => break
}
}
}
///|
fn Cursor::slice(self : Cursor, start : Int32, end : Int32) -> String {
String::from_array(self.chars[start:end])
}
///|
fn is_space(c : Char) -> Bool {
c == ' ' || c == '\t' || c == '\n' || c == '\r'
}
///|
fn is_digit(c : Char) -> Bool {
c >= '0' && c <= '9'
}
///|
fn is_alpha(c : Char) -> Bool {
(c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')
}
///|
fn is_identifier_char(c : Char) -> Bool {
is_alpha(c) || is_digit(c) || c == '-'
}
///|
fn is_wildcard(c : Char) -> Bool {
c == '*' || c == 'x' || c == 'X'
}
///|
fn describe_char(c : Char) -> String {
if is_space(c) {
"whitespace"
} else {
"'\{c}'"
}
}