Skip to content

Commit 9a44542

Browse files
committed
docs: add TODO.complete/ with all remaining ISC migration work
00-isc-runtime-integration — bridge ISC document → Interscript::Node 01-commit-isc-to-maps-repo — push 289 .isc files 02-fix-deep-equivalence-diffs — 13 remaining cosmetic differences 03-fix-isc-specs — bundler/Ruby 3.4 workaround + spec syntax fixes 04-is1-specification — compile Metanorma spec document 05-performance-cjk-maps — Parslet backtracking on 40k+ line maps 06-ruby-dsl-array-keys-bug — STANDARD_ARRAY_KEYS silently drops fields
1 parent 96dc349 commit 9a44542

7 files changed

Lines changed: 359 additions & 0 deletions
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
# 00 — ISC runtime integration: bridge ISC document → Interscript::Node
2+
3+
## Priority: P0 (blocks .isc adoption)
4+
5+
## Problem
6+
The ISC parser produces a document hash (`{metadata:, tests:, stages:, aliases:}`),
7+
but the existing `Interscript.transliterate()` only accepts:
8+
1. `.imp` files (parsed via Ruby DSL `instance_exec`)
9+
2. System codes resolved through `Interscript::Path`
10+
11+
There is **no bridge** from ISC document hash to `Interscript::Node::Document`.
12+
Until this exists, `.isc` files cannot be used for actual transliteration.
13+
14+
## Solution
15+
16+
### 1. Create `Interscript::Isc::NodeAdapter`
17+
```
18+
lib/interscript/isc/node_adapter.rb
19+
```
20+
```ruby
21+
module Interscript::Isc
22+
class NodeAdapter
23+
def self.to_interscript_node(isc_doc)
24+
Interscript::Node::Document.new.tap do |doc|
25+
doc.metadata = build_metadata(isc_doc[:metadata])
26+
doc.tests = build_tests(isc_doc[:tests])
27+
isc_doc[:stages].each { |s| doc.stages[s[:name]] = build_stage(s) }
28+
isc_doc[:aliases].each { |a| doc.aliases[a[:name]] = build_alias(a) }
29+
end
30+
end
31+
end
32+
end
33+
```
34+
35+
### 2. Update `Interscript::Path` to resolve `.isc` files
36+
```ruby
37+
# In Interscript::Path.find_map
38+
[".isc", ".imp"].each do |ext|
39+
path = "#{dir}/#{name}#{ext}"
40+
return path if File.exist?(path)
41+
end
42+
```
43+
44+
### 3. Update `Interscript.load_map` to dispatch by extension
45+
```ruby
46+
def self.parse_map(path)
47+
return Isc.load_file(path) if path.end_with?(".isc")
48+
DSL.parse(File.basename(path, ".imp")) # legacy
49+
end
50+
```
51+
52+
## Verification
53+
```ruby
54+
# Both should produce identical output:
55+
Interscript.transliterate("alalc-amh-Ethi-Latn-1997", "ሀለ") # .imp
56+
Interscript.transliterate("alalc-amh-Ethi-Latn-1997", "ሀለ") # .isc (if .isc exists)
57+
```
58+
59+
## Autoload Registration
60+
Add to `lib/interscript/isc.rb`:
61+
```ruby
62+
autoload :NodeAdapter, "interscript/isc/node_adapter"
63+
```
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
# 01 — Commit .isc files to the maps repo
2+
3+
## Priority: P0 (canonical source)
4+
5+
## Problem
6+
All 289 `.isc` files are generated in `/tmp/isc-verify/` but not committed
7+
to the `interscript/maps` repo. The maps repo only has `.imp` files.
8+
9+
## Solution
10+
11+
### Steps
12+
1. Generate .isc files into the maps repo:
13+
```bash
14+
cd interscript-ruby
15+
ruby -Ilib exe/codemod-imp-to-isc --out-dir=../maps/maps ../maps/maps/*.imp
16+
```
17+
18+
2. In the maps repo:
19+
```bash
20+
cd ../maps
21+
git checkout -b feat/isc-maps
22+
git add maps/*.isc
23+
git diff --cached --name-only | grep -c '.isc' # should be 289
24+
git commit -m "feat: add ISC-format maps for all 289 systems"
25+
git push -u origin feat/isc-maps
26+
gh pr create --title "feat: add ISC maps (289 systems)" --body-file ...
27+
```
28+
29+
### CI Guard
30+
Add a CI check that regenerates .isc from .imp and verifies no drift:
31+
```yaml
32+
# .github/workflows/isc-consistency.yml
33+
- name: Regenerate ISC
34+
run: cd ../interscript-ruby && ruby -Ilib exe/codemod-imp-to-isc --out-dir=../maps/maps ../maps/maps/*.imp
35+
- name: Check for drift
36+
run: cd ../maps && git diff --exit-code maps/*.isc
37+
```
38+
39+
## Coordination
40+
- Ask user before pushing to `interscript/maps` (shared repo).
41+
- The maps repo has its own CI (CodeQL).
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
# 02 — Fix remaining 13 deep equivalence differences
2+
3+
## Priority: P1
4+
5+
## Current State
6+
- 274/289 deep equivalent
7+
- 2 IMP-fail (bgnpcgn-tuk: Ruby DSL can't parse, ISC can)
8+
- 13 differ (cosmetic / edge cases)
9+
10+
## Categories
11+
12+
### A. Description whitespace (5 maps) — `normalize_heredoc`
13+
**Maps:** alalc-kor, gki-bel, var-pra, var-san + 1
14+
15+
The `normalize_heredoc` method strips ALL leading whitespace per line. The
16+
Ruby DSL's YAML heredoc strips only the COMMON indent (dedent), preserving
17+
relative indentation.
18+
19+
**Fix:** Replace the simple strip with a proper dedent algorithm:
20+
1. Find minimum indent across non-blank lines
21+
2. Strip that amount from every line
22+
3. Handle the first line specially (grammar consumed its leading whitespace
23+
after the opening `{`)
24+
25+
**Risk:** Changing normalize_heredoc regressed 91 maps last time (270→179).
26+
The new algorithm must be strictly better than the current simple strip.
27+
28+
### B. Codemod edge cases (6 maps)
29+
**Maps:** alalc-tir x2, bgnpcgn-fas, mext-jpn, odni-ara/fas/prs
30+
31+
Each has a unique metadata pattern the codemod mishandles:
32+
- `alalc-tir`: description followed by `implementation_notes: |` heredoc
33+
- `bgnpcgn-fas`: `TODO: Add tests` treated as metadata field
34+
- `mext-jpn`: CJK name field, description mismatch
35+
- `odni-*`: `notes: - item` or `[]` leaking into description
36+
37+
**Fix:** Audit each .imp individually, extend codemod handlers.
38+
39+
### C. Rule count (2 maps)
40+
- `din-san-Deva-Latn-33904-2018`: imp=155 isc=154 (off by 1, likely `run` or `deep`)
41+
- `var-ara-Arab-Arab-rababa`: imp=1 isc=0 (rababa directive → comment, expected)
42+
43+
**Fix for din-san:** Diff the stage body item-by-item between .imp and .isc.

TODO.complete/03-fix-isc-specs.md

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
# 03 — Fix ISC spec failures (bundler workaround)
2+
3+
## Priority: P1
4+
5+
## Current State
6+
- 73 ISC specs written, 50 pass, 23 fail
7+
- Failures are syntax issues, not logic errors:
8+
- Metadata specs need system block wrapper
9+
- Transform specs need real parser output
10+
11+
## Remaining Failures
12+
13+
### Metadata specs (10 failing)
14+
The grammar requires a root `system "..." { ... }` block. Metadata specs
15+
test `metadata { ... }` standalone, which fails.
16+
17+
**Fix:** Wrap each metadata test:
18+
```ruby
19+
it "parses minimal metadata" do
20+
tree = parser.parse(<<~ISC, filename: "t.isc")
21+
system "TEST:eng-Latn:Latn:2026" {
22+
metadata {
23+
authority_id test
24+
}
25+
stage main { }
26+
}
27+
ISC
28+
expect(tree[:system][:body]).to be_an(Array)
29+
end
30+
```
31+
32+
### Transform specs (8 failing)
33+
Specs construct Parslet trees manually (`{ string: { simple: "x" } }`),
34+
but the actual parser output shape differs (e.g., `Parslet::Slice` instead
35+
of plain strings).
36+
37+
**Fix:** Use real parser output:
38+
```ruby
39+
it "transforms a quoted string" do
40+
src = %Q{system "X:e-Latn:Latn:1" { stage main { sub "x" "y" } }}
41+
tree = parser.parse(src, filename: "t.isc")
42+
doc = Interscript::Isc::DocumentBuilder.build(tree, filename: "t.isc")
43+
rule = doc[:stages].first[:body].first
44+
expect(rule[:from]).to be_a(Interscript::Isc::Items::StringValue)
45+
end
46+
```
47+
48+
### Other (5 failing)
49+
- DocumentBuilder tests expecting `:tests` output shape
50+
- Codemod modifier kwargs test
51+
- Concatenation tests
52+
53+
## Infrastructure Fix
54+
The project's `spec/spec_helper.rb` requires `bundler/setup` which fails on
55+
Ruby 3.4.8 (`DidYouMean::SPELL_CHECKERS` NameError).
56+
57+
**Fix:** Update bundler or add a Ruby version guard. The ISC specs use their
58+
own `spec/interscript/isc/spec_helper.rb` that avoids bundler.
59+
60+
## CI Integration
61+
Add to `.github/workflows/ci.yml`:
62+
```yaml
63+
- name: ISC specs
64+
run: bundle exec rspec spec/interscript/isc/ --options /dev/null
65+
```
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
# 04 — IS 1 specification compilation and publication
2+
3+
## Priority: P2
4+
5+
## Current State
6+
- `spec/isc/document.adoc` exists but hasn't been compiled
7+
- The spec describes ISC format but may lag behind grammar changes
8+
- No published HTML/PDF
9+
10+
## Steps
11+
12+
### 1. Review spec against grammar
13+
- Verify all grammar rules documented
14+
- Add escaped-brace syntax (`\{`, `\}`) for raw text
15+
- Document `separate separator`, `decompose` directives
16+
- Add `any(space+line_end)` pattern
17+
18+
### 2. Compile
19+
```bash
20+
bundle exec metanorma spec/isc/document.adoc
21+
```
22+
Produces HTML, PDF, and XML.
23+
24+
### 3. Publish
25+
- Copy compiled HTML to `interscript.org/public/spec/`
26+
- Add a `/spec` page on the website linking to it
27+
- Version the spec (IS 1.0) and track changes
28+
29+
## Spec Structure (reference: ISO 24229)
30+
1. Scope
31+
2. Normative references
32+
3. Terms and definitions
33+
4. System codes
34+
5. Metadata block
35+
6. Tests block
36+
7. Aliases block
37+
8. Stages (parallel, sequence, sub, run, separate, compose)
38+
9. Items (strings, primitives, constructors, functions)
39+
10. Constraints (before, after, not_before, not_after)
40+
11. Annex A: Migration from .imp (codemod)
41+
12. Annex B: Grammar reference (PEG)
42+
13. Annex C: Examples
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
# 05 — Performance: optimize Parslet parsing for large CJK maps
2+
3+
## Priority: P2
4+
5+
## Current State
6+
6 maps take 15-38s to parse:
7+
8+
| Map | Lines | Time |
9+
|-----|-------|------|
10+
| var-kor-Kore-Hang-2013 | 30k | 38s |
11+
| lshk-yue-Hani-Latn-jyutping-1993 | 20k | 29s |
12+
| hk-yue-Hani-Latn-1888 | 20k | 23s |
13+
| acadsin-zho-Hani-Latn-2002 | 15k | 23s |
14+
| var-zho-Hani-Latn-wd-1979 | 43k | 20s |
15+
| sac-zho-Hans-Latn-1979 | 26k | 15s |
16+
17+
## Root Cause
18+
Parslet PEG parser has O(n²) backtracking. The `alias_arg` rule's
19+
`zero_width_primitive.absent?` lookahead fires for every `any()` call.
20+
With 5000+ any() calls in var-zho, overhead compounds.
21+
22+
## Optimization Options
23+
24+
### A. Pre-compile .isc → .rb (eliminates runtime parsing entirely)
25+
Compile .isc to a .rb file that constructs Interscript::Node objects:
26+
```ruby
27+
# Generated from var-zho-Hani-Latn-wd-1979.isc
28+
doc = Interscript::Node::Document.new
29+
doc.stages[:main] = Interscript::Node::Stage.new
30+
doc.stages[:main].children << Interscript::Node::Group::Parallel.new(...)
31+
# ... 27,000+ rules
32+
```
33+
Load time: <1s (require vs 20s parse).
34+
35+
### B. Switch parser engine
36+
- **Racc** (LALR): no backtracking, O(n)
37+
- **Tree-sitter**: C-speed, incremental parsing
38+
- **Hand-written recursive descent**: fastest, most maintenance
39+
40+
### C. Memoize lookaheads
41+
Cache `keyword.absent?` and `zero_width_primitive.absent?` results per
42+
position. Requires Parslet monkey-patch or wrapper atom.
43+
44+
## Recommendation
45+
**Option A** is the right long-term solution:
46+
1. .isc is the human-editable source format
47+
2. .rb (or .json IR) is the runtime-loaded artifact
48+
3. `rake compile` generates artifacts from sources
49+
4. No runtime parsing needed
50+
51+
This aligns with the existing JsonIR compilation pipeline.
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
# 06 — Ruby DSL STANDARD_ARRAY_KEYS bug
2+
3+
## Priority: P3 (ISC already correct)
4+
5+
## Bug
6+
`lib/interscript/dsl/metadata.rb` line 47-61: methods for
7+
`notes`, `implementation_notes`, `original_notes`, `url` process input
8+
but **never store the result** in `@node`.
9+
10+
```ruby
11+
STANDARD_ARRAY_KEYS.each do |sym|
12+
define_method sym do |stuff|
13+
stuff = Array(stuff)
14+
stuff.map do |i|
15+
case i
16+
when String then i
17+
else
18+
warn "..."
19+
i.inspect
20+
end
21+
end
22+
# BUG: result is discarded. Missing: @node[sym] = result
23+
end
24+
end
25+
```
26+
27+
## Impact
28+
- Ruby DSL `metadata.data[:url]` returns nil for ALL maps
29+
- ISC parser correctly stores these fields
30+
- Deep equivalence checker must skip these fields
31+
32+
## Fix
33+
```ruby
34+
STANDARD_ARRAY_KEYS.each do |sym|
35+
define_method sym do |stuff|
36+
@node[sym] = Array(stuff).map do |i|
37+
case i
38+
when String then i
39+
else
40+
warn "[#{@map_name}] Metadata key #{sym} expects String"
41+
i.inspect
42+
end
43+
end
44+
end
45+
end
46+
```
47+
48+
## Verification
49+
After fix, remove the skip in `exe/verify_isc_deep`:
50+
```ruby
51+
skip_fields = [:nonstandard, :tests, :stages, :aliases, :dependencies]
52+
# Remove :url, :notes, :implementation_notes, :original_notes from skip
53+
```
54+
Re-run: all 289 maps should match on these fields.

0 commit comments

Comments
 (0)