///|
/// The lexer's view of the text: the compiler's `char0`/`char1` lookahead
/// with its line-end rule (`next_source_char`: `\r\n` and a lone `\r` are
/// `\n`, at the `\r`'s byte), UTF-8 byte positions for spans, and UTF-16
/// unit indexes for lexemes.
priv struct Cursor {
  text : String
  mut unit : @span.Utf16Offset
  mut byte : @span.ByteOffset
}

///|
fn Cursor::Cursor(text : String) -> Cursor {
  { text, unit: @span.Utf16Offset::zero(), byte: @span.ByteOffset::zero(), }
}

///|
fn is_high_surrogate(c : Int32) -> Bool {
  c >= 0xD800 && c <= 0xDBFF
}

///|
fn is_low_surrogate(c : Int32) -> Bool {
  c >= 0xDC00 && c <= 0xDFFF
}

///|
/// The character at unit `i` as the compiler reads it, and its width in
/// UTF-16 units and in UTF-8 bytes. A surrogate pair reads as its high unit
/// (the lexer compares with ASCII only), 2 units, 4 bytes. A lone surrogate
/// is 1 unit, 3 bytes. `None` at or past the end.
fn Cursor::read_at(
  self : Cursor,
  offset : @span.Utf16Offset,
) -> (Char, Int32, Int32)? {
  let i = offset.to_int()
  let n = self.text.length()
  if i >= n {
    return None
  }
  let c = self.text.code_unit_at(i).to_int()
  if c == '\r'.to_int() {
    if i + 1 < n && self.text.code_unit_at(i + 1).to_int() == '\n'.to_int() {
      Some(('\n', 2, 2))
    } else {
      Some(('\n', 1, 1))
    }
  } else if c < 0x80 {
    Some((c.unsafe_to_char(), 1, 1))
  } else if c < 0x800 {
    Some((c.unsafe_to_char(), 1, 2))
  } else if is_high_surrogate(c) &&
    i + 1 < n &&
    is_low_surrogate(self.text.code_unit_at(i + 1).to_int()) {
    Some((c.unsafe_to_char(), 2, 4))
  } else {
    Some((c.unsafe_to_char(), 1, 3))
  }
}

///|
/// The compiler's `char0`.
fn Cursor::peek(self : Cursor) -> Char? {
  self.read_at(self.unit).map(r => r.0)
}

///|
/// The compiler's `char1`.
fn Cursor::peek1(self : Cursor) -> Char? {
  match self.read_at(self.unit) {
    None => None
    Some((_, units, _)) =>
      self.read_at(self.unit.shift(units).unwrap()).map(r => r.0)
  }
}

///|
/// Past `char0`. At the end nothing changes.
fn Cursor::advance(self : Cursor) -> Unit {
  match self.read_at(self.unit) {
    None => ()
    Some((_, units, bytes)) => {
      self.unit = self.unit.shift(units).unwrap()
      self.byte = self.byte.shift(bytes).unwrap()
    }
  }
}

///|
/// The compiler's `get_position()`: the byte offset of `char0`, or the byte
/// length of the text at the end.
fn Cursor::position(self : Cursor) -> @span.ByteOffset {
  self.byte
}

///|
fn Cursor::unit(self : Cursor) -> @span.Utf16Offset {
  self.unit
}

///|
fn Cursor::at_end(self : Cursor) -> Bool {
  self.unit.to_int() >= self.text.length()
}

///|
/// The text from unit `start_unit` to `char0`, raw (`\r\n` stays `\r\n`).
fn Cursor::slice(self : Cursor, start_unit : @span.Utf16Offset) -> String {
  self.text.unsafe_substring(start=start_unit.to_int(), end=self.unit.to_int())
}