///|
/// A small cursor over an IFC physical-file string.
priv struct Cursor {
source : String
mut offset : Int
mut line : Int
mut column : Int
}
///|
fn Cursor::new(source : String) -> Cursor {
{ source, offset: 0, line: 1, column: 1 }
}
///|
fn Cursor::position(self : Cursor) -> SourcePosition {
make_position(self.offset, self.line, self.column)
}
///|
fn Cursor::eof(self : Cursor) -> Bool {
self.offset >= self.source.length()
}
///|
fn Cursor::peek(self : Cursor) -> Char? {
self.source.get_char(self.offset)
}
///|
fn Cursor::advance(self : Cursor) -> Char? {
match self.source.get_char(self.offset) {
None => None
Some(character) => {
self.offset += 1
if character == '\n' {
self.line += 1
self.column = 1
} else {
self.column += 1
}
Some(character)
}
}
}
///|
fn Cursor::skip_space(self : Cursor) -> Unit {
for ;; {
match self.peek() {
Some(' ' | '\t' | '\r' | '\n') => ignore(self.advance())
Some('/') =>
if self.offset + 1 < self.source.length() &&
self.source[self.offset + 1] == '/' {
ignore(self.advance())
ignore(self.advance())
for ;; {
match self.peek() {
None => break
Some('\n') => break
Some(_) => ignore(self.advance())
}
}
} else {
break
}
Some('-') =>
if self.offset + 1 < self.source.length() &&
self.source[self.offset + 1] == '-' {
ignore(self.advance())
ignore(self.advance())
for ;; {
match self.peek() {
None => break
Some('\n') => break
Some(_) => ignore(self.advance())
}
}
} else {
break
}
_ => break
}
}
}
///|
fn is_identifier_start(character : Char) -> Bool {
character == '_' || character.is_ascii_alphabetic()
}
///|
fn is_identifier_continue(character : Char) -> Bool {
is_identifier_start(character) || character.is_ascii_digit()
}
///|
fn is_symbol_continue(character : Char) -> Bool {
is_identifier_continue(character) || character == '-'
}
///|
fn Cursor::read_symbol(self : Cursor) -> String raise ParseError {
self.skip_space()
let start = self.offset
guard self.peek() is Some(character) && is_symbol_continue(character) else {
raise expected(self.offset, self.line, self.column, "symbol")
}
ignore(self.advance())
for ;; {
match self.peek() {
Some(character) if is_symbol_continue(character) => ignore(self.advance())
_ => break
}
}
self.source[start:self.offset].to_owned()
}
///|
fn Cursor::read_until(
self : Cursor,
delimiter : Char,
) -> String raise ParseError {
let start = self.offset
let mut escaped = false
for ;; {
match self.advance() {
None => raise unexpected_end(self.offset, self.line, self.column)
Some(character) =>
if escaped {
escaped = false
} else if character == '\\' {
escaped = true
} else if character == delimiter {
return self.source[start:self.offset - 1].to_owned()
}
}
}
}