Skip to content

Commit 4e601c5

Browse files
committed
docs(WO13): ISC-PARG-GAPS — expressiveness record for the .parg port
No concern construct proved inexpressible. The file records the one forced deviation (keyword alternation reordered longest-first for the parg shadow lint — semantics-preserving under negative lookahead), the omitted dead concern rules (authority/source_spelling/target_spelling/ identifying/name fields are unreachable from metadata_field), the field_value repeat-binding trap the corpus caught (din-hye), and the any-matches-newlines semantics multiline descriptions depend on.
1 parent 1aa1fa2 commit 4e601c5

1 file changed

Lines changed: 91 additions & 0 deletions

File tree

‎grammar/ISC-PARG-GAPS.md‎

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
# ISC .parg port — expressiveness notes and gaps
2+
3+
Status of the port: `grammar/isc.parg` expresses **every rule of the
4+
canonical Ruby-DSL grammar** (`lib/interscript/isc/grammar/concerns/*.rb`
5+
+ `core.rb`, origin/main lineage). No construct of the concerns proved
6+
inexpressible in the parsanol .parg surface. What follows is the precise
7+
record of the four places the port could not be a verbatim transcription
8+
and why, plus the porting traps the corpus caught. Validation evidence:
9+
tools/isc-parg-check parses 319/319 corpus files (318 `maps/` + 1
10+
`maps-staging/`) and rejects 4/4 negative fixtures, with verdict parity
11+
against the Ruby DSL (`Interscript::Isc::Parser.parse_with_dsl`) on every
12+
file, positive and negative.
13+
14+
## 1. Keyword alternation: shadow-lint-forced reorder (deviation, safe)
15+
16+
- Construct: `rule(:keyword)` in `concerns/items.rb` — the ordered
17+
literal list of reserved words, used ONLY under `.absent?` (negative
18+
lookahead) in `alias_reference` and `alias_arg`.
19+
- Ruby spelling: `... | str("any") | ... | str("any_character")` —
20+
`"any"` precedes `"any_character"`.
21+
- Why the port deviates: the parsanol .parg compiler rejects
22+
prefix-shadowed alternation branches at compile time ("branch 21
23+
("any") shadows branch 34 ("any_character")"), because in a plain
24+
choice the shorter literal would make the longer unreachable.
25+
- Resolution: `"any_character"` moved before `"any"` (longest-first),
26+
as the lint instructs.
27+
- Why it is safe: `keyword` appears only under `!`. For a negative
28+
lookahead, every branch ordering is equivalent — the predicate fails
29+
whenever ANY branch matches at the position, and the two branch sets
30+
are identical. No corpus input changes verdict under the reorder.
31+
32+
## 2. Dead concern rules: omitted (not a .parg limitation)
33+
34+
- Construct: `authority_field`, `source_spelling_field`,
35+
`target_spelling_field`, `identifying_field`, `name_field` in
36+
`concerns/metadata.rb`.
37+
- Why omitted: none of them is reachable from `metadata_field` (they
38+
are defined but never listed in its alternation). In the Ruby DSL
39+
they are dead code; an `authority "X"` line parses via
40+
`generic_field` (identifier + quoted value), same as here.
41+
- Consequence: none for acceptance — a bare `authority "..."` /
42+
`name "..."` metadata line parses identically through
43+
`generic_field` on both engines (verified on the corpus, where
44+
`authority_id`, `name`, `url`, ... all flow through the generic
45+
branch).
46+
47+
## 3. Unreachable lexical helpers: not carried over
48+
49+
- `space?`, `newlines`, `newlines?` (`concerns/primitives.rb`) and
50+
`list_item` (`concerns/items.rb`, an alias of `item`) are defined but
51+
never referenced by a reachable rule. They are folded into their use
52+
sites here (`wsq`, `list_items`). `braced(inner)` is a Ruby method,
53+
not a rule; every use site spells the scaffold inline.
54+
55+
## 4. The `field_value` repeat-binding trap (expressed; first port got it wrong)
56+
57+
- Construct: `rule(:field_value)` in `concerns/metadata.rb`:
58+
`(newline.absent? >> (str("}").absent? >> str("{").absent? >> any)).repeat(1)`.
59+
- Trap: the `.repeat(1)` binds over the WHOLE parenthesized group, so
60+
`newline.absent?` is re-checked per iteration — the raw value run
61+
stops AT the newline (`din-hye`: `authority_id din` captures `raw:
62+
"din"`, not the whole metadata block).
63+
- Port: `field_raw = 1*(!newline !"}" !"{" chr)`. The first draft wrote
64+
`!newline` outside the repetition; the run then swallowed fields up
65+
to the next `{` and `din-hye-Armn-Latn-32706-2010.isc` failed. The
66+
harness caught it; the per-iteration guard matches the Ruby
67+
semantics.
68+
69+
## 5. Related semantics fixed while porting (recorded for reviewers)
70+
71+
- The DSL's `any` matches ANY character including raw newlines —
72+
multiline `description { ... }` bodies (raw_text) and quoted strings
73+
cross line breaks. `chr = %x00-10FFFF` here; newline stops exist
74+
ONLY where the concerns put an explicit `newline.absent?`
75+
(`line_comment`, `field_raw`, stages' `comment_item`).
76+
- Escape rulings from the concerns are mirrored: `\uXXXX` exactly 4
77+
hex; `\U` 4..8 hex (`gki-bel "\U040E"`); a lone `\U` accepted
78+
(un-mar converter corruption). Acceptance parity holds on all three
79+
corpus shapes.
80+
- The root rule pins full-input consumption with an explicit `!utf`
81+
(`isc_source`), so the harness rejects trailing garbage without
82+
relying on engine EOF behavior.
83+
84+
## Not a gap, but out of harness scope
85+
86+
The harness gates ACCEPTANCE (parse/reject verdicts). The capture-tree
87+
SHAPE produced by a .parg artifact is parg-shaped (`cap` nodes); the
88+
gem's `Isc::Normalizer` owns DSL-tree equivalence for the artifact
89+
runtime. Verifying shape parity byte-for-byte against
90+
`parse_with_dsl` trees is the gem's corpus gate's job, not this
91+
harness's.

0 commit comments

Comments
 (0)