// JSON 포매터. 전체 JSON 문법을 재귀 하강으로 검증하고 두 칸 들여쓰기로 다시 씁니다.
// 입력은 JSON 문서 하나이고, 출력은 정리한 문서를 담은 pre 조각이나 줄:열 위치가 붙은 한국어 오류 한 줄입니다.
// 같은 입력이면 인터프리터와 웹 빌드에서 바이트까지 같은 출력을 냅니다.
// 숫자는 원문 그대로 두고 실수로 다시 읽거나 반올림하지 않습니다. 문자열은 안전한 \uXXXX 만 글자로 풀고 나머지는 원문 그대로 둡니다. 같은 키가 두 번 나와도 오류로 보지 않고 그대로 둡니다.
// JSON formatter: validates the full grammar and pretty-prints with two-space indentation, reporting errors by line and column.

// 수와 문자열은 원문 토큰을 그대로 담고, 들여쓰기는 직렬화 단계에서만 붙입니다.
enum Json값 { 없음, 참거짓(bool), 수(string), 문자열(string), 배열(Array<Json값>), 객체(Array<멤버>) }
// 키는 따옴표까지 포함한 원문입니다.
record 멤버 { 키: string, 값: Json값 }
record 읽은값 { 값: Json값, 다음: int }
record 읽은문자열 { 원문: string, 다음: int }

enum 오류종류 {
  문자열미종료, 유니코드이스케이프, 잘못된이스케이프, 제어문자,
  숫자필요, 소수점숫자필요, 지수숫자필요,
  너무깊음, 값필요, 알수없는값,
  키필요, 콜론필요, 닫는중괄호필요, 쉼표나닫는중괄호,
  닫는대괄호필요, 쉼표나닫는대괄호, 잉여내용
}
record 구문오류 { 위치: int, 종류: 오류종류 }

let 최대깊이 = 200
let 십육진글자 = Set.of(
  "0", "1", "2", "3", "4", "5", "6", "7", "8", "9",
  "a", "b", "c", "d", "e", "f", "A", "B", "C", "D", "E", "F",
)
let 한글자이스케이프 = Set.of("\"", "\\", "/", "b", "f", "n", "r", "t")

// 공용 도우미 시작 (본문이스케이프 공백인가 숫자인가 연속끝 글자인가)
// 여러 도구가 같은 원본을 씁니다. 여기서 고치지 않고 원본에서 고친 뒤 다시 생성합니다.

// 요소 본문에 넣는 사용자 입력에 씁니다. 큰따옴표를 그대로 두므로 속성 값에는 이스케이프를 씁니다.
function 본문이스케이프(원문: string) -> string {
  원문.replace("&", "&amp;").replace("<", "&lt;").replace(">", "&gt;")
}

function 공백인가(글자: string) -> bool {
  글자 == " " || 글자 == "\t" || 글자 == "\n" || 글자 == "\r"
}

function 숫자인가(글자: string) -> bool {
  let 코드 = 글자.codePointAt(0) ?? 0
  코드 >= 48 && 코드 <= 57
}

// 시작부터 맞는가가 참인 글자가 이어지는 끝 위치를 돌려줍니다. 맞는 글자가 없으면 시작 그대로입니다.
function 연속끝(글자들: Array<string>, 시작: int, 맞는가: (string) -> bool) -> int {
  let mut 끝 = 시작
  while 끝 < 글자들.length && 맞는가(글자들[끝]) { 끝 += 1 }
  끝
}

// 위치가 글자들 끝을 넘으면 거짓입니다.
function 글자인가(글자들: Array<string>, 위치: int, 글자: string) -> bool {
  위치 < 글자들.length && 글자들[위치] == 글자
}

// 공용 도우미 끝

