Skip to content

Commit 6b76dc6

Browse files
committed
docs: TODO.restructure — eliminate JSON IR, .isc direct everywhere
10 TODOs for architecture restructure: 01 TS ISC parser (Peggy) 02 Website serve .isc instead of .json 03 Map pages from .isc at build time 04 TS ISC loader strategy 05 Remove JSON IR primary pipeline 06 Ruby JsonIR as optional export 07 Cross-runtime parity testing 08 CI validate .isc in all runtimes 09 Open PRs and merge 10 IS 1 specification Core principle: .isc is single source format. Both runtimes parse .isc directly. No compilation, no drift.
1 parent 3e2d88d commit 6b76dc6

11 files changed

Lines changed: 400 additions & 0 deletions
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
# 01 — TS ISC Parser (Peggy grammar)
2+
3+
## Priority: P0 — blocks all website restructure work
4+
5+
## Problem
6+
The TS runtime currently consumes compiled JSON IR (`.json` files generated
7+
by Ruby). To eliminate JSON IR, the TS runtime needs its own ISC parser.
8+
9+
## Design
10+
11+
Port the Ruby Parslet grammar to Peggy (PEG parser generator for JS/TS).
12+
The grammar rules map 1:1:
13+
14+
| Parslet (Ruby) | Peggy (JS) |
15+
|----------------|------------|
16+
| `str("system")` | `"system"` |
17+
| `whitespace` | `\\s+` |
18+
| `quoted_string` | `'"' ('\\\\' ./ | !'"' .)* '"'` |
19+
| `braced(inner)` | `'{' \\s* inner \\s* '}'` |
20+
| `rule(:name) do ... end` | `name = ...` |
21+
22+
### Structure
23+
```
24+
interscript-ts/
25+
src/
26+
isc/
27+
grammar.peggy # Peggy grammar (source of truth for TS)
28+
parser.ts # Wrapper: parse(src) → document hash
29+
document-builder.ts # Hash → typed CompiledMap
30+
types.ts # IscDocument, IscStage, IscRule, IscItem types
31+
test/
32+
isc/
33+
parser.test.ts # Unit tests
34+
parity.test.ts # Cross-validate with Ruby document hashes
35+
```
36+
37+
### Grammar scope (from Ruby Parslet)
38+
- System block: `system "CODE" { body }`
39+
- Metadata: `metadata { key value ... }`
40+
- Tests: `tests { "input" -> "expected" }`
41+
- Aliases: `aliases { name = item }`
42+
- Stages: `stage name { parallel { ... } sub "a" "b" ... }`
43+
- Items: quoted strings, any(), capture(), ref(), none, primitives
44+
- Constraints: before, after, not_before, not_after
45+
- Directives: run, separate, compose, downcase/upcase/title_case
46+
47+
### API
48+
```typescript
49+
import { parseIsc } from "interscript-ts/isc"
50+
51+
const doc = parseIsc(iscSource, "map.isc")
52+
// doc: { systemCode, metadata, tests, stages, aliases, dependencies }
53+
```
54+
55+
### Loader strategy
56+
```typescript
57+
import { iscStrategy } from "interscript-ts"
58+
59+
configure({ strategies: [iscStrategy({ baseUrl: "/maps" })] })
60+
// Fetches /maps/foo.isc, parses, feeds to runtime
61+
```
62+
63+
## Verification
64+
- Parse all 289 .isc files
65+
- Document hash matches Ruby document hash (cross-validate)
66+
- Transliteration output matches Ruby 100%
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
# 02 — Website: serve .isc files instead of .json
2+
3+
## Priority: P0
4+
5+
## Problem
6+
The website serves compiled JSON IR from `/public/maps/*.json`.
7+
These are generated by Ruby from `.imp` files (now `.isc`).
8+
9+
## Solution
10+
Replace JSON IR files with `.isc` source files. The TS ISC parser
11+
handles parsing at load time.
12+
13+
### Changes
14+
1. Copy 289 `.isc` files from maps repo to `interscript.org/public/maps/`
15+
2. Remove 291 `.json` files from `interscript.org/public/maps/`
16+
3. Update `MapExplorer.vue` to use `iscStrategy` instead of `bundledStrategy`
17+
4. Update transliteration worker to use ISC loader
18+
19+
### Performance consideration
20+
Parsing .isc in the browser is slower than loading pre-compiled JSON.
21+
Mitigation:
22+
- Web Worker parses off main thread
23+
- Cache parsed documents in IndexedDB
24+
- Pre-parse at build time for SSG pages (map detail)
25+
26+
### Verification
27+
- All 38 E2E tests pass
28+
- Full-map validation (37 tests) passes
29+
- 7,502-sample parity test shows 0 diffs
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
# 03 — Website: render map pages from .isc at build time
2+
3+
## Priority: P1
4+
5+
## Problem
6+
Map detail pages (e.g., `/maps/bgnpcgn-ukr-Cyrl-Latn-2019`) currently
7+
render from JSON IR metadata. They could render directly from .isc source.
8+
9+
## Solution
10+
At Astro build time:
11+
1. Read each `.isc` file
12+
2. Parse with TS ISC parser (or Ruby if building with Ruby available)
13+
3. Extract metadata, tests, stage structure
14+
4. Render to static HTML
15+
16+
### Pages affected
17+
- `/maps/[code]` — map detail (metadata, rules, tests)
18+
- `/maps` — catalogue (list all maps with metadata)
19+
- `/authorities/[auth]` — authority grouping
20+
21+
### Implementation
22+
```typescript
23+
// astro.config or scripts/generate-map-pages.ts
24+
import { parseIsc } from "interscript-ts/isc"
25+
import { readFileSync } from "fs"
26+
27+
const maps = readFileSync("public/maps/*.isc").map(parseIsc)
28+
// Generate static pages from parsed documents
29+
```
30+
31+
### Benefit
32+
- No JSON IR files needed
33+
- Map pages always reflect the latest .isc source
34+
- No compilation step or drift
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
# 04 — TS runtime: ISC loader strategy
2+
3+
## Priority: P1
4+
5+
## Problem
6+
The TS runtime has load strategies for JSON IR (`bundledStrategy`,
7+
`httpStrategy`). Need a new strategy that loads `.isc` source files
8+
and parses them on the fly.
9+
10+
## Design
11+
```typescript
12+
// src/isc/isc-loader.ts
13+
import { parseIsc } from "./parser"
14+
import { normaliseMap } from "../types"
15+
16+
export function iscStrategy(opts: { baseUrl: string }): LoadStrategy {
17+
return {
18+
async load(code: string): Promise<CompiledMap | null> {
19+
const res = await fetch(`${opts.baseUrl}/${code}.isc`)
20+
if (!res.ok) return null
21+
const source = await res.text()
22+
const doc = parseIsc(source, code)
23+
return normaliseMap(doc) // Convert to CompiledMap shape
24+
}
25+
}
26+
}
27+
```
28+
29+
### Backward compatibility
30+
Keep existing JSON IR strategies as optional. Users who prefer
31+
pre-compiled JSON can still use `bundledStrategy` or `httpStrategy`.
32+
The new `iscStrategy` is the recommended default.
33+
34+
## Verification
35+
- `transliterate("bgnpcgn-ukr-Cyrl-Latn-2019", "Антон")` works with iscStrategy
36+
- All 289 maps load and transliterate correctly
37+
- Performance: parse time < 100ms for 95% of maps (large CJK maps may be slower)
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
# 05 — Remove JSON IR as primary pipeline
2+
3+
## Priority: P2 (after 01-04 are done)
4+
5+
## Problem
6+
`Interscript::Compiler::JsonIR` generates JSON IR from Node::Document.
7+
This was the ONLY way to feed maps to the TS runtime. With a TS ISC
8+
parser, JSON IR is no longer needed as the primary pipeline.
9+
10+
## Solution
11+
1. Keep JsonIR compiler as an OPTIONAL export (for backward compat)
12+
2. Remove it from the default build pipeline
13+
3. Remove JSON IR files from the website
14+
4. Remove the `gen-parity-fixtures.rb` dependency on JSON IR
15+
16+
## What stays
17+
- `Interscript::Compiler::JsonIR` class — still available for users who
18+
want pre-compiled maps
19+
- `interscript.org/public/maps/*.json` — removed (replaced by .isc)
20+
21+
## Migration path for existing users
22+
1. Users who load `.json` via `bundledStrategy` → switch to `iscStrategy`
23+
2. Users who generate `.json` via Ruby → can still use JsonIR compiler
24+
3. The .isc files are the canonical source for both runtimes
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
# 06 — Ruby: keep JsonIR as optional export
2+
3+
## Priority: P2
4+
5+
## Problem
6+
The JsonIR compiler should remain available but not be the default
7+
pipeline. Users who want pre-compiled JSON for performance can still
8+
generate it.
9+
10+
## Design
11+
```ruby
12+
# Generate JSON IR from .isc (optional, not default)
13+
Interscript.load_path.unshift("maps")
14+
doc = Interscript.parse_isc("maps/foo.isc") # Parse .isc
15+
node = Interscript::Isc::NodeAdapter.to_interscript_node(doc)
16+
json = Interscript::Compiler::JsonIR.compile(node)
17+
File.write("foo.json", json)
18+
```
19+
20+
## No code change needed
21+
The existing `Interscript::Compiler::JsonIR` already works with
22+
Node::Document objects. The NodeAdapter converts .isc → Node.
23+
So the pipeline `.isc → parse → NodeAdapter → JsonIR` already works.
24+
25+
## Verification
26+
```ruby
27+
# Generate IR from .isc and compare with old .json
28+
node = Isc::NodeAdapter.to_interscript_node(
29+
Isc::DocumentBuilder.build(
30+
Isc::Parser.parse(File.read("maps/foo.isc"))))
31+
ir = Interscript::Compiler::JsonIR.compile(node)
32+
old_ir = JSON.parse(File.read("public/maps/foo.json"))
33+
# ir and old_ir should be equivalent
34+
```
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
# 07 — Cross-runtime parity testing
2+
3+
## Priority: P1
4+
5+
## Problem
6+
With two ISC parsers (Ruby Parslet + TS Peggy), we need to verify
7+
they produce semantically equivalent document models.
8+
9+
## Design
10+
1. Ruby parses all 289 .isc files → document hashes
11+
2. TS parses all 289 .isc files → document hashes
12+
3. Compare: same system code, same test count, same stage structure
13+
4. Compare: same transliteration output for all test vectors
14+
15+
### Test structure
16+
```
17+
interscript-ts/test/isc/
18+
cross-parity.test.ts # Compare TS parse vs Ruby parse
19+
transliteration.test.ts # Compare TS transliteration vs known-good
20+
```
21+
22+
### Ruby side: export reference hashes
23+
```bash
24+
ruby -Ilib -e '
25+
require "interscript/isc"
26+
require "json"
27+
results = {}
28+
Dir.glob("../maps/maps/*.isc").each do |path|
29+
tree = Isc::Parser.parse(File.read(path))
30+
doc = Isc::DocumentBuilder.build(tree)
31+
results[doc[:systemCode]] = {
32+
tests: doc[:tests].size,
33+
stages: doc[:stages].size,
34+
}
35+
end
36+
File.write("test/fixtures/reference-hashes.json", JSON.pretty_generate(results))
37+
'
38+
```
39+
40+
### TS side: parse and compare
41+
```typescript
42+
describe("cross-runtime parity", () => {
43+
const refs = JSON.parse(readFileSync("test/fixtures/reference-hashes.json"))
44+
for (const code of Object.keys(refs)) {
45+
it(`${code}: matches Ruby parse`, () => {
46+
const src = readFileSync(`../maps/maps/${code}.isc`, "utf8")
47+
const doc = parseIsc(src)
48+
expect(doc.tests.length).toBe(refs[code].tests)
49+
expect(doc.stages.length).toBe(refs[code].stages)
50+
})
51+
}
52+
})
53+
```
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
# 08 — CI: validate .isc parse in all runtimes
2+
3+
## Priority: P2
4+
5+
## Problem
6+
Need CI checks to verify .isc files parse correctly in both Ruby and TS.
7+
8+
## Design
9+
### Ruby CI
10+
```yaml
11+
- name: ISC specs
12+
run: rspec spec/interscript/isc/ --options /dev/null
13+
14+
- name: Parse all maps
15+
run: ruby -Ilib -e '
16+
require "interscript/isc"
17+
Dir.glob("../maps/maps/*.isc").each do |path|
18+
Isc::Parser.parse(File.read(path), filename: File.basename(path))
19+
end
20+
'
21+
```
22+
23+
### TS CI
24+
```yaml
25+
- name: ISC parser tests
26+
run: npx vitest run test/isc/
27+
28+
- name: Parse all maps
29+
run: npx tsx -e '
30+
import { parseIsc } from "./src/isc/parser"
31+
import { readdirSync, readFileSync } from "fs"
32+
for (const f of readdirSync("../maps/maps").filter(f => f.endsWith(".isc"))) {
33+
parseIsc(readFileSync(`../maps/maps/${f}`, "utf8"))
34+
}
35+
'
36+
```
37+
38+
### Maps repo CI
39+
```yaml
40+
- name: ISC parse check
41+
run: |
42+
cd ../interscript-ruby
43+
ruby -Ilib -e 'require "interscript/isc"; Dir.glob("../maps/maps/*.isc").each { |p| Isc::Parser.parse(File.read(p), filename: File.basename(p)) }'
44+
45+
- name: Codemod drift check (optional)
46+
run: |
47+
# Verify .isc files are valid (no manual edits broke the format)
48+
# This is a lightweight check, not a full codemod re-run

TODO.restructure/09-open-prs.md

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
# 09 — Open PRs and merge
2+
3+
## Priority: P0 — blocks all other work
4+
5+
## Steps
6+
7+
### Maps repo PR
8+
```bash
9+
cd interscript/maps
10+
gh pr create --title "feat: replace .imp with .isc format (289 maps)" \
11+
--body "All 289 maps converted from Ruby DSL to ISC format."
12+
```
13+
14+
### Ruby repo PR
15+
```bash
16+
cd interscript/interscript-ruby
17+
gh pr create --title "feat: ISC format — parser, NodeAdapter, YAML round-trip, serializer" \
18+
--body "Complete ISC infrastructure: 105 specs, 289/289 parse, NodeAdapter, YAML bridge, serializer."
19+
```
20+
21+
### Merge order
22+
1. Maps repo PR first (provides .isc files)
23+
2. Ruby repo PR second (depends on .isc for locate)
24+
3. Website changes third (depends on both)
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
# 10 — IS 1 specification (Metanorma)
2+
3+
## Priority: P2
4+
5+
## Goal
6+
Compile `spec/isc/document.adoc` and publish to the website.
7+
8+
## Architecture impact
9+
The spec should document:
10+
- ISC as the canonical source format (not .imp or .json)
11+
- Both Ruby and TS parsers as first-class implementations
12+
- YAML round-trip as an optional interchange format
13+
- JsonIR as an optional compiled format
14+
15+
## Steps
16+
1. Update spec/isc/document.adoc to reflect current grammar
17+
2. Add YAML round-trip annex
18+
3. Add TS parser specification
19+
4. Compile with Metanorma
20+
5. Publish to interscript.org/spec

0 commit comments

Comments
 (0)