prpm-dbp 0.18.1 → 0.19.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/SKILL.md CHANGED
@@ -30,6 +30,21 @@ Semuanya kata lazim dalam bahan Pendidikan Islam. Setiap satu ada jawapan tegas
30
30
 
31
31
  Alat untuk menangkap kesemuanya ada di tangan agen itu sepanjang masa. Ia tidak pernah dijalankan ke atas teks Rumi keluarannya sendiri. Kedua-dua pusingan ditemui kerana **pengguna bertanya**, bukan kerana proses menangkapnya.
32
32
 
33
+ `gate` menyemak **kedua-dua tulisan dalam panggilan yang sama**. Versi terdahulu menyemak Rumi sahaja, jadi dokumen Jawi penuh melepasinya tanpa satu aduan pun — tepat jenis dokumen yang alat ini paling diperlukan.
34
+
35
+ Empat perkara yang ditangkap dalam teks Jawi:
36
+
37
+ | Keadaan | Contoh | Keputusan |
38
+ |---|---|---|
39
+ | ruang tersasar di dalam perkataan | `ݢول وڠن` | **ralat**, cadang `ݢولوڠن` |
40
+ | ejaan tiada dalam DBP | `منجاوهكن` | **ralat**, cadang `منجاءوهکن` |
41
+ | homograf tulen | `سنة` | tidak dapat disahkan, dua bacaan |
42
+ | petikan Arab | `الشِّمال` | tidak dapat disahkan, §11.1 |
43
+
44
+ Ruang tersasar itu kes paling berbahaya. Separuh perkataan boleh **berjaya secara palsu**: `ݢول` benar-benar memetakan kepada dua perkataan DBP tulen, `gol` dan `gul`. Penukar yang memilih calon paling kerap akan mengeluarkan "gol" dengan yakin, dan separuh perkataan hilang tanpa sesiapa perasan. Sebab itu percantuman dicuba apabila **salah satu** jiran belum selesai, bukan kedua-duanya.
45
+
46
+ Nama khas Arab tanpa kata sandang `ال` (`أصحاب`, nama tokoh, nama tempat) tidak dapat disahkan kamus Melayu. Isytiharkannya melalui `npx prpm-dbp nama <rumi> <jawi> --jenis arab`; selepas itu ia berhenti menjadi ralat, tetapi tetap **bukan** lulus DBP.
47
+
33
48
  `gate` keluar dengan kod 1 apabila ada ralat, jadi ia boleh dipasang sebagai hook atau langkah CI. Peraturan yang bergantung pada agen ingat sendiri akan gagal, dan gagal secara senyap.
34
49
 
35
50
  ## Kuatkuasa automatik
@@ -237,83 +252,6 @@ Ejaan yang disimpan begini ditandakan datang daripada pengguna, bukan daripada D
237
252
 
238
253
  Bagi nama berasal Arab, ejaan Jawinya memang ejaan Arab asal, jadi ia nampak seperti dikekalkan. Bagi nama bukan Arab ia ditransliterasi penuh: `Kuala Lumpur` menjadi `کوالا لومڤور`.
239
254
 
