Skip to content

Commit bb6c5a9

Browse files
committed
Fix document-root block scalar with column-0 content
A document-root block scalar (`--- |` or a bare `|`/`>` at column 0, no mapping key before it) has a body that may sit at column 0. The node-indent- bounded block-scalar region couldn't keep it — its `while: \G(?=\1[ \t]|…)` has an empty `\1` node indent at column 0, so a column-0 body line failed both arms and leaked to the top-level rules, where a `%`-led body line (a PostScript header `%!PS-Adobe-2.0`) mis-scoped as a directive. Add a dedicated `${bsKey}-doc` region derived from indent.blockScalar.documentMarkers: it bounds the body at the next column-0 document marker (`---`/`...`) or EOF instead of by node indent, reusing the existing introducer/funky-body helper. Ranked above the plain block-scalar region so it wins the column-0 case; its lookahead requires the introducer to be the first value token so it never steals a key:/dash/nested header. Exposed by the extended role-oracle: YAML lit.string now 556/556, zero Monogram-wrong; Monogram 99.2% vs official 98.9%. Other 6 grammars byte-identical.
1 parent 4fef1df commit bb6c5a9

2 files changed

Lines changed: 125 additions & 1 deletion

File tree

src/gen-tm.ts

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4741,6 +4741,33 @@ export function generateTmLanguage(grammar: CstGrammar, langName: string): TmGra
47414741
while: '\\G(?=\\1[ \\t]|[ \\t]*$)',
47424742
patterns: [bsIntroRule(bsIndicator, bsContent), ...bsHeaderIncs],
47434743
};
4744+
// DOCUMENT-ROOT block scalar (`--- |` / bare `|` at column 0, NO mapping key / sequence dash
4745+
// before it). Such a node sits at the document level, whose indentation is "-1", so its body may
4746+
// begin at COLUMN 0 (W4TN `--- |` / M7A3 bare `|`, both valid YAML). The node-indent–bounded
4747+
// `${bsKey}` region above fails here: its `while: \G(?=\1[ \t]|…)` has `\1` = the EMPTY column-0
4748+
// node indent, so `\1[ \t]` degenerates to `[ \t]` (one leading space REQUIRED) and a column-0
4749+
// body line matches neither arm → the region ends after the header and the body falls through to
4750+
// the directive/key tokens (a `%`-led body line mis-scopes as a directive). A document-root scalar
4751+
// is instead bounded by the next column-0 DOCUMENT MARKER (`---`/`...`) or EOF — exactly how the
4752+
// maintained RedCMD grammar bounds it (its block scalar sits under the document region whose
4753+
// `while: \G(?!(?:…|---)…)` releases only at a marker). Derived from `indent.blockScalar`: the
4754+
// introducer chars (`intro`) and the `documentMarkers` (the only YAML-specific literals, read from
4755+
// config — never hardcoded). The funky body still AUTO-DETECTS the content indent, so a doc-root
4756+
// scalar with an INDENTED body releases at its dedent too; only a genuinely column-0 body (indent
4757+
// 0) runs to the marker. Ranked ABOVE `${bsKey}` (scopeOrder) so it wins this column-0 case; its
4758+
// lookahead requires the introducer to be the FIRST value token (after an optional marker), so it
4759+
// never steals a `key: |` / `- |` / nested header (those keep their node-indent regions).
4760+
const docMarkers = blockScalar.documentMarkers;
4761+
if (docMarkers && docMarkers.length) {
4762+
const docMarkAlt = docMarkers.map(escapeRegex).join('|');
4763+
const docMark = `(?:${docMarkAlt})(?=[\\t ]|$)`;
4764+
repository[`${bsKey}-doc`] = {
4765+
begin: `^()(?=(?:${docMark}[\\t ]+)?${intro}[\\t ]*(?:#|$))`,
4766+
while: `\\G(?!${docMark})`,
4767+
patterns: [bsIntroRule(bsIndicator, bsContent), ...bsHeaderIncs],
4768+
};
4769+
topPatterns.push({ include: `#${bsKey}-doc` });
4770+
}
47444771
// Sequence entry whose mapping VALUE is the block scalar (`- a: |` … ` b:`): bound siblings at the
47454772
// KEY column, not the dash column, else the next entry key is swallowed. The begin consumes the
47464773
// leading indent `\1` AND the dash + its trailing spaces `\3`, and the bound `\1[ \t]\3[ \t]` is
@@ -7268,10 +7295,16 @@ export function generateTmLanguage(grammar: CstGrammar, langName: string): TmGra
72687295
// • `blockscalar-key` (`?`-anchored) and the plain rule BOTH match `? |` (the plain VP admits a
72697296
// leading `?`), but the key variant scopes the body as the key NAME, so it must win → below
72707297
// the plain rule.
7271-
// • the plain `blockscalar` is the fallback (bare `|`, `key: |`, `--- |`).
7298+
// • `-doc` (document-root: bare `|` / `--- |` at column 0, no key/dash) and the plain rule BOTH
7299+
// match a column-0 header, but `-doc` bounds the body at the next document marker (so a
7300+
// column-0 body survives, instead of the plain rule's node-indent `while` ending it early), so
7301+
// `-doc` must be tried first → above the plain rule. It never collides with `-seq`/`-key`
7302+
// (those carry a leading `-`/`?` its lookahead forbids).
7303+
// • the plain `blockscalar` is the fallback (bare `|`, `key: |`, `--- |` with an indented body).
72727304
const bsRank = grammar.indent?.blockScalar?.token.toLowerCase();
72737305
if (bsRank && key === `${bsRank}-seq`) return 0.5;
72747306
if (key === 'blockscalar-key') return 0.55;
7307+
if (bsRank && key === `${bsRank}-doc`) return 0.58;
72757308
if (bsRank && key === bsRank) return 0.6;
72767309
// A flow collection (`{ … }` / `[ … ]`) is a begin/end region opened by a bracket; it must be
72777310
// tried before #punctuation (which would otherwise claim the `{`/`[` as a bare bracket) and

