///|
fn unicode_character(code : Int) -> @parsec.Parser[Char, Char] {
  if code >= 0xD800 && code <= 0xDBFF {
    raw_char('\\', expected="low surrogate escape")
    .then_right(raw_char('u', expected="low surrogate escape"))
    .then_right(code_unit())
    .flat_map(low => {
      if low >= 0xDC00 && low <= 0xDFFF {
        @parsec.Parser::pure(
          (0x10000 + (code - 0xD800) * 1024 + (low - 0xDC00)).unsafe_to_char(),
        )
      } else {
        fail_at("low surrogate")
      }
    })
  } else if code >= 0xDC00 && code <= 0xDFFF {
    fail_at("non-surrogate Unicode escape")
  } else {
    @parsec.Parser::pure(code.unsafe_to_char())
  }
}

///|
fn escaped_character() -> @parsec.Parser[Char, Char] {
  raw_char('\\', expected="escape sequence").then_right(
    @parsec.Parser::choice([
      raw_char('"', expected="escape sequence").replace('"'),
      raw_char('\\', expected="escape sequence").replace('\\'),
      raw_char('/', expected="escape sequence").replace('/'),
      raw_char('b', expected="escape sequence").replace('\b'),
      raw_char('f', expected="escape sequence").replace('\f'),
      raw_char('n', expected="escape sequence").replace('\n'),
      raw_char('r', expected="escape sequence").replace('\r'),
      raw_char('t', expected="escape sequence").replace('\t'),
      raw_char('u', expected="Unicode escape")
      .then_right(code_unit())
      .flat_map(unicode_character),
    ]),
  )
}

///|
fn unescaped_character() -> @parsec.Parser[Char, Char] {
  @parsec.Parser::satisfy("unescaped string character", char => {
    char != '"' && char != '\\' && char.to_int() >= 0x20
  })
}

///|
fn string_character() -> @parsec.Parser[Char, Char] {
  unescaped_character().or_else(escaped_character())
}

///|
fn string(limits : JsonLimits) -> @parsec.Parser[Char, String] {
  raw_char('"', expected="opening quote")
  .then_right(
    string_character()
    .repeat_0_to_n(limits.max_string_chars)
    .then_left(string_character().not_followed_by(expected=string_limit_label))
    .map(chars => String::from_array(chars[:])),
  )
  .then_left(raw_char('"', expected="closing quote"))
  .then_left(json_whitespace())
}