// Portions adapted from google/go-jsonnet v0.21.0 (Apache-2.0).
// https://github.com/google/go-jsonnet/tree/67968688d9952f50662e979fab1bef889d0d3e57
// Source: internal/parser/string_util.go
// Copyright 2016 Google Inc. All rights reserved.
// Modifications: Ported string decoding to MoonBit and deferred it until token consumption.
// License and retained attribution: LICENSE and NOTICE at the module root.

// Go v0.21.0 internal/parser/string_util.go unescape semantics.
// Copyright Google Inc.; Apache-2.0; see NOTICE.

///|
fn literal_hex(bytes : Bytes, start : Int) -> Int? {
  let mut value = 0
  for i in start..<(start + 4) {
    let b = bytes[i].to_int()
    let digit = if b >= 48 && b <= 57 {
      b - 48
    } else if b >= 65 && b <= 70 {
      b - 55
    } else if b >= 97 && b <= 102 {
      b - 87
    } else {
      return None
    }
    value = value * 16 + digit
  }
  Some(value)
}

///|
fn unescape_literal(raw : String, loc : Location) -> String raise JsonnetError {
  let bytes = @utf8.encode(raw)
  let out = StringBuilder()
  let mut i = 0
  let error = fn(message) { problem(Syntax, loc, message) }
  while i < bytes.length() {
    if bytes[i] != b'\\' {
      let start = i
      while i < bytes.length() && bytes[i] != b'\\' {
        i += 1
      }
      out.write_string(@utf8.decode_lossy(bytes[start:i]))
      continue
    }
    i += 1
    if i == bytes.length() {
      raise error("Truncated escape sequence in string literal.")
    }
    let esc = bytes[i]
    i += 1
    match esc {
      b'"' | b'\'' | b'\\' | b'/' =>
        out.write_char(esc.to_int().to_char().unwrap())
      b'b' => out.write_char('\b')
      b'f' => out.write_char('\u000c')
      b'n' => out.write_char('\n')
      b'r' => out.write_char('\r')
      b't' => out.write_char('\t')
      b'u' => {
        if i + 4 > bytes.length() {
          raise error("Truncated unicode escape sequence in string literal.")
        }
        let mut code = literal_hex(bytes, i).unwrap_or_else(() => {
          raise error(
            "Unicode escape sequence was malformed: " +
            decode_go_utf8(bytes[:4].to_owned()),
          )
        })
        i += 4
        if code >= 0xd800 && code <= 0xdfff {
          if i + 6 > bytes.length() {
            raise error(
              "Truncated unicode surrogate pair escape sequence in string literal.",
            )
          }
          if bytes[i] != b'\\' || bytes[i + 1] != b'u' {
            raise error(
              "Unicode surrogate pair escape sequence missing low surrogate in string literal.",
            )
          }
          i += 2
          let low = literal_hex(bytes, i).unwrap_or_else(() => {
            raise error(
              "Unicode low surrogate escape sequence was malformed: " +
              decode_go_utf8(bytes[:4].to_owned()),
            )
          })
          i += 4
          code = if code <= 0xdbff && low >= 0xdc00 && low <= 0xdfff {
            0x10000 + (code - 0xd800) * 1024 + low - 0xdc00
          } else {
            0xfffd
          }
        }
        out.write_char(code.to_char().unwrap())
      }
      _ => {
        let c = @utf8.decode_lossy(bytes[i - 1:]).to_array()[0]
        raise error(
          "Unknown escape sequence in string literal: \\" + c.to_string(),
        )
      }
    }
  }
  out.to_string()
}