///|
#callsite(autofill(loc))
pub fn[A] error(msg : String, loc~ : SourceLoc) -> A raise ControlFlow {
  raise Error(msg + " " + loc.to_string())
}

///|
pub fn control_error(msg : String) -> ControlFlow {
  Error(msg)
}

///|
fn fromCharCode(hex : StringView, base : Int) -> Char {
  (try! @string.parse_int(hex, base~)).to_char().unwrap_or('?')
}

///|
pub fn manualUnescape(input : StringView, buf : StringBuilder) -> Unit {
  for state = input {
    lexmatch state with longest {
      // EOF handled by the `_` case below
      (re"^(?:\\n)", after=rest) => {
        buf.write_char('\n')
        continue rest
      }
      (re"^(?:\\t)", after=rest) => {
        buf.write_char('\t')
        continue rest
      }
      (re"^(?:\\r)", after=rest) => {
        buf.write_char('\r')
        continue rest
      }
      // Backspace
      (re"^(?:\\b)", after=rest) => {
        buf.write_char('\b')
        continue rest
      }
      // Unicode escape
      (re"^(?:\\u[0-9a-fA-F]{4})" as raw, after=rest) => {
        buf.write_char(fromCharCode(raw.exact_view(start=2), 16))
        continue rest
      }
      (re"^(?:\\x[0-9a-fA-F]{2})" as raw, after=rest) => {
        buf.write_char(fromCharCode(raw.exact_view(start=2), 16))
        continue rest
      }
      // Unicode escape with braces
      (re"^(?:\\u[{][0-9a-fA-F]+[}])" as raw, after=rest) => {
        buf.write_char(
          fromCharCode(raw.exact_view(start=3, end=raw.length() - 1), 16),
        )
        continue rest
      }
      // Octal escape
      (re"^(?:\\o[0-3][0-7]{2})" as raw, after=rest) => {
        buf.write_char(fromCharCode(raw.exact_view(start=2), 8))
        continue rest
      }
      (re"^(?:\\\\)", after=rest) => {
        buf.write_char('\\')
        continue rest
      }
      (re"^(?:.)" as c, after=rest) => {
        buf.write_char(c)
        continue rest
      }
      _ => break
    }
  }
}

///|
test "manualUnescape" {
  let builder = @builtin.StringBuilder()
  manualUnescape("\\nhello", builder)
  inspect(
    builder.to_string(),
    content=(
      #|
      #|hello
    ),
  )
  let builder = @builtin.StringBuilder()
  manualUnescape("hello\\nworld", builder)
  inspect(
    builder.to_string(),
    content=(
      #|hello
      #|world
    ),
  )
  let builder = @builtin.StringBuilder()
  manualUnescape("\\u0041", builder)
  inspect(builder.to_string(), content="A")
  let builder = @builtin.StringBuilder()
  manualUnescape("\\x41", builder)
  inspect(builder.to_string(), content="A")
  let builder = @builtin.StringBuilder()
  manualUnescape("\\o061", builder)
  inspect(builder.to_string(), content="1")
}