function 오류문구(종류: 오류종류) -> string {
  match 종류 {
    case 문자열미종료 => "문자열이 끝나지 않았습니다"
    case 유니코드이스케이프 => "\\u 뒤에 16진수 4자리가 필요합니다"
    case 잘못된이스케이프 => "잘못된 이스케이프 문자입니다"
    case 제어문자 => "문자열 안의 제어문자는 이스케이프해야 합니다"
    case 숫자필요 => "숫자가 필요합니다"
    case 소수점숫자필요 => "소수점 뒤에 숫자가 필요합니다"
    case 지수숫자필요 => "지수 뒤에 숫자가 필요합니다"
    case 너무깊음 => "중첩이 너무 깊습니다 (최대 200단계)"
    case 값필요 => "값이 필요합니다"
    case 알수없는값 => "알 수 없는 값입니다"
    case 키필요 => "키(문자열)가 필요합니다"
    case 콜론필요 => "콜론(:)이 필요합니다"
    case 닫는중괄호필요 => "닫는 중괄호(\})가 필요합니다"
    case 쉼표나닫는중괄호 => "쉼표(,)나 닫는 중괄호(\})가 필요합니다"
    case 닫는대괄호필요 => "닫는 대괄호(])가 필요합니다"
    case 쉼표나닫는대괄호 => "쉼표(,)나 닫는 대괄호(])가 필요합니다"
    case 잉여내용 => "값 뒤에 불필요한 내용이 있습니다"
  }
}

function 십육진인가(글자: string) -> bool {
  글자 in 십육진글자
}

function 낱말인가(글자들: Array<string>, 위치: int, 낱말: string) -> bool {
  let 길이 = 낱말.scalars().length
  위치 + 길이 <= 글자들.length && 글자들.slice(위치, 위치 + 길이).join("") == 낱말
}

// 위치는 백슬래시를 가리킵니다.
function 이스케이프읽기(글자들: Array<string>, 위치: int) -> Result<읽은문자열, 구문오류> {
  if 위치 + 1 >= 글자들.length { return Err(구문오류 { 위치: 위치, 종류: 오류종류.문자열미종료 }) }
  let 표지 = 글자들[위치 + 1]
  if 표지 in 한글자이스케이프 { return Ok(읽은문자열 { 원문: "\\" + 표지, 다음: 위치 + 2 }) }
  if 표지 != "u" { return Err(구문오류 { 위치: 위치 + 1, 종류: 오류종류.잘못된이스케이프 }) }
  if 위치 + 5 >= 글자들.length { return Err(구문오류 { 위치: 위치, 종류: 오류종류.유니코드이스케이프 }) }
  let 틀린자리 = [for k in 0..<4 if !십육진인가(글자들[위치 + 2 + k]) => 위치 + 2 + k]
  if 틀린자리.length > 0 { return Err(구문오류 { 위치: 틀린자리[0], 종류: 오류종류.유니코드이스케이프 }) }
  let 십육진 = 글자들.slice(위치 + 2, 위치 + 6).join("")
  let 코드 = toIntRadix(십육진, 16) ?? -1
  let 문자 = fromCodePoint(코드) ?? ""
  // 제어문자, 따옴표, 백슬래시, 글자가 될 수 없는 코드는 풀면 문자열이 깨지므로 원문으로 둡니다.
  let 풀어도되는가 = 코드 >= 32 && 코드 != 34 && 코드 != 92 && 문자.byteLength() > 0
  let 원문 = if 풀어도되는가 { 문자 } else { "\\u" + 십육진 }
  Ok(읽은문자열 { 원문: 원문, 다음: 위치 + 6 })
}

// 위치는 여는 따옴표를 가리키고, 원문은 양쪽 따옴표를 포함합니다.
function 문자열읽기(글자들: Array<string>, 위치: int) -> Result<읽은문자열, 구문오류> {
  let mut 부분들 = ["\""]
  let mut i = 위치 + 1
  while i < 글자들.length {
    let 글자 = 글자들[i]
    if 글자 == "\"" {
      부분들.push("\"")
      return Ok(읽은문자열 { 원문: 부분들.join(""), 다음: i + 1 })
    }
    if 글자 == "\\" {
      let 이스케이프 = 이스케이프읽기(글자들, i)?
      부분들.push(이스케이프.원문)
      i = 이스케이프.다음
    } else {
      // RFC 8259 는 U+0000..U+001F 를 문자열 안에 그대로 두지 못하게 합니다.
      if (글자.codePointAt(0) ?? 0) < 32 { return Err(구문오류 { 위치: i, 종류: 오류종류.제어문자 }) }
      부분들.push(글자)
      i += 1
    }
  }
  Err(구문오류 { 위치: 위치, 종류: 오류종류.문자열미종료 })
}

