Skip to content

Commit bae6620

Browse files
committed
scope≡role: catch a structural literal painted as a NAME (extends gate-1)
gate-1 flagged a `$punct`/`$keyword` painted as CONTENT (#24). Extend it to also flag a `$punct` painted as a NAME bucket (entity/variable/support): a `-`/`[`/`:` is never a named entity, so a flat grammar that leaks a flow region into a block and paints a sequence `-` as a key name (monogram's YAML explicit-key gap) is caught. `$keyword` is EXCLUDED from the name class — a keyword legitimately can be a name in context (the TS `this` parameter is `variable.parameter`), which would false-positive; content (never valid for a keyword) still flags both. The overlap check already clears any literal the grammar DECLARES name/content via a scopes-override, so reaching here means the highlighter invented the class. Also fix a ledger/check corpus mismatch: gap-ledger.ts generated with the DEFAULT opts (maxInputs 400) while generative.ts uses GEN_OPTS (1500) — so the ledger could miss divergences the check reports. The ledger now uses GEN_OPTS (the same corpus). KNOWN-GAPS.md: 2→3 gaps — the YAML explicit-key divergence now files, ddmin-minimized to `? a:\n -` (the `[` was incidental; the real trigger is an explicit-key value's same-column sequence `-` painted entity.name.tag). 7/7 gated-clean (no $keyword false positive); deterministic; --check + selftest + agnostic 9/9 green.
1 parent 437a5ea commit bae6620

3 files changed

Lines changed: 37 additions & 5 deletions

File tree

KNOWN-GAPS.md

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,29 @@ commits) so the ledger is deterministic and commit-trackable.
1414

1515
Regenerate: `node test/gap-ledger.ts --write` · verify up-to-date: `node test/gap-ledger.ts --check`.
1616

17-
**2 gaps** across 7 grammars · 0 dropped.
17+
**3 gaps** across 7 grammars · 0 dropped.
18+
19+
## `0b90a1776868` — yaml: structural-literal→name
20+
21+
- **Language:** yaml
22+
- **Minimal repro:** `? a:\n -`
23+
- **Divergent token:** `-` (parser token `$punct`)
24+
- **Role vs scope:** want **punct**, got **name** (highlighter scope `entity.name.tag.yaml`)
25+
- **Fingerprint:** `0b90a1776868`
26+
27+
```json
28+
{
29+
"id": "0b90a1776868",
30+
"language": "yaml",
31+
"kind": "structural-literal→name",
32+
"repro": "? a:\n -",
33+
"tokenType": "$punct",
34+
"tokenText": "-",
35+
"want": "punct",
36+
"got": "name",
37+
"gotScope": "entity.name.tag.yaml"
38+
}
39+
```
1840

1941
## `525e867dc205` — html: #24 structural-literal→content
2042

test/gap-ledger.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ import type { CstGrammar } from '../src/types.ts';
5252
import { generateInputs } from './grammar-gen.ts';
5353
import {
5454
type TmTok, type Violation,
55-
buildRoleMap, leafRoles, anchoredScopes, collectViolations, isGated,
55+
buildRoleMap, leafRoles, anchoredScopes, collectViolations, isGated, GEN_OPTS,
5656
} from './generative-detect.ts';
5757

5858
// ── language registry — the SAME per-language DATA shape as generative.ts's LANGS, plus an
@@ -247,7 +247,7 @@ async function runLang(cfg: LangCfg): Promise<LangResult> {
247247
// 1) DISCOVER — generate deterministically, then collect the DISCOVERED (report-only, !isGated)
248248
// divergences over the full-document inputs. (The gated ones are generative.ts's hard failures;
249249
// on this UNFIXED branch there are none. The ledger files the floor-blind DISCOVERED class.)
250-
const inputs = generateInputs(grammar);
250+
const inputs = generateInputs(grammar, GEN_OPTS); // SAME corpus generative.ts checks, or the ledger misses divergences the check reports
251251
const discoveredVs: Violation[] = [];
252252
for (const inp of inputs) {
253253
let cst: CstNode, toks: TmTok[];

test/generative-detect.ts

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -153,9 +153,19 @@ export function collectViolations(args: {
153153
const got = spanBuckets(toks, input, lr.start, lr.end);
154154
const overlap = [...lr.expected].some((b) => got.has(b));
155155
if (overlap) continue; // highlighter painted the declared scope somewhere → consistent
156+
// A structural literal (`$punct`/`$keyword`) the parser placed as grammar structure painted as a
157+
// CONTENT class (#24) OR as a NAME class (entity/variable/support — a `-` indicator scoped as a key
158+
// name when a flat grammar leaks a flow region into a block, monogram's explicit-key `[` gap). Both
159+
// are unambiguous: the overlap check above already cleared any literal the grammar DECLARES as
160+
// name/content (a scopes-override), so reaching here means the highlighter invented the class.
156161
const contentGot = [...got].find((b) => CONTENT.has(b));
157-
if (lr.lit && contentGot && count < cap) {
158-
out.push({ input, strategy, pos: lr.start, text: lr.text, tokenType: lr.tokenType, expected: [...lr.expected].join('|'), got: contentGot, gotScope: innerOf(scopeAt(toks, lr.start)), kind: '#24 structural-literal→content' });
162+
// the NAME check is `$punct`-only: a `-`/`[`/`:` is never a named entity, but a `$keyword` legitimately
163+
// CAN be (the TS `this` parameter is painted `variable.parameter`, a name) — so keywords are excluded
164+
// from the name class to avoid that false positive (content, where a keyword is never valid, stays both).
165+
const nameGot = lr.tokenType === '$punct' && got.has('name') ? 'name' as const : undefined;
166+
const badGot = contentGot ?? nameGot;
167+
if (lr.lit && badGot && count < cap) {
168+
out.push({ input, strategy, pos: lr.start, text: lr.text, tokenType: lr.tokenType, expected: [...lr.expected].join('|'), got: badGot, gotScope: innerOf(scopeAt(toks, lr.start)), kind: contentGot ? '#24 structural-literal→content' : 'structural-literal→name' });
159169
count++;
160170
}
161171
}

0 commit comments

Comments
 (0)