yaml.tmLanguage.json

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,9 @@
1717
{
1818
"include": "#blockscalar-key"
1919
},
20+
{
21+
"include": "#blockscalar-doc"
22+
},
2023
{
2124
"include": "#blockscalar"
2225
},
@@ -105,6 +108,9 @@
105108
{
106109
"include": "#blockscalar-key"
107110
},
111+
{
112+
"include": "#blockscalar-doc"
113+
},
108114
{
109115
"include": "#blockscalar"
110116
},
@@ -412,6 +418,91 @@
412418
"name": "variable.other.yaml",
413419
"match": "(?!)"
414420
},
421+
"blockscalar-doc": {
422+
"begin": "^()(?=(?:(?:---|\\.\\.\\.)(?=[\\t ]|$)[\\t ]+)?[|>](?:[1-9][-+]?|[-+][1-9]?|[-+])?[\\t ]*(?:#|$))",
423+
"while": "\\G(?!(?:---|\\.\\.\\.)(?=[\\t ]|$))",
424+
"patterns": [
425+
{
426+
"begin": "[\\t ]*([|>](?:[1-9][-+]?|[-+][1-9]?|[-+])?)(?=[\\t ]*(?:#|$))([\\t ]*.*)",
427+
"beginCaptures": {
428+
"1": {
429+
"name": "keyword.control.flow.block-scalar.yaml"
430+
},
431+
"2": {
432+
"patterns": [
433+
{
434+
"include": "#comment"
435+
}
436+
]
437+
}
438+
},
439+
"while": "\\G",
440+
"patterns": [
441+
{
442+
"begin": "$",
443+
"while": "\\G",
444+
"patterns": [
445+
{
446+
"begin": "\\G( ++)$",
447+
"while": "\\G(?>(\\1)$|(?!\\1)( *+)($|.))",
448+
"contentName": "string.unquoted.block.yaml"
449+
},
450+
{
451+
"begin": "\\G(?!$)(?=( *+))",
452+
"end": "\\G(?!\\1)(?=[\\t ]*+#)",
453+
"patterns": [
454+
{
455+
"begin": "\\G( *+)",
456+
"while": "\\G(?>(\\1)|( *+)($|[^\\t#]|[\\t ]++[^#]))",
457+
"contentName": "string.unquoted.block.yaml"
458+
}
459+
]
460+
},
461+
{
462+
"begin": "(?!\\G)(?=[\\t ]*+#)",
463+
"while": "\\G",
464+
"patterns": [
465+
{
466+
"include": "#comment"
467+
}
468+
]
469+
}
470+
]
471+
}
472+
]
473+
},
474+
{
475+
"include": "#docstart"
476+
},
477+
{
478+
"include": "#docend"
479+
},
480+
{
481+
"include": "#explicit-key"
482+
},
483+
{
484+
"include": "#explicit-key-indicator"
485+
},
486+
{
487+
"include": "#dquotekey"
488+
},
489+
{
490+
"include": "#squotekey"
491+
},
492+
{
493+
"include": "#key"
494+
},
495+
{
496+
"include": "#anchor"
497+
},
498+
{
499+
"include": "#tag"
500+
},
501+
{
502+
"include": "#punctuation"
503+
}
504+
]
505+
},
415506
"blockscalar-seq": {
416507
"begin": "^([ \\t]*)(-)([ \\t]+)(?=(?:[-?][\\t ]+)*[^\\n]*?:[\\t ]+(?:[&!][^\\t\\n\\f\\r \\[\\]{},]*[\\t ]+)*[|>](?:[1-9][-+]?|[-+][1-9]?|[-+])?[\\t ]*(?:#|$))",
417508
"beginCaptures": {

0 commit comments

Comments
 (0)