///|
/// UTF-16 offsets [start,end), excluding line terminator; next includes it.
pub(all) struct SourceLine {
  number : Int
  start : Int
  end : Int
  next : Int
  text : String
} derive(Eq, Debug, ToJson)

///|
/// Exact source index. A final newline produces a final empty line.
pub fn source_lines(source : String) -> Array[SourceLine] {
  let out : Array[SourceLine] = []
  let mut start = 0
  let mut i = 0
  while i < source.length() {
    if source[i] == '\n' || source[i] == '\r' {
      let end = i
      if source[i] == '\r' && i + 1 < source.length() && source[i + 1] == '\n' {
        i += 1
      }
      i += 1
      out.push({
        number: out.length() + 1,
        start,
        end,
        next: i,
        text: source[start:end].to_owned(),
      })
      start = i
    } else {
      i += 1
    }
  }
  out.push({
    number: out.length() + 1,
    start,
    end: i,
    next: i,
    text: source[start:i].to_owned(),
  })
  out
}

///|
fn stripped(s : String) -> String {
  s.trim(chars=" \t").to_owned()
}

///|
pub fn SourceLine::content(self : SourceLine) -> String {
  if self.number == 1 && self.text.has_prefix("\u{FEFF}") {
    self.text[1:].to_owned()
  } else {
    self.text
  }
}