240
- ## Sebelum menghantar apa-apa
241
-
242
- Jalankan `npx prpm-dbp gate <fail> --luar-talian` ke atas **keseluruhan teks**. Bukan sebahagian. Bukan perkataan yang anda rasa berisiko.
243
-
244
- Ejaan yang salah ialah tepat ejaan yang anda **tidak** syak. Kalau anda syak, anda sudah menyemaknya.
245
-
246
- Ini termasuk ejaan yang anda tulis dalam **kod, ujian dan dokumentasi**, bukan hanya dalam teks untuk pengguna. Semasa membina pakej ini sendiri, satu nilai Jawi ditulis ke dalam ujian daripada ingatan tanpa disemak. Nilainya kebetulan betul. Kebetulan bukan proses.
247
-
248
- **Apa yang berlaku apabila langkah ini dilangkau:**
249
-
250
- Satu agen menggunakan skill ini sepanjang sesi penuh untuk menukar Jawi ke Rumi, kemudian menghantar 549 kad RPH Pendidikan Islam ke pangkalan data produksi dengan **273 ejaan yang salah**.
251
-
252
- ```
253
- pusingan 1 sunnah x206, hadith x16, fardhu x2 -> masuk produksi
254
- pusingan 2 tayammum, dhuha, qadha, redha, iddah x90 -> masuk produksi
255
- ```
256
-
257
- Semuanya kata lazim dalam bahan Pendidikan Islam. Setiap satu ada jawapan tegas dalam PRPM: `sunnah` ialah rujukan silang ke `sunah`, `hadith` sepatutnya `hadis`, `fardhu` sepatutnya `fardu`, `tayammum` sepatutnya `tayamum`, `dhuha` sepatutnya `duha`, `qadha` sepatutnya `qada`.
258
-
259
- Alat untuk menangkap kesemuanya ada di tangan agen itu sepanjang masa. Ia tidak pernah dijalankan ke atas teks Rumi keluarannya sendiri. Kedua-dua pusingan ditemui kerana **pengguna bertanya**, bukan kerana proses menangkapnya.
260
-
261
- `gate` keluar dengan kod 1 apabila ada ralat, jadi ia boleh dipasang sebagai hook atau langkah CI. Peraturan yang bergantung pada agen ingat sendiri akan gagal, dan gagal secara senyap.
262
-
263
- ## Kuatkuasa automatik
264
-
265
- ```bash
266
- npx prpm-dbp install --hook
267
- ```
268
-
269
- Memasang hook PostToolUse yang menjalankan `gate --luar-talian` ke atas setiap fail `.md`, `.txt` atau `.json` yang ditulis atau disunting. Ia **melaporkan** ralat kepada agen tanpa menyekat tulisan: menyekat atas kamus yang tidak lengkap akan menghalang kerja yang sah, manakala melaporkan sudah memadai kerana agen membaca output hook.
270
-
271
- Untuk menampal sendiri: `npx prpm-dbp hook-json`.
272
-
273
- Panaskan cache **sekali** semasa pemasangan, bukan semasa gate berjalan:
274
-
275
- ```bash
276
- npx prpm-dbp warm kosa-kata-projek.txt
277
- ```
278
-
279
- Gate atas cache panas mengambil 0.19 saat bagi 406 perkataan. Itu cukup pantas untuk setiap penyuntingan. Gate yang perlahan akan dimatikan orang.
280
-
281
- ## Tiga keadaan gate, bukan dua
282
-
283
- | Medan | Maksud | Kesan |
284
- |---|---|---|
285
- | `lulusDbp` | ada dalam PRPM, ejaan padan | — |
286
- | `ralat` | ada dalam PRPM dengan ejaan lain, entri rujukan silang `®`, atau tiada dalam PRPM mahupun dataset | **exit 1** |
287
- | `tidakDapatDisahkan` | tiada dalam cache, nama khas, akronim, atau tiada entri PRPM tetapi wujud dalam dataset | dilapor; exit 1 jika melebihi `--ambang` |
288
-
289
- `tidakDapatDisahkan` **tidak pernah lulus senyap**. Gate yang lulus atas cache sejuk memberi jaminan palsu, dan itu lebih bahaya daripada tiada gate. Gunakan `--ambang 10` supaya cache sejuk menggagalkan gate.
290
-
291
- PRPM tiada entri untuk kata tugas paling asas: `dan`, `di`, `ke`, `yang`, `atau`. Menandakannya sebagai ralat menjadikan gate mustahil dipakai. Pembezanya bersumber, bukan senarai putih: kata tugas itu **semua** wujud dalam dataset, manakala `hadith`, `fardhu`, `tayammum`, `dhuha`, `qadha`, `redha` dan `iddah` **tiada satu pun**.
292
-
293
- ## Prosedur wajib
294
-
295
- Ikut ini setiap kali, jangan langkau langkah.
296
-
297
- **Menulis Jawi daripada Rumi:**
298
-
299
- 1. `npx prpm-dbp jawi <fail> --luar-talian --homograf --json`
300
- 2. Baca `belumDisahkan` dan `namaKhas`. Jangan reka ejaan untuknya. Sahkan dengan `lookup`, atau tanya pengguna, atau tandakan dalam hasil akhir.
301
- 3. Baca `mencurigakan`. Setiap satu bermakna PRPM sendiri mungkin tersilap. Semak di pautan yang diberi sebelum menggunakannya.
302
- 4. Baca `homograf`. Beritahu pengguna perkataan mana yang akan taksa apabila dibaca.
303
- 5. Laporkan `semuanyaDbp`. Jika `false`, nyatakan bahagian mana bukan daripada sumber rasmi.
304
-
305
- **Merumikan daripada Jawi:**
306
-
307
- 1. `npx prpm-dbp rumi <fail> --pilih --konteks --json`
308
- 2. Untuk **setiap** item dalam `kabur`: baca ayat yang mengandunginya, baca makna setiap calon, tentukan yang mana sesuai.
309
- 3. Jika `dipilih` salah bagi ayat itu, **gantikan** dalam hasil akhir. Kekerapan kalah kepada konteks.
310
- 4. Nyatakan kepada pengguna berapa banyak kekaburan wujud dan mana yang anda ubah.
311
- 5. `tidakDikenali` bermakna perkataan itu tiada dalam cache. Jalankan `warm` di luar talian, bukan tinggalkan bertanda.
312
-
313
- **Berhenti dan tanya pengguna apabila:** ejaan `mencurigakan` mengubah makna, nama khas tiada rujukan bertulis, atau dua calon sama-sama munasabah dalam ayat itu.
314
-
315
- Semua perintah menyokong `--json`, jadi langkah di atas boleh dibuat secara berulang tanpa menghurai teks.
316
-
317
255
  ## Peraturan Pedoman DBP