// -? (0 | [1-9][0-9]*) (. [0-9]+)? ([eE][+-]?[0-9]+)? 를 받습니다. 앞자리 0 뒤의 숫자는 이 토큰에 넣지 않으므로 01 은 뒤쪽에서 오류가 됩니다.
function 숫자읽기(글자들: Array<string>, 위치: int) -> Result<읽은값, 구문오류> {
  let 정수시작 = if 글자인가(글자들, 위치, "-") { 위치 + 1 } else { 위치 }
  if 정수시작 >= 글자들.length || !숫자인가(글자들[정수시작]) {
    return Err(구문오류 { 위치: 정수시작, 종류: 오류종류.숫자필요 })
  }
  let 정수끝 = if 글자들[정수시작] == "0" { 정수시작 + 1 } else { 연속끝(글자들, 정수시작, 숫자인가) }
  let 소수끝 = if 글자인가(글자들, 정수끝, ".") {
    let 끝 = 연속끝(글자들, 정수끝 + 1, 숫자인가)
    if 끝 == 정수끝 + 1 { return Err(구문오류 { 위치: 끝, 종류: 오류종류.소수점숫자필요 }) }
    끝
  } else {
    정수끝
  }
  let 지수끝 = if 글자인가(글자들, 소수끝, "e") || 글자인가(글자들, 소수끝, "E") {
    let 부호뒤 = if 글자인가(글자들, 소수끝 + 1, "+") || 글자인가(글자들, 소수끝 + 1, "-") { 소수끝 + 2 } else { 소수끝 + 1 }
    let 끝 = 연속끝(글자들, 부호뒤, 숫자인가)
    if 끝 == 부호뒤 { return Err(구문오류 { 위치: 끝, 종류: 오류종류.지수숫자필요 }) }
    끝
  } else {
    소수끝
  }
  Ok(읽은값 { 값: Json값.수(글자들.slice(위치, 지수끝).join("")), 다음: 지수끝 })
}

// 깊이는 공백을 건너뛰기 전에 검사하므로 너무깊음 오류의 위치는 공백 앞입니다.
function 값읽기(글자들: Array<string>, 위치: int, 깊이: int) -> Result<읽은값, 구문오류> {
  if 깊이 > 최대깊이 { return Err(구문오류 { 위치: 위치, 종류: 오류종류.너무깊음 }) }
  let i = 연속끝(글자들, 위치, 공백인가)
  if i >= 글자들.length { return Err(구문오류 { 위치: i, 종류: 오류종류.값필요 }) }
  let 글자 = 글자들[i]
  match 글자 {
    case "\{" => 객체읽기(글자들, i, 깊이)
    case "[" => 배열읽기(글자들, i, 깊이)
    case "\"" => {
      let 읽음 = 문자열읽기(글자들, i)?
      Ok(읽은값 { 값: Json값.문자열(읽음.원문), 다음: 읽음.다음 })
    }
    case _ => {
      if 글자 == "-" || 숫자인가(글자) {
        숫자읽기(글자들, i)
      } else if 낱말인가(글자들, i, "true") {
        Ok(읽은값 { 값: Json값.참거짓(true), 다음: i + 4 })
      } else if 낱말인가(글자들, i, "false") {
        Ok(읽은값 { 값: Json값.참거짓(false), 다음: i + 5 })
      } else if 낱말인가(글자들, i, "null") {
        Ok(읽은값 { 값: Json값.없음, 다음: i + 4 })
      } else {
        Err(구문오류 { 위치: i, 종류: 오류종류.알수없는값 })
      }
    }
  }
}

function 객체읽기(글자들: Array<string>, 위치: int, 깊이: int) -> Result<읽은값, 구문오류> {
  let 첫자리 = 연속끝(글자들, 위치 + 1, 공백인가)
  if 글자인가(글자들, 첫자리, "\}") { return Ok(읽은값 { 값: Json값.객체([]), 다음: 첫자리 + 1 }) }
  let mut 멤버들: Array<멤버> = []
  let mut i = 첫자리
  while true {
    let 키자리 = 연속끝(글자들, i, 공백인가)
    if !글자인가(글자들, 키자리, "\"") { return Err(구문오류 { 위치: 키자리, 종류: 오류종류.키필요 }) }
    let 키 = 문자열읽기(글자들, 키자리)?
    let 콜론자리 = 연속끝(글자들, 키.다음, 공백인가)
    if !글자인가(글자들, 콜론자리, ":") { return Err(구문오류 { 위치: 콜론자리, 종류: 오류종류.콜론필요 }) }
    let 읽음 = 값읽기(글자들, 콜론자리 + 1, 깊이 + 1)?
    멤버들.push(멤버 { 키: 키.원문, 값: 읽음.값 })
    let 뒤 = 연속끝(글자들, 읽음.다음, 공백인가)
    if 뒤 >= 글자들.length { return Err(구문오류 { 위치: 뒤, 종류: 오류종류.닫는중괄호필요 }) }
    if 글자들[뒤] == "\}" { return Ok(읽은값 { 값: Json값.객체(멤버들), 다음: 뒤 + 1 }) }
    if 글자들[뒤] != "," { return Err(구문오류 { 위치: 뒤, 종류: 오류종류.쉼표나닫는중괄호 }) }
    i = 뒤 + 1
  }
}

