///|
fn boundary(input : String, pos : Int) -> Bool {
  if pos <= 0 || pos >= input.length() {
    return true
  }
  let left = input[pos - 1].to_int()
  let right = input[pos].to_int()
  !(left >= 0xD800 && left <= 0xDBFF && right >= 0xDC00 && right <= 0xDFFF)
}

///|
/// Replay ordered edits. Overlap, out-of-bounds and split-surrogate offsets fail.
pub fn apply_edits(
  input : String,
  edits : Array[Edit],
) -> String raise RepairError {
  let out = StringBuilder()
  let mut cursor = 0
  for edit in edits {
    if edit.start < cursor ||
      edit.end < edit.start ||
      edit.end > input.length() ||
      !boundary(input, edit.start) ||
      !boundary(input, edit.end) {
      raise Rejected("INVALID_EDIT", cursor)
    }
    out.write_string(input[cursor:edit.start].to_owned())
    out.write_string(edit.replacement)
    cursor = edit.end
  }
  out.write_string(input[cursor:].to_owned())
  out.to_string()
}

///|
fn Parser::edit(
  self : Parser,
  start : Int,
  end : Int,
  replacement : String,
  code : String,
) -> Unit raise RepairError {
  if self.edits.length() >= self.options.max_edits {
    raise Rejected("EDIT_LIMIT", start)
  }
  let e = Edit::{ start, end, replacement, code, }
  self.edits.push(e)
  let mut i = self.edits.length() - 1
  while i > 0 && self.edits[i - 1].start > start {
    self.edits[i] = self.edits[i - 1]
    i -= 1
  }
  self.edits[i] = e
}

///|
fn identifier_start(c : Int) -> Bool {
  (c >= 65 && c <= 90) || (c >= 97 && c <= 122) || c == 95 || c == 36
}

///|
fn Parser::key(self : Parser) -> String raise RepairError {
  if self.peek() == 39 && self.options.relaxed {
    return self.single_string()
  }
  if self.peek() == 34 {
    return self.string_token()
  }
  if !self.options.relaxed || !identifier_start(self.peek()) {
    raise Rejected("EXPECTED_KEY", self.pos)
  }
  let start = self.pos
  self.pos += 1
  while identifier_start(self.peek()) ||
        (self.peek() >= 48 && self.peek() <= 57) {
    self.pos += 1
  }
  let key = self.source[start:self.pos].to_owned()
  self.edit(start, self.pos, Json::string(key).stringify(), "QUOTE_KEY")
  key
}