Module talk:Auphen: Difference between revisions
Log the % fix (D15) |
Log the ~ word boundary (D16) |
||
| Line 16: | Line 16: | ||
* '''D14''' — category→category mapping pairs members by '''declaration order''', not a longest-first sort, so digraph categories map correctly (e.g. palatalization <code>[H]/[J]</code> where <code>[J]</code> holds <code>tʃ, dʒ</code>: declared order gives s→ʃ, t→tʃ, g→ɣ). The JS sorted members longest-first, silently mapping s→tʃ. Longest-first is still used for ''matching'', so multi-character phones still match greedily. | * '''D14''' — category→category mapping pairs members by '''declaration order''', not a longest-first sort, so digraph categories map correctly (e.g. palatalization <code>[H]/[J]</code> where <code>[J]</code> holds <code>tʃ, dʒ</code>: declared order gives s→ʃ, t→tʃ, g→ɣ). The JS sorted members longest-first, silently mapping s→tʃ. Longest-first is still used for ''matching'', so multi-character phones still match greedily. | ||
* '''D15''' — <code>%</code> ("same as the target") substitutes on '''every''' occurrence in a result, so <code>%%</code> doubles the target (e.g. gemination <code>[C]/%%/_4</code>). The JS replaced only the first <code>%</code>. A literal per-cent sign in output is written <code>!%</code>. | * '''D15''' — <code>%</code> ("same as the target") substitutes on '''every''' occurrence in a result, so <code>%%</code> doubles the target (e.g. gemination <code>[C]/%%/_4</code>). The JS replaced only the first <code>%</code>. A literal per-cent sign in output is written <code>!%</code>. | ||
* '''D16''' (Auphen addition) — <code>~</code> (tilde) is a '''universal word boundary'''. The engine splits input on it, so each part is processed as its own word (its own <code>#</code> edges), and the parts are joined with a space; a <code>~</code> produced late by a rule becomes a plain display space. It does not clash with nasal vowels (precomposed like <code>ã</code>, or a combining U+0303 tilde — both distinct codepoints from ASCII <code>~</code>). | |||
Divergences from CWS (documented quirks fixed, not reproduced): | Divergences from CWS (documented quirks fixed, not reproduced): | ||
Revision as of 12:53, 18 July 2026
This page records the design, provenance, and behavioural divergences of Module:Auphen — out-of-frame notes, kept off the module source.
Provenance
Auphen is a pure-Lua (Scribunto) port of a JavaScript sound-change engine, itself an implementation of ConWorkShop's PhoMo rule notation. Rule anatomy (target / change / environment / exception / else), the affix-vs-replace split, category sets, and the operator set follow PhoMo; the divergences below are deliberate. The engine is developed and tested off-wiki: a 334-case local suite covering every operation, ground-truth parity against the original JS engine, and the live YBS-PJ ruleset.
Divergences
Fixed bugs in the source JS engine (each has a proving test off-wiki):
- D1 — the ELS
@-index is parsed and used as a fallback instance filter (the JS assigned it to the CHG index, so ELS indices were dead). - D2 — ditto
"fires inside the exception as well as the environment (the JS dropped the "previous" flag). - D3 —
?-reversal indices are computed on the clean word; negatives are not off by one, and positive?nstarts at char n. - D4 — positive-index affix insertion/deletion is not off by one.
- D5 — circumfix
A#Bwraps the word (the JS concatenated A+B). - D6 — local conditions inside an affix rule are cleanly false.
- D14 — category→category mapping pairs members by declaration order, not a longest-first sort, so digraph categories map correctly (e.g. palatalization
[H]/[J]where[J]holdstʃ, dʒ: declared order gives s→ʃ, t→tʃ, g→ɣ). The JS sorted members longest-first, silently mapping s→tʃ. Longest-first is still used for matching, so multi-character phones still match greedily. - D15 —
%("same as the target") substitutes on every occurrence in a result, so%%doubles the target (e.g. gemination[C]/%%/_4). The JS replaced only the first%. A literal per-cent sign in output is written!%. - D16 (Auphen addition) —
~(tilde) is a universal word boundary. The engine splits input on it, so each part is processed as its own word (its own#edges), and the parts are joined with a space; a~produced late by a rule becomes a plain display space. It does not clash with nasal vowels (precomposed likeã, or a combining U+0303 tilde — both distinct codepoints from ASCII~).
Divergences from CWS (documented quirks fixed, not reproduced):
- D7 — a category/index resolving out of range skips the candidate (CWS deletes the target).
- D8 —
!escape makes the next character a literal. - D9 / D10 — movement
>and copy>!use clean semantics; CWS's off-by-one is not reproduced. - D11 —
-word operations are opt-in (opts.dashToSpace). - D12 — the wildcard
*in local conditions is defined and reliable. - D13 — a UTF-8 codepoint layer replaces the JS's UTF-16 indexing: combining marks are separate characters, and digraph phones work in categories (CWS bans them).
Kept behaviours: @0/@-0 select nothing; an empty ELS is skip, not delete; only the first % in a result is substituted; capitals never survive to output; candidates are collected against the pre-rule word and applied together (no intra-rule bleeding).
Performance
Category matching currently materialises the Cartesian product of the category references in a rule, so cost is (category size) to the power of (number of references) — dominated by large sets like [C]. A guard (opts.explodeCap, default 5,000,000) skips a pathological rule with a warning rather than exceeding Scribunto's CPU limit. Tracked follow-up: a streaming/positional matcher that tests membership per word-position, removing the ceiling entirely.