← 제니앱

유니코드 변환기

코드포인트(10진수)를 스페이스와 줄바꿈으로 구분해 문자로 바꾸고, 잘못된 값은 따로 표시합니다. 예 44032 110가n.

convert.tpz
// Unicode converter. Turns space-separated decimal code points into text. Written in Topaz.
// Key identifiers: 줄정리=escapeLine, 파싱=parseToken, 변환=convert, 줄=line, 텍스트=text,
//   토=token, 값=value, 문자=char, 파싱결과=parseOutcome (유효=valid / 무효=invalid),
//   누적=accumulator (결과문자=resultChars / 무효=invalidText / 무효개수=invalidCount),
//   누=acc, 판정=verdict, 결과=result.
// 유니코드 변환기. 공백으로 구분된 10진수 코드포인트를 텍스트로 바꿉니다. Topaz로 작성했습니다.
// 예 "44032 110 111" -> "가no". 각 토큰을 파싱하고(toInt) 문자로 만듭니다
// E.g. "44032 110 111" -> "가no". Parses each token (toInt) and builds a char
// (fromCodePoint). 숫자가 아니거나 유효한 유니코드 스칼라가 아닌 토큰은
// (fromCodePoint). Tokens that aren't numbers or aren't valid Unicode scalars are
// 무효로 표시됩니다(조용히 버리지 않습니다). 이 기능은 `fromCodePoint`가 반드시 필요합니다. 임의의 문자를
// flagged invalid (not silently dropped). This needs `fromCodePoint`: making arbitrary chars
// 코드로 만드는 것은 룩업 테이블로는 불가능합니다. 순수하고 결정적입니다.
// from codes is impossible with a lookup table. Pure and deterministic.

// 파싱 성공 여부를 담는 합 타입. 빈 문자열 센티널 대신 명시적 변형으로 표현합니다.
// Sum type for a token's parse outcome, replacing the empty-string sentinel.
enum 파싱결과 { 유효(string), 무효(string) }

// 누적 상태: 만들어진 문자열, 무효 토큰 나열, 무효 개수.
// Accumulator: built chars, listed invalid tokens, invalid count.
record 누적 { 결과문자: string, 무효: string, 무효개수: int }

function 줄정리(줄: string) -> string {
  // & 를 가장 먼저 바꿔야 </> 가 도입한 & 가 다시 이스케이프되지 않습니다.
  // Replace & first so the & introduced by </> is not re-escaped.
  줄.replace("&", "&amp;").replace("<", "&lt;").replace(">", "&gt;")
}

function 파싱(토: string) -> 파싱결과 {
  // toInt과 fromCodePoint을 flatMap으로 잇습니다. 둘 중 하나라도 None이면 무효. 센티널이 없습니다.
  // Chain toInt and fromCodePoint with flatMap. If either step is None, the token is invalid. No sentinels.
  match toInt(토).flatMap(fromCodePoint) {
    case Some(문자) => 파싱결과.유효(문자)
    case None => 파싱결과.무효(토)
  }
}

function 변환(텍스트: string) -> string {
  // 줄바꿈을 공백으로 편 뒤 토큰화하고, 각 토큰을 파싱해 누적으로 접습니다.
  // Flatten newlines to spaces, tokenize, parse each token, fold into the accumulator.
  let 상태 = 텍스트.replace("\n", " ").split(" ")
    .filter(토 => 토.byteLength() > 0)
    .map(파싱)
    .reduce(누적 { 결과문자: "", 무효: "", 무효개수: 0 }, (누, 판정) => match 판정 {
      case 유효(c) => 누적 { ...누, 결과문자: 누.결과문자 + c }
      case 무효(t) => 누적 { ...누, 무효: 누.무효 + t + " ", 무효개수: 누.무효개수 + 1 }
    })

  // 무효부는 무효 토큰이 있을 때만 붙는 블록입니다. 순수 if/else 식으로 계산해 마지막에 한 번만 이어붙입니다.
  // The invalid block is appended only when there are invalid tokens. Compute it with a pure
  // if/else expression, then interpolate everything in a single final string.
  let 무효부 = if 상태.무효개수 > 0 {
    "<div class=\"bad\">무효 {상태.무효개수}개: {줄정리(상태.무효)}</div>"
  } else {
    ""
  }
  "<div class=\"uni\"><div class=\"out\">{줄정리(상태.결과문자)}</div>{무효부}</div>"
}

input() |> 변환 |> print
가녕하 세계