MoonMojibake — encoding forensics and mojibake repair for MoonBit
| Area | State |
|---|---|
| Code pages — GBK, Big5, Shift_JIS, EUC-JP, EUC-KR | ✅ 63 551 two-byte mappings, generated from CPython codecs |
| Code pages — EUC-JP three-byte form (JIS X 0212) | ✅ 6 067 mappings in their own 94×94 table |
| Code pages — UTF-8, UTF-16LE/BE, UTF-32LE/BE, Latin-1, windows-1252 | ✅ |
| Detection — ranking, confidence, BOM handling | ✅ 12 candidates per call |
| Detection — Chinese/Japanese disambiguation | ✅ JIS X 0208 level-1 test on kana texts |
| Detection — ties | ✅ reported as ties (ambiguous, tied), never as a confident guess |
| Repair — chained mojibake, up to 4 layers | ✅ returns the re-encode chain |
| Repair — script guard | ✅ never swaps Chinese for Hangul or kana |
| CLI — detect / repair / convert / scan / fix | ✅ js backend (Node), library works on every backend |
| CLI — fix over a tree | ✅ dry run by default, --write to apply, idempotent |
| CLI — --json on every subcommand | ✅ stable field names, for pipelines |
| CLI — --fail-if-not | ✅ exit 2, so CI can reject a non-UTF-8 tree |
| CLI — exit codes | ✅ 0 success, 1 usage/IO error, 2 assertion failed |
| Unit tests | ✅ 67 passing (moon test) |
| Cross-check against CPython | ✅ 257/257 (scripts/e2e_check.py) |
| Exhaustive byte-space check | ✅ 127 287/127 287, both directions (scripts/space_check.py) |
| Dependencies | ✅ MoonBit core only, zero FFI |
| License | ✅ Apache-2.0 |
moon build --target js
alias mm="node _build/js/debug/build/cmd/moonmojibake/moonmojibake.js"
mm detect samples/gbk.txtsamples/gbk.txt (36 bytes)
# encoding score conf errors sample
1 gbk 86 51 0 我们把这件事说清楚,然后开始写代码。
2 euc-kr 69 0 0 乖쳬겉侶숭慤綱헌뇝,횔빈역迦畇덜쯤。
3 euc-jp 66 0 2 厘断委宸周並傍賠萱��隼朔蝕兵亟旗鷹。
4 big5 54 0 2 扂蠅參涴璃岈佽ь奠ㄛ�遣騜羌標斐�鎢﹝
...
10 utf-8 3 0 19 ���ǰ������˵�����Ȼ��ʼд���
12 utf-32be 0 0 9 ���������
guess: gbk (confidence 51%, score 86)mm repair samples/moji_latin1.txtsamples/moji_latin1.txt
score 40 -> 86
chain: latin1 -> utf-8
我们把这件事说清楚,然后开始写代码。mm convert samples/utf8.txt --to gbk -o out.bin
mm detect --text "中文测试"mm scan samples -rscan samples (8 files)
encoding files
big5 1
euc-jp 1
euc-kr 1
gbk 1
utf-8 3
shift_jis 1
samples/big5.txt big5
samples/eucjp.txt euc-jp
samples/euckr.txt euc-kr
samples/gbk.txt gbk
samples/moji_cp1252.txt utf-8
samples/moji_latin1.txt utf-8
samples/sjis.txt shift_jis
samples/utf8.txt utf-8mm fix samples -r # dry run: what would change
mm fix samples -r --write # actually rewrite them as UTF-8fix samples (recursive) (8 files, dry run)
path read as change
samples/big5.txt big5 transcode
samples/eucjp.txt euc-jp transcode
samples/euckr.txt euc-kr transcode
samples/gbk.txt gbk transcode
samples/moji_cp1252.txt utf-8 repair: windows-1252 -> utf-8 45 -> 90
samples/moji_latin1.txt utf-8 repair: latin1 -> utf-8 40 -> 86
samples/sjis.txt shift_jis transcode
7 of 8 text files would change; nothing written. Re-run with --write to apply.mm detect samples/gbk.txt --json | jq -r '.guess, .confidence'
mm repair samples/moji_latin1.txt --json | jq -r '.chain[0].wrong + " -> " + .chain[0].right'
mm scan . -r --json | jq -r '.entries[] | select(.binary or .charset != "utf-8") | .path'mm scan . -r --fail-if-not utf-8error: docs/legacy.txt reads as gbk, expected utf-8// Detect: which code page produced these bytes?
let rows = @mojibake.detect(bytes)
let best = rows[0]
println(best.charset().name()) // "gbk"
println(best.confidence()) // 0..100
println(best.text()) // the decoded text
// Repair: undo the accident layer by layer.
let fixed = @mojibake.repair(mojibake_text)
println(fixed.text()) // the recovered text
println(@mojibake.render_chain(fixed)) // "windows-1252 -> utf-8"
// Direct control when you already know both ends.
let decoded = @mojibake.decode(bytes, @mojibake.Charset::Big5)
let encoded = @mojibake.encode("繁體中文", @mojibake.Charset::Big5)| Signal | Weight | What it catches |
|---|---|---|
| Clean decode | 30 | A wrong code page leaves malformed sequences behind |
| Script coherence | 15 | Garbage mixes scripts; real text does not |
| Character frequency | 35 | Wrong decodes produce rare characters, never common ones |
| Printability | 10 | Control characters and private-use code points |
| Script exclusivity | 10 | Hangul is Korean, full-width kana is Japanese, and neither is Chinese |
| Code page | Two-byte mappings | Notes |
|---|---|---|
| GBK | 21 791 | includes GB2312 and the GBK extension area |
| Big5 | 13 710 | includes the HKSCS lead-byte range |
| Shift_JIS | 6 879 | half-width katakana computed, not tabulated |
| EUC-JP | 6 879 (+6 067 three-byte) | JIS X 0208 in the pair table, JIS X 0212 behind 0x8F |
| EUC-KR | 8 225 | KS X 1001 |
| UTF-8 / UTF-16LE / UTF-16BE / UTF-32LE / UTF-32BE | — | self-validating; BOM handled exactly |
| ISO-8859-1 / windows-1252 | — | the 0x80–0x9F difference is modelled exactly |
moon test # 67 unit tests
moon build --target js
python scripts/e2e_check.py # 257 cross-checks against CPython
python scripts/space_check.py # 127 287 exhaustive checks, both directionsInstall
Download zipMoonMojibake — encoding forensics and mojibake repair for MoonBit