Architecture
How the engine is built.
About 2,100 lines of TypeScript in three layers: a letter model, a rule interpreter, and declarative rule data with citations. No runtime dependencies.
Design principles
- Derive from letters and diacritics only. The printed pronunciation hints in the Uthmani encoding (small mīm, maddah, small unvoweled letters as outcome markers) are never read by the engine. They are reserved for verification.
- Rules as data, engine as interpreter. Each rule's names, citation and
derivation templates live in a declarative table (
RULE_META); the engine implements the conditions. The scholar-facing specification (docs/SPEC.md) is kept in sync with the engine by tests. - What cannot be derived is data, and is labeled as such. The four transmitted sakt sites of Ḥafṣ are riwāyah data; the printed waqf marks are muṣḥaf data; meaning-based stop preferences are transmitted opinion. None of these are derivable from orthography, so the library carries them as explicit data rather than pretending to derive them.
- Fail loudly. Unknown codepoints, orphan marks and empty input throw.
Where the sources transmit two admissible readings, the annotation is emitted
with
confidence: "flagged"rather than silently choosing one. - The riwāyah is a parameter. Ḥafṣ by al-Shāṭibiyyah is the default parameter set, not a hard-coded assumption. The architecture separates uṣūl (rule parameters) from farsh (word-specific readings) so that further transmissions can be added as parameter sets.
Layer 1: the letter model
The tokenizer turns a verse string into words and letters, resolving the encoding questions that decide whether any later rule can be right:
- hamzah seats: a hamzah written on alif, wāw, yāʾ, a stem, or a superscript alif is one letter, the hamzah, and the seat is not a separate letter;
- the dagger alif and the small ṣila letters as madd vowels of the preceding letter;
- shaddah as gemination, including gemination created across word and verse boundaries by idghām;
- tanwīn as a nūn sākinah for rule purposes, carried on the final vowel;
- combining-mark order preserved exactly as Tanzil writes it, which is why input must not be NFC-normalized.
One defect found during verification lived in this layer (a superscript-alif hamzah read as a madd letter, R-006); the letter model, not the rules, is where such errors hide, which is why it is a separate layer with its own tests.
Layer 2: the interpreter
The engine walks the tokenized text and evaluates each rule family's conditions:
the nūn sākinah and mīm sākinah families across word boundaries, the lām rules with
the article's attachment forms, the tafkhīm rules with their contextual conditions on
the rāʾ, the full madd family with the aqwā al-maddayn precedence when two causes
compete for one madd letter, and the fawātiḥ model, in which the opening letters are
expanded to their recited names and rules apply across the names. Recitation context
(continue, stop, stop-at-word, fresh start) enters as
explicit parameters, and mode-dependent annotations carry an
appliesIn label when both modes are requested.
Layer 3: the rule data
RULE_META carries, for each of the 41 rules: canonical Arabic name,
transliteration, English gloss, derivation templates in English and Arabic, the source
citation with line numbers, and the waqf-dependence flag. The poem transcriptions in
docs/sources/ are the citation targets; the
rule catalog on this site is generated from this table, and a
test suite enforces that the specification, the table and the engine agree.
Proportions
| component | lines | role |
|---|---|---|
| engine.ts | 917 | the interpreter: all rule conditions |
| rules/meta.ts | 307 | rule table: names, citations, derivations |
| tokenizer.ts | 268 | the letter model |
| chars.ts, letters.ts | 290 | codepoint tables; letter profiles with makhraj and ṣifāt |
| remainder | ~300 | public API, verse lookup, waqf marks, riwāyah data |
| tests | 692 | 110 tests, including spec-engine sync |
The verification apparatus (diff harness, oracle mapping, drift reports) is larger than the library itself and lives in the private verification workspace; the library ships only what a consumer runs.