@tabnas/yaml 0.5.11 → 0.5.12

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/yaml.d.ts CHANGED
@@ -3,6 +3,6 @@ type YamlOptions = {
3
3
  meta?: boolean;
4
4
  };
5
5
  declare const Yaml: Plugin;
6
- declare const VERSION = "0.5.11";
6
+ declare const VERSION = "0.5.12";
7
7
  export { Yaml, VERSION, };
8
8
  export type { YamlOptions, };
package/dist/yaml.js CHANGED
@@ -51,8 +51,11 @@ const grammarText = `
51
51
  rule: indent: open: [
52
52
  # Key pair => map.
53
53
  { s: ['#KEY' '#CL'] p: map b: 2 g: yaml }
54
- # Element marker => list.
55
- { s: '#EL' p: list g: yaml }
54
+ # Element marker => block sequence. yamlBlockList, not jsonic's list:
55
+ # list reads a '[' as its own opening bracket, so a flow sequence as the
56
+ # first item (k:\\n - [1, 2]) became the block sequence itself, or
57
+ # failed at the next item (tabnas/yaml#88).
58
+ { s: '#EL' p: yamlBlockList g: yaml }
56
59
  # Flow collection as a block-mapping value on the FOLLOWING line:
57
60
  # required:
58
61
  # [a, b, c]
@@ -221,6 +224,13 @@ const grammarText = `
221
224
  }
