@@ -1838,6 +1838,57 @@ console.log('\n\n=== TEST: Flex single-line /* */ comments not parsed as rules =
18381838 `Exactly 3 rules parsed (not the /* */ comments), got ${ docComment . rules . length } ` ) ;
18391839}
18401840
1841+ // ════════════════════════════════════════
1842+ // TEST: $n with single-quoted and double-quoted literals (issue #4)
1843+ // ════════════════════════════════════════
1844+ console . log ( '\n\n=== TEST: $n bounds — literals counted as symbols ===\n' ) ;
1845+
1846+ {
1847+ // rule: A '(' B ')' { $$ = $3; } → 4 symbols, $3 valid → 0 diagnostics
1848+ const bisonLit = [
1849+ '%token A B' ,
1850+ '%%' ,
1851+ 'start : rule ;' ,
1852+ "rule : A '(' B ')' { $$ = $3; }" ,
1853+ '%%' ,
1854+ ] . join ( '\n' ) ;
1855+ const docLit = parseBisonDocument ( bisonLit ) ;
1856+ const diagsLit = computeBisonDiagnostics ( docLit , bisonLit ) ;
1857+ const oobLit = diagsLit . filter ( d => d . message . includes ( 'out of bounds' ) ) ;
1858+ assert ( oobLit . length === 0 ,
1859+ `Single-quoted literals '(' and ')' are counted as symbols — $3 in A '(' B ')' must not be flagged (got: ${ oobLit . map ( d => d . message ) . join ( '; ' ) } )` ) ;
1860+
1861+ // rule: A "and" B "or" C { $$ = $4; } → 5 symbols, $4 valid → 0 diagnostics
1862+ const bisonAlias = [
1863+ '%token A B C' ,
1864+ '%%' ,
1865+ 'start : rule ;' ,
1866+ 'rule : A "and" B "or" C { $$ = $4; }' ,
1867+ '%%' ,
1868+ ] . join ( '\n' ) ;
1869+ const docAlias = parseBisonDocument ( bisonAlias ) ;
1870+ const diagsAlias = computeBisonDiagnostics ( docAlias , bisonAlias ) ;
1871+ const oobAlias = diagsAlias . filter ( d => d . message . includes ( 'out of bounds' ) ) ;
1872+ assert ( oobAlias . length === 0 ,
1873+ `Double-quoted aliases "and" and "or" are counted as symbols — $4 in A "and" B "or" C must not be flagged (got: ${ oobAlias . map ( d => d . message ) . join ( '; ' ) } )` ) ;
1874+
1875+ // rule: A B { $$ = $3; } → 2 symbols, $3 out of bounds → 1 Error
1876+ const bisonOob = [
1877+ '%token A B' ,
1878+ '%%' ,
1879+ 'start : rule ;' ,
1880+ 'rule : A B { $$ = $3; }' ,
1881+ '%%' ,
1882+ ] . join ( '\n' ) ;
1883+ const docOob = parseBisonDocument ( bisonOob ) ;
1884+ const diagsOob = computeBisonDiagnostics ( docOob , bisonOob ) ;
1885+ const oobOob = diagsOob . filter ( d => d . message . includes ( '$3' ) && d . message . includes ( 'out of bounds' ) ) ;
1886+ assert ( oobOob . length >= 1 ,
1887+ `$3 in a 2-symbol rule A B must be flagged as out of bounds` ) ;
1888+ assert ( oobOob [ 0 ] ?. severity === 1 ,
1889+ `$3 out-of-bounds diagnostic has Error severity` ) ;
1890+ }
1891+
18411892// ════════════════════════════════════════
18421893// SUMMARY
18431894// ════════════════════════════════════════
0 commit comments