318
256
 
319
257
  Satu fail sahaja dalam pakej ini **menjana** ejaan dan bukan mencarinya: `src/pedoman.js`. Ia dibenarkan kerana setiap peraturan di dalamnya datang daripada *Pedoman Umum Ejaan Jawi Bahasa Melayu* terbitan DBP, dan dirujuk pada seksyennya.
@@ -425,83 +363,6 @@ Dua puluh dua kes kabur yang tinggal itu sah: `قلقله` boleh dibaca `qalqala
425
363
 
426
364
  **Sifar perkataan hilang atau rosak.**
427
365
 
428
- ## Sebelum menghantar apa-apa
429
-
430
- Jalankan `npx prpm-dbp gate <fail> --luar-talian` ke atas **keseluruhan teks**. Bukan sebahagian. Bukan perkataan yang anda rasa berisiko.
431
-
432
- Ejaan yang salah ialah tepat ejaan yang anda **tidak** syak. Kalau anda syak, anda sudah menyemaknya.
433
-
434
- Ini termasuk ejaan yang anda tulis dalam **kod, ujian dan dokumentasi**, bukan hanya dalam teks untuk pengguna. Semasa membina pakej ini sendiri, satu nilai Jawi ditulis ke dalam ujian daripada ingatan tanpa disemak. Nilainya kebetulan betul. Kebetulan bukan proses.
435
-
436
- **Apa yang berlaku apabila langkah ini dilangkau:**
437
-
438
- Satu agen menggunakan skill ini sepanjang sesi penuh untuk menukar Jawi ke Rumi, kemudian menghantar 549 kad RPH Pendidikan Islam ke pangkalan data produksi dengan **273 ejaan yang salah**.
439
-
440
- ```
441
- pusingan 1 sunnah x206, hadith x16, fardhu x2 -> masuk produksi
442
- pusingan 2 tayammum, dhuha, qadha, redha, iddah x90 -> masuk produksi
443
- ```
444
-
445
- Semuanya kata lazim dalam bahan Pendidikan Islam. Setiap satu ada jawapan tegas dalam PRPM: `sunnah` ialah rujukan silang ke `sunah`, `hadith` sepatutnya `hadis`, `fardhu` sepatutnya `fardu`, `tayammum` sepatutnya `tayamum`, `dhuha` sepatutnya `duha`, `qadha` sepatutnya `qada`.
446
-
447
- Alat untuk menangkap kesemuanya ada di tangan agen itu sepanjang masa. Ia tidak pernah dijalankan ke atas teks Rumi keluarannya sendiri. Kedua-dua pusingan ditemui kerana **pengguna bertanya**, bukan kerana proses menangkapnya.
448
-
449
- `gate` keluar dengan kod 1 apabila ada ralat, jadi ia boleh dipasang sebagai hook atau langkah CI. Peraturan yang bergantung pada agen ingat sendiri akan gagal, dan gagal secara senyap.
450
-
451
- ## Kuatkuasa automatik
452
-
453
- ```bash
454
- npx prpm-dbp install --hook
455
- ```
456
-
457
- Memasang hook PostToolUse yang menjalankan `gate --luar-talian` ke atas setiap fail `.md`, `.txt` atau `.json` yang ditulis atau disunting. Ia **melaporkan** ralat kepada agen tanpa menyekat tulisan: menyekat atas kamus yang tidak lengkap akan menghalang kerja yang sah, manakala melaporkan sudah memadai kerana agen membaca output hook.
458
-
459
- Untuk menampal sendiri: `npx prpm-dbp hook-json`.
460
-
461
- Panaskan cache **sekali** semasa pemasangan, bukan semasa gate berjalan:
462
-
463
- ```bash
464
- npx prpm-dbp warm kosa-kata-projek.txt
465
- ```
466
-
467
- Gate atas cache panas mengambil 0.19 saat bagi 406 perkataan. Itu cukup pantas untuk setiap penyuntingan. Gate yang perlahan akan dimatikan orang.
468
-
469
- ## Tiga keadaan gate, bukan dua
470
-
471
- | Medan | Maksud | Kesan |
472
- |---|---|---|
473
- | `lulusDbp` | ada dalam PRPM, ejaan padan | — |
474
- | `ralat` | ada dalam PRPM dengan ejaan lain, entri rujukan silang `®`, atau tiada dalam PRPM mahupun dataset | **exit 1** |
475
- | `tidakDapatDisahkan` | tiada dalam cache, nama khas, akronim, atau tiada entri PRPM tetapi wujud dalam dataset | dilapor; exit 1 jika melebihi `--ambang` |
476
-
477
- `tidakDapatDisahkan` **tidak pernah lulus senyap**. Gate yang lulus atas cache sejuk memberi jaminan palsu, dan itu lebih bahaya daripada tiada gate. Gunakan `--ambang 10` supaya cache sejuk menggagalkan gate.
478
-
479
- PRPM tiada entri untuk kata tugas paling asas: `dan`, `di`, `ke`, `yang`, `atau`. Menandakannya sebagai ralat menjadikan gate mustahil dipakai. Pembezanya bersumber, bukan senarai putih: kata tugas itu **semua** wujud dalam dataset, manakala `hadith`, `fardhu`, `tayammum`, `dhuha`, `qadha`, `redha` dan `iddah` **tiada satu pun**.
480
-
481
- ## Prosedur wajib
482
-
483
- Ikut ini setiap kali, jangan langkau langkah.
484
-
485
- **Menulis Jawi daripada Rumi:**
486
-
487
- 1. `npx prpm-dbp jawi <fail> --luar-talian --homograf --json`
488
- 2. Baca `belumDisahkan` dan `namaKhas`. Jangan reka ejaan untuknya. Sahkan dengan `lookup`, atau tanya pengguna, atau tandakan dalam hasil akhir.
489
- 3. Baca `mencurigakan`. Setiap satu bermakna PRPM sendiri mungkin tersilap. Semak di pautan yang diberi sebelum menggunakannya.
490
- 4. Baca `homograf`. Beritahu pengguna perkataan mana yang akan taksa apabila dibaca.
491
- 5. Laporkan `semuanyaDbp`. Jika `false`, nyatakan bahagian mana bukan daripada sumber rasmi.
492
-
493
- **Merumikan daripada Jawi:**
494
-
495
- 1. `npx prpm-dbp rumi <fail> --pilih --konteks --json`
496
- 2. Untuk **setiap** item dalam `kabur`: baca ayat yang mengandunginya, baca makna setiap calon, tentukan yang mana sesuai.
497
- 3. Jika `dipilih` salah bagi ayat itu, **gantikan** dalam hasil akhir. Kekerapan kalah kepada konteks.
498
- 4. Nyatakan kepada pengguna berapa banyak kekaburan wujud dan mana yang anda ubah.
499
- 5. `tidakDikenali` bermakna perkataan itu tiada dalam cache. Jalankan `warm` di luar talian, bukan tinggalkan bertanda.
500
-
501
- **Berhenti dan tanya pengguna apabila:** ejaan `mencurigakan` mengubah makna, nama khas tiada rujukan bertulis, atau dua calon sama-sama munasabah dalam ayat itu.
502
-
503
- Semua perintah menyokong `--json`, jadi langkah di atas boleh dibuat secara berulang tanpa menghurai teks.
504
-
505
366
  ## Peraturan yang sengaja TIDAK dilaksanakan
506
367
 
507
368
  | Seksyen | Sebab |
package/bin/cli.js CHANGED
@@ -318,6 +318,18 @@ async function main() {
318
318
  for (const fail of baki) {
319
319
  const laporan = await gate(await readFile(fail, 'utf8'), cache, {
320
320
  ...opts,
321
+ // Dataset MESTI dimuatkan. Tanpanya peta songsang menyusut daripada
322
+ // 64k ejaan kepada saiz cache sahaja, dan kata tugas paling asas
323
+ // seperti دان dilaporkan sebagai ejaan Jawi yang salah.
324
+ dataset: await muatDataset(),
325
+ // Nama khas Arab (nama surah, tokoh, tempat) tidak boleh disahkan
326
+ // kamus Melayu. Yang sudah diisytihar melalui `nama` diambil kira
327
+ // supaya pengisytiharan itu benar-benar bermakna.
328
+ namaJawi: new Set(
329
+ Object.values(await muatNama())
330
+ .map((n) => n?.jawi)
331
+ .filter(Boolean),
332
+ ),
321
333
  luarTalian: pilihan.luarTalian !== false,
322
334
  abai,
323
335
  })
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "prpm-dbp",
3
- "version": "0.18.1",
3
+ "version": "0.19.0",
4
4
  "description": "Semak ejaan Bahasa Melayu dan Jawi terhadap PRPM Dewan Bahasa dan Pustaka. CLI dan skill untuk AI agent.",
5
5
  "keywords": [
6
6
  "bahasa-melayu",
package/src/gate.js CHANGED
@@ -14,13 +14,189 @@
14
14
  * TIGA keadaan, bukan dua. `tidakDapatDisahkan` TIDAK PERNAH lulus senyap:
15
15
  * gate yang lulus atas cache sejuk memberi jaminan palsu, dan itu lebih
16
16
  * bahaya daripada tiada gate.
17
+ *
18
+ * Versi pertama menyemak Rumi SAHAJA. Regexnya `[A-Za-zÀ-ÿ]` bermakna
19
+ * dokumen Jawi penuh melepasi hook tanpa satu aduan pun — tepat jenis
20
+ * dokumen yang skill ini paling diperlukan. Sebaris teks pelajaran dengan
21
+ * `ݢول وڠن` (golongan dengan ruang tersasar di tengahnya) dan `منجاوهكن`
22
+ * (menjauhkan tanpa hamzah) lulus sepenuhnya. Gate kini menyemak
23
+ * kedua-dua tulisan.
17
24
  */
18
25
 
19
26
  import { lookup } from './prpm.js'
20
27
  import { mungkinAkronim, mungkinNamaKhas } from './nama.js'
21
28
  import { cariDataset } from './dataset.js'
29
+ import { normalisasiJawi } from './kamus.js'
30
+ import { jarakHuruf, petaSongsang } from './tukar.js'
31
+ import { pisahUlangJawi } from './pedoman.js'
32
+ import { rangkaKonsonan } from './semak.js'
22
33
 
23
34
  const TOKEN_RUMI = /([A-Za-zÀ-ÿ][A-Za-zÀ-ÿ'-]*)/g
35
+ const TOKEN_JAWI = /([؀-ۿݐ-ݿﭐ-﷿ﹰ-]+)/g
36
+
37
+ // Baris tanda vokal Arab. Jawi Melayu tidak menggunakannya; petikan al-Quran
38
+ // dan hadis dalam dokumen Melayu menggunakannya. Kehadirannya bermakna
39
+ // potongan itu bahasa Arab, yang Pedoman §11.1 kata kekalkan seadanya.
40
+ const HARAKAT = /[ً-ْٰٓ-ٕ]/
41
+
42
+ /**
43
+ * Semak token Jawi terhadap peta songsang DBP.
44
+ *
45
+ * Yang paling penting di sini ialah pengesanan RUANG TERSASAR. Sebuah
46
+ * perkataan yang terbelah dua boleh gagal dengan LANTANG pada satu separuh
47
+ * dan BERJAYA SECARA PALSU pada separuh yang lain: `ݢولوڠن` (golongan)
48
+ * yang terbelah menjadi `ݢول` + `وڠن` meninggalkan `ݢول`, yang benar-benar
49
+ * memetakan kepada dua perkataan DBP tulen, gol dan gul. Penukar yang
50
+ * memilih calon paling kerap akan mengeluarkan "gol" dengan penuh yakin,
51
+ * dan separuh perkataan hilang tanpa sesiapa perasan.
52
+ *
53
+ * Sebab itu percantuman dicuba apabila SALAH SATU jiran tidak dapat
54
+ * diselesaikan. Menuntut kedua-duanya gagal akan terlepas kes ini justeru
55
+ * kerana separuh pertama "berjaya".
56
+ */
57
+ export function gateJawi(teks, cache, opts = {}) {
58
+ const { dataset = null, abai = new Set(), namaJawi = new Set() } = opts
59
+ const peta = petaSongsang(cache, dataset)
60
+
61
+ // Indeks rangka konsonan, dibina sekali. Ejaan yang tersalah biasanya
62
+ // berbeza hanya pada vokal atau hamzah — منجاوهكن lawan منجاءوهکن — jadi
63
+ // rangkanya kekal sama dan ejaan DBP yang betul boleh dinamakan terus.
64
+ // Melaporkan "tiada dalam DBP" sahaja menyerahkan kerja mencari kepada
65
+ // pembaca; melaporkan ejaan sebenar menutup jurang itu.
66
+ const rangka = new Map()
67
+ for (const [kunci, slot] of peta) {
68
+ if (!slot.layak.length) continue
69
+ const r = rangkaKonsonan(kunci)
70
+ if (!r) continue
71
+ if (!rangka.has(r)) rangka.set(r, [])
72
+ rangka.get(r).push({ jawi: kunci, rumi: slot.layak[0] })
73
+ }
74
+ const cadang = (t) => {
75
+ const senarai = rangka.get(rangkaKonsonan(t)) ?? []
76
+ if (!senarai.length) return null
77
+ const terbaik = senarai
78
+ .map((c) => ({ ...c, jarak: jarakHuruf(normalisasiJawi(t), c.jawi) }))
79
+ .sort((a, b) => a.jarak - b.jarak)[0]
80
+ // Jarak besar bermakna ia perkataan lain sepenuhnya yang kebetulan
81
+ // berkongsi rangka, bukan salah eja. Cadangan begitu mengelirukan.
82
+ return terbaik.jarak <= 3 ? terbaik : null
83
+ }
84
+
85
+ const lulusDbp = []
86
+ const ralat = []
87
+ const tidakDapatDisahkan = []
88
+ const dilihat = new Set()
89
+
90
+ const bahagian = teks.split(TOKEN_JAWI)
91
+ const token = []
92
+ for (const [i, cebis] of bahagian.entries()) {
93
+ if (i % 2 === 1) token.push({ teks: cebis, i })
94
+ }
95
+
96
+ // Pemisah antara dua token mesti ruang tulen sebelum percantuman dicuba.
97
+ // Tanda baca, petikan atau baris baru antara mereka bermakna ia memang
98
+ // dua perkataan berasingan, bukan satu yang terbelah.
99
+ const ruangSahaja = (a, b) => /^[ \t ‌​]*$/.test(bahagian.slice(a + 1, b).join(''))
100
+
101
+ // §17.1: kata ulang penuh ialah kata dasar + angka dua Arab. `اوسها٢`
102
+ // ialah usaha-usaha, dan kata dasarnya yang ada dalam kamus, bukan bentuk
103
+ // berangka itu. Tanpa langkah ini setiap kata ulang dalam dokumen jadi
104
+ // ralat palsu.
105
+ const cari = (t) => {
106
+ const langsung = peta.get(normalisasiJawi(t))
107
+ if (langsung) return langsung
108
+ const dasar = pisahUlangJawi(t)
109
+ return dasar ? (peta.get(normalisasiJawi(dasar)) ?? null) : null
110
+ }
111
+ const selesai = (t) => {
112
+ const m = cari(t)
113
+ return m ? m.layak.length === 1 : false
114
+ }
115
+
116
+ for (const [n, tok] of token.entries()) {
117
+ const t = tok.teks
118
+ if (abai.has(t) || abai.has(normalisasiJawi(t))) continue
119
+
120
+ // Petikan Arab tidak selalu bertanda baris. Kata sandang `ال` ialah
121
+ // penanda paling boleh dipercayai yang tinggal: ia membuka hampir setiap
122
+ // frasa Arab yang dipetik (اليمين, المقربون, الشمال) dan pada dasarnya
123
+ // tidak pernah membuka kata Melayu Jawi. Nama khas Arab tanpa kata
124
+ // sandang (أصحاب) tetap perlu diisytihar melalui simpanan `nama`;
125
+ // kamus Melayu memang tidak boleh mengesahkannya, dan berpura-pura
126
+ // boleh adalah jaminan palsu.
127
+ if (HARAKAT.test(t) || (/^ال/.test(t) && !cari(t))) {
128
+ // Pedoman §11.1: petikan Arab kekalkan ejaan asal. Ia bukan lulus DBP
129
+ // kerana kamus Melayu tidak boleh mengesahkannya; ia keadaan ketiga.
130
+ if (!dilihat.has(t)) { dilihat.add(t); tidakDapatDisahkan.push({ kata: t, sebab: 'teks Arab, dikekalkan (Pedoman §11.1)' }) }
131
+ continue
132
+ }
133
+
134
+ const m = cari(t)
135
+
136
+ // Cuba cantum dengan jiran SEBELUM melaporkan apa-apa, kerana ruang
137
+ // tersasar mencemarkan kedua-dua separuh sekali gus.
138
+ const seterusnya = token[n + 1]
139
+ if (seterusnya && ruangSahaja(tok.i, seterusnya.i)) {
140
+ const belumSelesai = !selesai(t) || !selesai(seterusnya.teks)
141
+ if (belumSelesai) {
142
+ const gabung = cari(t + seterusnya.teks)
143
+ if (gabung?.layak.length) {
144
+ const kunci = t + ' ' + seterusnya.teks
145
+ if (!dilihat.has(kunci)) {
146
+ dilihat.add(kunci)
147
+ ralat.push({
148
+ kata: kunci,
149
+ sebab: 'ruang tersasar di dalam perkataan',
150
+ sepatutnya: t + seterusnya.teks,
151
+ bacaan: gabung.layak[0],
152
+ })
153
+ }
154
+ dilihat.add(t)
155
+ dilihat.add(seterusnya.teks)
156
+ continue
157
+ }
158
+ }
159
+ }
160
+
161
+ if (dilihat.has(t)) continue
162
+ dilihat.add(t)
163
+
164
+ if (!m) {
165
+ // Nama khas yang SUDAH diisytihar pengguna bukan ralat. Ia tetap bukan
166
+ // lulus DBP — kamus Melayu tidak mengesahkannya — jadi ia keadaan
167
+ // ketiga, dan pembaca nampak apa yang tidak disemak.
168
+ if (namaJawi.has(t) || namaJawi.has(normalisasiJawi(t))) {
169
+ tidakDapatDisahkan.push({ kata: t, sebab: 'nama khas diisytihar pengguna' })
170
+ continue
171
+ }
172
+ const c = cadang(t)
173
+ ralat.push({
174
+ kata: t,
175
+ sebab: 'ejaan Jawi tiada dalam DBP mahupun dataset',
176
+ ...(c ? { sepatutnya: c.jawi, bacaan: c.rumi } : {}),
177
+ })
178
+ continue
179
+ }
180
+ if (m.layak.length === 1) {
181
+ lulusDbp.push(t)
182
+ continue
183
+ }
184
+ if (m.layak.length > 1) {
185
+ // Homograf tulen. Ejaannya SAH; hanya bacaannya perlu konteks. Ini
186
+ // bukan ralat, dan menandakannya begitu akan menggagalkan setiap
187
+ // dokumen yang mengandungi سنة.
188
+ tidakDapatDisahkan.push({ kata: t, sebab: `homograf, ${m.layak.length} bacaan DBP`, calon: m.layak })
189
+ continue
190
+ }
191
+ if (m.entah.length) {
192
+ tidakDapatDisahkan.push({ kata: t, sebab: 'hanya dalam dataset, bukan DBP', calon: m.entah })
193
+ continue
194
+ }
195
+ ralat.push({ kata: t, sebab: 'hanya padan bentuk rujukan silang DBP', calon: m.tolak })
196
+ }
197
+
198
+ return { lulusDbp, ralat, tidakDapatDisahkan }
199
+ }
24
200
 
25
201
  /**
26
202
  * Semak teks Rumi. Setiap perkataan jatuh ke dalam SATU daripada tiga baldi.
@@ -114,6 +290,13 @@ export async function gate(teks, cache, opts = {}) {
114
290
  lulusDbp.push(kata)
115
291
  }
116
292
 
293
+ // Jawi disemak dalam panggilan yang SAMA. Kalau ia perintah berasingan,
294
+ // ia jadi langkah yang boleh dilupakan, dan itu punca kegagalan asal.
295
+ const j = gateJawi(teks, cache, opts)
296
+ lulusDbp.push(...j.lulusDbp)
297
+ ralat.push(...j.ralat.map((r) => ({ ...r, tulisan: 'jawi' })))
298
+ tidakDapatDisahkan.push(...j.tidakDapatDisahkan.map((r) => ({ ...r, tulisan: 'jawi' })))
299
+
117
300
  const jumlah = lulusDbp.length + ralat.length + tidakDapatDisahkan.length
118
301
  const peratusTidakSah = jumlah ? Math.round((tidakDapatDisahkan.length / jumlah) * 100) : 0
119
302
 
package/src/tukar.js CHANGED
@@ -170,7 +170,7 @@ export async function cariDasar(kata, cache, opts = {}) {
170
170
  * dan ia mengalahkan سورة (surah) yang sebenarnya betul. Susunan huruf itulah
171
171
  * maklumat yang membezakannya.
172
172
  */
173
- function jarakHuruf(a, b) {
173
+ export function jarakHuruf(a, b) {
174
174
  const m = a.length
175
175
  const n = b.length
176
176
  if (!m) return n