222
225
  `;
223
226
  // --- END EMBEDDED yaml-grammar.jsonic ---
227
+ // The characters updateFlowState acts on, by char code: quotes, the escape
228
+ // backslash, the comment mark, flow brackets, and block scalar indicators.
229
+ // Every other character leaves its state unchanged, so it is skipped with
230
+ // one table read.
231
+ const FLOW_SCAN_CHARS = new Uint8Array(126);
232
+ for (const c of '"\'\\#{}[]|>')
233
+ FLOW_SCAN_CHARS[c.charCodeAt(0)] = 1;
224
234
  const Yaml = (tabnas, options) => {
225
235
  // Install once per instance: a second use(Yaml) on the same instance
226
236
  // returns here. options() does not re-run plugins, and make() builds a
@@ -277,19 +287,38 @@ const Yaml = (tabnas, options) => {
277
287
  // Incremental flow-depth cache for text.check (avoids O(n²) rescan).
278
288
  let _flowDepth = 0;
279
289
  let _flowScanPos = 0;
290
+ let _flowScanUpTo = 0;
280
291
  // Persistent quote state so multi-call scans handle quotes spanning slices.
281
292
  let _inSingleQuote = false;
282
293
  let _inDoubleQuote = false;
283
294
  // Bring _flowDepth up to date with `upTo` by scanning lex.src incrementally.
284
- // Skips quoted regions so embedded brackets don't mis-count the flow depth.
295
+ //
296
+ // The scan has to read the source the way the lexer does, or text is
297
+ // counted as syntax. A quoted region is skipped, and so are the regions
298
+ // where a bracket or a quote is ordinary text (tabnas/yaml#89):
299
+ // - a comment (`#` at the start of a line or after whitespace);
300
+ // - a block scalar's content lines (`|` / `>`);
301
+ // - in block context, any `{` `[` `"` `'` that does not begin a node:
302
+ // the `{` in `f({` or the `"` in `say "hi` is inside a plain scalar.
303
+ // Counting those left the depth above zero, so the rest of the document
304
+ // was read as flow (a `{` failed the parse), or left a quote open, so a
305
+ // later `[1, 2]` was read as the one string "1, 2]".
306
+ // The scan may run past `upTo` (to the end of a comment or a block
307
+ // scalar), since the lexer never stops inside either.
285
308
  function updateFlowState(src, upTo) {
286
- if (upTo < _flowScanPos) {
309
+ if (upTo < _flowScanUpTo) {
287
310
  _flowDepth = 0;
288
311
  _flowScanPos = 0;
289
312
  _inSingleQuote = false;
290
313
  _inDoubleQuote = false;
291
314
  }
292
- for (let fi = _flowScanPos; fi < upTo; fi++) {
315
+ _flowScanUpTo = upTo;
316
+ let fi = _flowScanPos;
317
+ for (; fi < upTo; fi++) {
318
+ // Fast path: only these ten characters matter in any state.
319
+ let cc = src.charCodeAt(fi);
320
+ if (cc > 125 || 0 === FLOW_SCAN_CHARS[cc])
321
+ continue;
293
322
  let fc = src[fi];
294
323
  if (_inDoubleQuote) {
295
324
  if (fc === '\\')
@@ -307,23 +336,265 @@ const Yaml = (tabnas, options) => {
307
336
  }
308
337
  continue;
309
338
  }
310
- if (fc === '{' || fc === '[')
311
- _flowDepth++;
312
- else if (fc === '}' || fc === ']') {
313
- if (_flowDepth > 0)
314
- _flowDepth--;
339
+ if (fc === '#' && (fi === 0 || isYamlSpace(src[fi - 1]))) {
340
+ while (fi + 1 < src.length && src[fi + 1] !== '\n' && src[fi + 1] !== '\r')
341
+ fi++;
342
+ continue;
315
343
  }
316
- else if (fc === '"') {
317
- _inDoubleQuote = true;
344
+ if (_flowDepth > 0) {
345
+ if (fc === '{' || fc === '[')
346
+ _flowDepth++;
347
+ else if (fc === '}' || fc === ']')
348
+ _flowDepth--;
349
+ else if (fc === '"')
350
+ _inDoubleQuote = true;
351
+ else if (fc === "'") {
352
+ let pc = fi > 0 ? src.charCodeAt(fi - 1) : 0;
353
+ if (!((pc >= 65 && pc <= 90) || (pc >= 97 && pc <= 122) || (pc >= 48 && pc <= 57))) {
354
+ _inSingleQuote = true;
355
+ }
356
+ }
357
+ continue;
318
358
  }
319
- else if (fc === "'") {
320
- let pc = fi > 0 ? src.charCodeAt(fi - 1) : 0;
321
- if (!((pc >= 65 && pc <= 90) || (pc >= 97 && pc <= 122) || (pc >= 48 && pc <= 57))) {
359
+ // Block context: only a node's first character opens anything.
360
+ if (fc === '{' || fc === '[' || fc === '"' || fc === "'" ||
361
+ fc === '|' || fc === '>') {
362
+ if (!startsYamlNode(src, fi)) {
363
+ // Inside a plain scalar, which nothing can open until it ends at
364
+ // `: `, ` #` or the end of the line: skip to there.
365
+ fi = plainScalarRest(src, fi) - 1;
366
+ }
367
+ else if (fc === '{' || fc === '[')
368
+ _flowDepth++;
369
+ else if (fc === '"')
370
+ _inDoubleQuote = true;
371
+ else if (fc === "'")
322
372
  _inSingleQuote = true;
373
+ else {
374
+ let end = blockScalarEnd(src, fi);
375
+ if (end > fi)
376
+ fi = end - 1;
377
+ }
378
+ }
379
+ }
380
+ _flowScanPos = fi;
381
+ }
382
+ // Whether a quoted scalar opens at `i` and is a block mapping key: its
383
+ // closing quote is followed, on the same line, by `:` and then whitespace
384
+ // or the end of the line.
385
+ function quotedKeyAt(src, i) {
386
+ let q = src[i];
387
+ let j = i + 1;
388
+ while (j < src.length && src[j] !== '\n' && src[j] !== '\r') {
389
+ if (q === '"' && src[j] === '\\') {
390
+ j += 2;
391
+ continue;
392
+ }
393
+ if (src[j] === q) {
394
+ if (q === "'" && src[j + 1] === "'") {
395
+ j += 2;
396
+ continue;
397
+ }
398
+ break;
399
+ }
400
+ j++;
401
+ }
402
+ if (src[j] !== q)
403
+ return false;
404
+ j++;
405
+ while (src[j] === ' ' || src[j] === '\t')
406
+ j++;
407
+ return src[j] === ':' && (j + 1 >= src.length || isYamlSpace(src[j + 1]));
408
+ }
409
+ // `text` with its quotes removed when the whole of it is one quoted
410
+ // scalar on one line; otherwise `text` unchanged. Double quotes take the
411
+ // common escapes; single quotes take '' for a quote.
412
+ function unquoteWholeScalar(text) {
413
+ let q = text[0];
414
+ if ((q !== '"' && q !== "'") || text.length < 2 || text[text.length - 1] !== q) {
415
+ return text;
416
+ }
417
+ let out = '';
418
+ for (let j = 1; j < text.length - 1; j++) {
419
+ let c = text[j];
420
+ if (q === "'" && c === "'") {
421
+ if (text[j + 1] !== "'" || j + 1 === text.length - 1)
422
+ return text;
423
+ out += "'";
424
+ j++;
425
+ }
426
+ else if (q === '"' && c === '"') {
427
+ return text;
428
+ }
429
+ else if (q === '"' && c === '\\') {
430
+ let e = text[++j];
431
+ let m = { n: '\n', t: '\t', r: '\r', '0': '\0', '"': '"', '\\': '\\', '/': '/', ' ': ' ' };
432
+ if (j >= text.length - 1 || !(e in m))
433
+ return text;
434
+ out += m[e];
435
+ }
436
+ else {
437
+ out += c;
438
+ }
439
+ }
440
+ return out;
441
+ }
442
+ function isYamlSpace(c) {
443
+ return c === ' ' || c === '\t' || c === '\n' || c === '\r';
444
+ }
445
+ // Whether the character at `i` (block context) is the first character of
446
+ // a node: first on its line, or after a `: ` / `- ` / `? ` indicator, a
447
+ // `---` marker, or a node property (`&anchor`, `!tag`) that itself starts
448
+ // a node. Anything else is inside a plain scalar.
449
+ function startsYamlNode(src, i) {
450
+ // Char codes throughout: this runs for every bracket and quote in block
451
+ // text, and indexing a string allocates a one-character string.
452
+ let p = i - 1;
453
+ let c = p >= 0 ? src.charCodeAt(p) : -1;
454
+ if (c === 10 || c === 13 || c < 0)
455
+ return true;
456
+ if (c !== 32 && c !== 9)
457
+ return false;
458
+ while (c === 32 || c === 9)
459
+ c = --p >= 0 ? src.charCodeAt(p) : -1;
460
+ if (c === 10 || c === 13 || c < 0)
461
+ return true;
462
+ if (c === 58)
463
+ return true; // `:`
464
+ if (c === 45 || c === 63) { // `-` / `?`
465
+ if (c === 45 && src.charCodeAt(p - 1) === 45 && src.charCodeAt(p - 2) === 45 &&
466
+ (p - 3 < 0 || src.charCodeAt(p - 3) === 10 || src.charCodeAt(p - 3) === 13))
467
+ return true;
468
+ return startsYamlNode(src, p);
469
+ }
470
+ let w = p;
471
+ while (w > 0 && !isYamlSpaceCode(src.charCodeAt(w - 1)))
472
+ w--;
473
+ c = src.charCodeAt(w);
474
+ if (c === 38 || c === 33)
475
+ return startsYamlNode(src, w); // `&` / `!`
476
+ return false;
477
+ }
478
+ // From `i` inside a block-context plain scalar, the offset where it can
479
+ // end: the next line break, a `:` followed by whitespace or the end, or a
480
+ // `#` after whitespace.
481
+ function plainScalarRest(src, i) {
482
+ let j = i + 1;
483
+ for (; j < src.length; j++) {
484
+ let c = src.charCodeAt(j);
485
+ if (c === 10 || c === 13)
486
+ break;
487
+ if (c === 58) { // `:`
488
+ let n = j + 1 < src.length ? src.charCodeAt(j + 1) : -1;
489
+ if (n < 0 || isYamlSpaceCode(n))
490
+ break;
491
+ }
492
+ else if (c === 35) { // `#`
493
+ let b = src.charCodeAt(j - 1);
494
+ if (b === 32 || b === 9)
495
+ break;
496
+ }
497
+ }
498
+ return j;
499
+ }
500
+ function isYamlSpaceCode(c) {
501
+ return c === 32 || c === 9 || c === 10 || c === 13;
502
+ }
503
+ // A block scalar indicator (`|` or `>`, optional chomping/indentation
504
+ // indicators, then only a comment) at `i`: the offset just past its content
505
+ // lines, or `i` when it is not one. Content runs while lines are blank or
506
+ // indented at least as far as the first content line, which must be deeper
507
+ // than the indicator's line (column 0 is allowed for a document's root).
508
+ function blockScalarEnd(src, i) {
509
+ let j = i + 1;
510
+ let explicit = 0;
511
+ for (let k = 0; k < 2; k++) {
512
+ if (src[j] === '+' || src[j] === '-')
513
+ j++;
514
+ else if (src[j] >= '1' && src[j] <= '9') {
515
+ explicit = +src[j];
516
+ j++;
517
+ }
518
+ }
519
+ while (src[j] === ' ' || src[j] === '\t')
520
+ j++;
521
+ if (src[j] === '#') {
522
+ while (j < src.length && src[j] !== '\n' && src[j] !== '\r')
523
+ j++;
524
+ }
525
+ if (j < src.length && src[j] !== '\n' && src[j] !== '\r')
526
+ return i;
527
+ let ls = i;
528
+ while (ls > 0 && src[ls - 1] !== '\n' && src[ls - 1] !== '\r')
529
+ ls--;
530
+ let lineIndent = 0;
531
+ while (src[ls + lineIndent] === ' ')
532
+ lineIndent++;
533
+ let isRoot = ls + lineIndent === i ||
534
+ (src[ls] === '-' && src[ls + 1] === '-' && src[ls + 2] === '-');
535
+ let parentIndent = isRoot && lineIndent === 0 ? -1 : lineIndent;
536
+ let pos = j;
537
+ if (src[pos] === '\r')
538
+ pos++;
539
+ if (src[pos] === '\n')
540
+ pos++;
541
+ let blockIndent = -1;
542
+ if (explicit > 0) {
543
+ // As the block scalar handler does: after a key on the same line
544
+ // (`- a: |2`), the indent the indicator counts from includes each
545
+ // `- ` before the key, not only the line's leading spaces.
546
+ let base = Math.max(parentIndent, 0);
547
+ let hasColon = false;
548
+ for (let ci = ls + lineIndent; ci < i; ci++) {
549
+ if (src[ci] === ':' && (src[ci + 1] === ' ' || src[ci + 1] === '\t')) {
550
+ hasColon = true;
551
+ break;
323
552
  }
324
553
  }
554
+ if (hasColon) {
555
+ let si = ls + lineIndent;
556
+ while (si < i && src[si] === '-' && (src[si + 1] === ' ' || src[si + 1] === '\t')) {
557
+ base += 2;
558
+ si += 2;
559
+ while (si < i && src[si] === ' ') {
560
+ base++;
561
+ si++;
562
+ }
563
+ }
564
+ }
565
+ blockIndent = base + explicit;
566
+ }
567
+ let end = pos;
568
+ while (pos < src.length) {
569
+ let n = 0;
570
+ while (src[pos + n] === ' ')
571
+ n++;
572
+ let c = src[pos + n];
573
+ let lineEnd = pos + n;
574
+ while (lineEnd < src.length && src[lineEnd] !== '\n' && src[lineEnd] !== '\r')
575
+ lineEnd++;
576
+ let next = lineEnd;
577
+ if (src[next] === '\r')
578
+ next++;
579
+ if (src[next] === '\n')
580
+ next++;
581
+ if (c === undefined || c === '\n' || c === '\r') {
582
+ pos = next;
583
+ continue;
584
+ }
585
+ if (blockIndent < 0) {
586
+ if (n <= parentIndent)
587
+ break;
588
+ blockIndent = n;
589
+ }
590
+ if (n < blockIndent)
591
+ break;
592
+ if (n === 0 && (src.startsWith('---', pos) || src.startsWith('...', pos)))
593
+ break;
594
+ end = lineEnd;
595
+ pos = next;
325
596
  }
326
- _flowScanPos = upTo;
597
+ return end;
327
598
  }
328
599
  tabnas.options({
329
600
  fixed: {
@@ -1085,6 +1356,7 @@ const Yaml = (tabnas, options) => {
1085
1356
  yamlStreamCurMeta = null;
1086
1357
  _flowDepth = 0;
1087
1358
  _flowScanPos = 0;
1359
+ _flowScanUpTo = 0;
1088
1360
  _inSingleQuote = false;
1089
1361
  _inDoubleQuote = false;
1090
1362
  // Empty / whitespace-only / comments-only source: emit one
@@ -1631,6 +1903,9 @@ const Yaml = (tabnas, options) => {
1631
1903
  explicitKeyTag = tagMatch[1];
1632
1904
  key = tagMatch[2];
1633
1905
  }
1906
+ // A quoted explicit key is the scalar inside the quotes, not
1907
+ // the quoted text (`? "k"` is the key k).
1908
+ key = unquoteWholeScalar(key);
1634
1909
  let consumed = keyEnd;
1635
1910
  // Track position before consuming newline (for !hasValue case).
1636
1911
  let beforeNewline = consumed;
@@ -2149,7 +2424,10 @@ const Yaml = (tabnas, options) => {
2149
2424
  // trailing commas (e.g. 12,), or non-numeric text after a space
2150
2425
  // (e.g. "64 characters, hexadecimal.") must be captured before
2151
2426
  // jsonic's number matcher grabs just the digits.
2152
- if (fwd[0] >= '0' && fwd[0] <= '9') {
2427
+ // A sign before the digit counts too: `+190:20:30` is one plain
2428
+ // scalar, where the number matcher would take `+190`.
2429
+ if ((fwd[0] >= '0' && fwd[0] <= '9') ||
2430
+ ((fwd[0] === '+' || fwd[0] === '-') && fwd[1] >= '0' && fwd[1] <= '9')) {
2153
2431
  updateFlowState(lex.src, pnt.sI);
2154
2432
  let inFlow = _flowDepth > 0;
2155
2433
  let hasEmbeddedColon = false;
@@ -2157,10 +2435,16 @@ const Yaml = (tabnas, options) => {
2157
2435
  let hasBlockComma = false;
2158
2436
  let pi = 1;
2159
2437
  while (pi < fwd.length && fwd[pi] !== '\n' && fwd[pi] !== '\r') {
2438
+ // An embedded colon (20:03:20) does not end the scan: the
2439
+ // scalar can go on past a space ("09:00 AM"), and only the
2440
+ // trailing-text branch below reads that as one scalar.
2441
+ // Stopping here took "09:00" and left "AM" to fail the
2442
+ // parse (tabnas/yaml#90).
2160
2443
  if (fwd[pi] === ':' && fwd[pi + 1] !== ' ' && fwd[pi + 1] !== '\t' &&
2161
2444
  fwd[pi + 1] !== '\n' && fwd[pi + 1] !== '\r' && fwd[pi + 1] !== undefined) {
2162
2445
  hasEmbeddedColon = true;
2163
- break;
2446
+ pi++;
2447
+ continue;
2164
2448
  }
2165
2449
  // A COMMA IS NOT A SEPARATOR IN BLOCK CONTEXT.
2166
2450
  //
@@ -2219,10 +2503,13 @@ const Yaml = (tabnas, options) => {
2219
2503
  return null;
2220
2504
  }
2221
2505
  if (hasEmbeddedColon || hasBlockComma) {
2222
- // Scan to end of plain scalar token (space, tab, newline, eof).
2506
+ // Scan to end of plain scalar token (space, tab, newline,
2507
+ // eof, or in a flow collection its `,` `]` `}`: `[1, 12:30]`
2508
+ // holds "12:30", not "12:30,").
2223
2509
  let end = 0;
2224
2510
  while (end < fwd.length && fwd[end] !== ' ' && fwd[end] !== '\t' &&
2225
- fwd[end] !== '\n' && fwd[end] !== '\r')
2511
+ fwd[end] !== '\n' && fwd[end] !== '\r' &&
2512
+ !(inFlow && (fwd[end] === ',' || fwd[end] === ']' || fwd[end] === '}')))
2226
2513
  end++;
2227
2514
  let text = fwd.substring(0, end);
2228
2515
  let tkn = lex.token('#TX', text, text, lex.pnt);
@@ -2488,9 +2775,14 @@ const Yaml = (tabnas, options) => {
2488
2775
  // Skip #IN when the next content is a flow indicator or quoted
2489
2776
  // string at column 0 — there's no block to indent into. Match
2490
2777
  // the previous behavior of the inline `--- foo` handler.
2778
+ // A quoted KEY at column 0 is different: it continues (or
2779
+ // closes back to) the root block mapping, so it needs its #IN
2780
+ // like a plain key does. Without it a block sequence ending
2781
+ // just before it swallowed the pair (tabnas/yaml#86).
2491
2782
  if (spaces === 0 &&
2492
2783
  (fwd[pos] === '{' || fwd[pos] === '[' ||
2493
- fwd[pos] === '"' || fwd[pos] === "'")) {
2784
+ ((fwd[pos] === '"' || fwd[pos] === "'") &&
2785
+ !quotedKeyAt(fwd, pos)))) {
2494
2786
  pnt.sI += pos;
2495
2787
  pnt.rI += rows;
2496
2788
  pnt.cI = 0;
@@ -2858,6 +3150,6 @@ Yaml.defaults = {
2858
3150
  // VERSION is this package's version. It MUST equal package.json "version":
2859
3151
  // the release orchestrator rewrites both, and test/version.test.ts fails the
2860
3152
  // build if they drift. Mirrors `const VERSION` in go/yaml.go.
2861
- const VERSION = '0.5.11';
3153
+ const VERSION = '0.5.12';
2862
3154
  exports.VERSION = VERSION;
2863
3155
  //# sourceMappingURL=yaml.js.map