|
| 1 | +// yaml-blockscalar-depth-probe.ts — the depth gate for the BLOCK-SCALAR carrier (§2a⁗), the indent |
| 2 | +// construct whose body-mode (the content FLOOR) is per-construct and therefore NOT covered by the |
| 3 | +// block-sequence deepest-sibling probe even though both share the consuming-carry core. |
| 4 | +// |
| 5 | +// A `|N`/`>N`/`|` block scalar whose mapping sits in a compact block sequence (`- - … k: |N`) has a |
| 6 | +// content floor; a line AT the floor is block-scalar content, a line one column SHALLOWER ends the |
| 7 | +// scalar (a comment / sibling). The §2a⁗ carry must place that floor correctly at ARBITRARY compact |
| 8 | +// depth (the monogram#12 #14 class: a flat floor saturated at depth ≥ 2). This sweeps d=1..12 and |
| 9 | +// checks BOTH directions against the eemeli CST oracle: the floor line must be block content (an |
| 10 | +// UNDER-floor regression paints it a comment), the floor-1 line must be a comment (an OVER-floor |
| 11 | +// regression paints it block string). Covers explicit `|N` (N=2,3) and the auto-detect `|`. |
| 12 | +// |
| 13 | +// Run (bare node): node test/yaml-blockscalar-depth-probe.ts · Exit 0 iff zero mismatch. |
| 14 | +import { readFileSync } from 'node:fs'; |
| 15 | +import { createRequire } from 'node:module'; |
| 16 | +import vsctm from 'vscode-textmate'; |
| 17 | +import onig from 'vscode-oniguruma'; |
| 18 | +import YAML from 'yaml'; |
| 19 | + |
| 20 | +const { INITIAL, Registry } = vsctm; |
| 21 | +const { loadWASM, OnigScanner, OnigString } = onig; |
| 22 | +const require = createRequire(import.meta.url); |
| 23 | +const wasm = readFileSync(require.resolve('vscode-oniguruma/release/onig.wasm')); |
| 24 | +await loadWASM(wasm.buffer.slice(wasm.byteOffset, wasm.byteOffset + wasm.byteLength)); |
| 25 | + |
| 26 | +const raw = JSON.parse(readFileSync('./yaml.tmLanguage.json', 'utf8')); |
| 27 | +const registry = new Registry({ |
| 28 | + onigLib: Promise.resolve({ |
| 29 | + createOnigScanner: (ps: string[]) => new OnigScanner(ps), |
| 30 | + createOnigString: (s: string) => new OnigString(s), |
| 31 | + }), |
| 32 | + loadGrammar: async (scope: string) => (scope === raw.scopeName ? raw : null), |
| 33 | +}); |
| 34 | +const grammar = await registry.loadGrammar(raw.scopeName); |
| 35 | + |
| 36 | +function scopeAt(src: string, lineIdx: number, col: number): string | null { |
| 37 | + const lines = src.split('\n'); |
| 38 | + let rs = INITIAL; |
| 39 | + let res: string | null = null; |
| 40 | + for (let i = 0; i < lines.length; i++) { |
| 41 | + const r = grammar!.tokenizeLine(lines[i], rs); |
| 42 | + if (i === lineIdx) for (const t of r.tokens) if (col >= t.startIndex && col < t.endIndex) res = t.scopes[t.scopes.length - 1]; |
| 43 | + rs = r.ruleStack; |
| 44 | + } |
| 45 | + return res; |
| 46 | +} |
| 47 | + |
| 48 | +// eemeli CST oracle: the kind of token covering the byte offset of (lineIdx, col). |
| 49 | +function oracleKind(src: string, lineIdx: number, col: number): 'blockscalar' | 'comment' | '?' { |
| 50 | + const lines = src.split('\n'); |
| 51 | + let off = 0; |
| 52 | + for (let i = 0; i < lineIdx; i++) off += lines[i].length + 1; |
| 53 | + off += col; |
| 54 | + let kind: 'blockscalar' | 'comment' | '?' = '?'; |
| 55 | + const walk = (t: any) => { |
| 56 | + if (!t || typeof t !== 'object') return; |
| 57 | + if (t.offset != null && typeof t.source === 'string' && off >= t.offset && off < t.offset + t.source.length) { |
| 58 | + if (t.type === 'comment') kind = 'comment'; |
| 59 | + else if (t.type === 'block-scalar') kind = 'blockscalar'; |
| 60 | + } |
| 61 | + for (const k in t) if (t[k] && typeof t[k] === 'object') walk(t[k]); |
| 62 | + }; |
| 63 | + for (const tok of new YAML.Parser().parse(src)) walk(tok); |
| 64 | + return kind; |
| 65 | +} |
| 66 | + |
| 67 | +const bad: string[] = []; |
| 68 | +let checked = 0; |
| 69 | + |
| 70 | +// At each depth, the floor line (indent = floor) must be block content; the floor-1 line a comment. |
| 71 | +function check(label: string, src: string, floor: number) { |
| 72 | + // floor line is line 1 for explicit (header, floor-line, below) — caller passes the line layout via |
| 73 | + // marker text; we locate the two marker lines by their leading-space width. |
| 74 | + const lines = src.split('\n'); |
| 75 | + const floorLine = lines.findIndex((l) => l.startsWith(' '.repeat(floor) + '#atfloor')); |
| 76 | + const belowLine = lines.findIndex((l) => l.startsWith(' '.repeat(floor - 1) + '#below')); |
| 77 | + if (floorLine < 0 || belowLine < 0) { bad.push(`${label}: witness malformed`); return; } |
| 78 | + checked += 2; |
| 79 | + const gF = scopeAt(src, floorLine, floor), oF = oracleKind(src, floorLine, floor); |
| 80 | + const gB = scopeAt(src, belowLine, floor - 1), oB = oracleKind(src, belowLine, floor - 1); |
| 81 | + // floor line: oracle blockscalar, grammar string.unquoted.block (UNDER-floor if grammar says comment) |
| 82 | + if (!(oF === 'blockscalar' && /string\.unquoted\.block/.test(gF ?? ''))) bad.push(`${label} FLOOR@${floor}: grammar «${gF}» oracle «${oF}» (want block content)`); |
| 83 | + // floor-1 line: oracle comment, grammar comment (OVER-floor if grammar says block string) |
| 84 | + if (!(oB === 'comment' && /comment/.test(gB ?? ''))) bad.push(`${label} FLOOR-1@${floor - 1}: grammar «${gB}» oracle «${oB}» (want comment)`); |
| 85 | +} |
| 86 | + |
| 87 | +for (let d = 1; d <= 12; d++) { |
| 88 | + const dash = '- '.repeat(d); |
| 89 | + const prefix = dash.length; // 2*d |
| 90 | + // EXPLICIT |N (N = 2, 3): floor = prefix + N. |
| 91 | + for (const N of [2, 3]) { |
| 92 | + const floor = prefix + N; |
| 93 | + const src = `${dash}k: |${N}\n` + ' '.repeat(floor) + '#atfloor\n' + ' '.repeat(floor - 1) + '#below\n'; |
| 94 | + if (YAML.parseDocument(src).errors.length === 0) check(`d${d} |${N}`, src, floor); |
| 95 | + } |
| 96 | + // AUTO-detect |: the floor is the first body line's indent; place it at prefix+2. |
| 97 | + { |
| 98 | + const floor = prefix + 2; |
| 99 | + const src = `${dash}k: |\n` + ' '.repeat(floor) + '#atfloor\n' + ' '.repeat(floor - 1) + '#below\n'; |
| 100 | + if (YAML.parseDocument(src).errors.length === 0) check(`d${d} |auto`, src, floor); |
| 101 | + } |
| 102 | +} |
| 103 | + |
| 104 | +console.log(`checked ${checked} (floor, floor-1) assertions at d=1..12 (explicit |2/|3 + auto |) vs the eemeli CST oracle`); |
| 105 | +for (const b of bad) console.log(` ✗ ${b}`); |
| 106 | +console.log(bad.length === 0 ? '\n✓ block-scalar content floor correct at every depth (no saturation)' : `\n✗ ${bad.length} mismatch(es)`); |
| 107 | +process.exit(bad.length === 0 ? 0 : 1); |
0 commit comments