function 배열읽기(글자들: Array<string>, 위치: int, 깊이: int) -> Result<읽은값, 구문오류> {
  let 첫자리 = 연속끝(글자들, 위치 + 1, 공백인가)
  if 글자인가(글자들, 첫자리, "]") { return Ok(읽은값 { 값: Json값.배열([]), 다음: 첫자리 + 1 }) }
  let mut 요소들: Array<Json값> = []
  let mut i = 첫자리
  while true {
    let 읽음 = 값읽기(글자들, i, 깊이 + 1)?
    요소들.push(읽음.값)
    let 뒤 = 연속끝(글자들, 읽음.다음, 공백인가)
    if 뒤 >= 글자들.length { return Err(구문오류 { 위치: 뒤, 종류: 오류종류.닫는대괄호필요 }) }
    if 글자들[뒤] == "]" { return Ok(읽은값 { 값: Json값.배열(요소들), 다음: 뒤 + 1 }) }
    if 글자들[뒤] != "," { return Err(구문오류 { 위치: 뒤, 종류: 오류종류.쉼표나닫는대괄호 }) }
    i = 뒤 + 1
  }
}

function 문서읽기(글자들: Array<string>) -> Result<Json값, 구문오류> {
  let 읽음 = 값읽기(글자들, 0, 0)?
  let 끝 = 연속끝(글자들, 읽음.다음, 공백인가)
  if 끝 < 글자들.length { Err(구문오류 { 위치: 끝, 종류: 오류종류.잉여내용 }) } else { Ok(읽음.값) }
}

function 들여쓰기(깊이: int) -> string {
  [for i in 0..<깊이 => "  "].join("")
}

function 블록(여는: string, 닫는: string, 항목들: Array<string>, 깊이: int) -> string {
  if 항목들.length == 0 {
    여는 + 닫는
  } else {
    let 안쪽 = 들여쓰기(깊이 + 1)
    "{여는}\n{항목들.map(항목 => 안쪽 + 항목).join(",\n")}\n{들여쓰기(깊이)}{닫는}"
  }
}

function 직렬화(값: Json값, 깊이: int) -> string {
  match 값 {
    case 없음 => "null"
    case 참거짓(참인가) => if 참인가 { "true" } else { "false" }
    case 수(원문) | 문자열(원문) => 원문
    case 배열(요소들) => 블록("[", "]", 요소들.map(요소 => 직렬화(요소, 깊이 + 1)), 깊이)
    case 객체(멤버들) => 블록("\{", "\}", 멤버들.map(쌍 => 쌍.키 + ": " + 직렬화(쌍.값, 깊이 + 1)), 깊이)
  }
}

// 줄과 열은 1부터 셉니다. 위치가 입력 끝을 넘으면 끝에 둡니다.
function 줄열(글자들: Array<string>, 위치: int) -> string {
  let 끝 = 위치.atMost(글자들.length)
  let 줄바꿈들 = [for i in 0..<끝 if 글자들[i] == "\n" => i]
  let 줄시작 = if 줄바꿈들.length > 0 { 줄바꿈들[줄바꿈들.length - 1] + 1 } else { 0 }
  "{줄바꿈들.length + 1}:{끝 - 줄시작 + 1}"
}

function 포맷(원본: string) -> string {
  let 글자들 = 원본.scalars()
  match 문서읽기(글자들) {
    case Ok(문서) => "<pre class=\"ok\">{본문이스케이프(직렬화(문서, 0))}</pre>"
    case Err(오류) => "<div class=\"err\">오류 {줄열(글자들, 오류.위치)}: {본문이스케이프(오류문구(오류.종류))}</div>"
  }
}

input() |> 포맷 |> print
