@tabnas/yaml 0.5.11 → 0.5.13
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 +1 -1
- package/dist/yaml.js +378 -37
- package/dist/yaml.js.map +1 -1
- package/package.json +3 -2
- package/src/yaml.ts +323 -31
package/src/yaml.ts
CHANGED
|
@@ -77,8 +77,11 @@ const grammarText = `
|
|
|
77
77
|
rule: indent: open: [
|
|
78
78
|
# Key pair => map.
|
|
79
79
|
{ s: ['#KEY' '#CL'] p: map b: 2 g: yaml }
|
|
80
|
-
# Element marker => list
|
|
81
|
-
|
|
80
|
+
# Element marker => block sequence. yamlBlockList, not jsonic's list:
|
|
81
|
+
# list reads a '[' as its own opening bracket, so a flow sequence as the
|
|
82
|
+
# first item (k:\\n - [1, 2]) became the block sequence itself, or
|
|
83
|
+
# failed at the next item (tabnas/yaml#88).
|
|
84
|
+
{ s: '#EL' p: yamlBlockList g: yaml }
|
|
82
85
|
# Flow collection as a block-mapping value on the FOLLOWING line:
|
|
83
86
|
# required:
|
|
84
87
|
# [a, b, c]
|
|
@@ -249,6 +252,37 @@ const grammarText = `
|
|
|
249
252
|
// --- END EMBEDDED yaml-grammar.jsonic ---
|
|
250
253
|
|
|
251
254
|
|
|
255
|
+
// The characters updateFlowState acts on, by char code: quotes, the escape
|
|
256
|
+
// backslash, the comment mark, flow brackets, and block scalar indicators.
|
|
257
|
+
// Every other character leaves its state unchanged, so it is skipped with
|
|
258
|
+
// one table read.
|
|
259
|
+
const FLOW_SCAN_CHARS = new Uint8Array(126)
|
|
260
|
+
for (const c of '"\'\\#{}[]|>') FLOW_SCAN_CHARS[c.charCodeAt(0)] = 1
|
|
261
|
+
|
|
262
|
+
|
|
263
|
+
// The value a `!!int` tag gives its text: YAML's core-schema hex (0x1f)
|
|
264
|
+
// and octal (0o17) forms, then a decimal parse. A text no form reads
|
|
265
|
+
// stays NaN, as before: the tag applies even when its value does not.
|
|
266
|
+
function yamlTagInt(raw: string): number {
|
|
267
|
+
let m = raw.match(/^([-+]?)0x([0-9a-fA-F]+)$/) || raw.match(/^([-+]?)0o([0-7]+)$/)
|
|
268
|
+
if (m) {
|
|
269
|
+
let n = parseInt(m[2], raw.includes('0x') ? 16 : 8)
|
|
270
|
+
return '-' === m[1] ? -n : n
|
|
271
|
+
}
|
|
272
|
+
return parseInt(raw, 10)
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
// The value a `!!float` tag gives its text: the core schema's infinities
|
|
276
|
+
// and not-a-number (.inf, -.inf, +.inf, .nan in three casings), then a
|
|
277
|
+
// decimal parse.
|
|
278
|
+
function yamlTagFloat(raw: string): number {
|
|
279
|
+
let m = raw.match(/^([-+]?)\.(inf|Inf|INF)$/)
|
|
280
|
+
if (m) return '-' === m[1] ? -Infinity : Infinity
|
|
281
|
+
if (/^\.(nan|NaN|NAN)$/.test(raw)) return NaN
|
|
282
|
+
return parseFloat(raw)
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
|
|
252
286
|
const Yaml: Plugin = (tabnas: Tabnas, options: YamlOptions) => {
|
|
253
287
|
// Install once per instance: a second use(Yaml) on the same instance
|
|
254
288
|
// returns here. options() does not re-run plugins, and make() builds a
|
|
@@ -308,18 +342,36 @@ const Yaml: Plugin = (tabnas: Tabnas, options: YamlOptions) => {
|
|
|
308
342
|
// Incremental flow-depth cache for text.check (avoids O(n²) rescan).
|
|
309
343
|
let _flowDepth = 0
|
|
310
344
|
let _flowScanPos = 0
|
|
345
|
+
let _flowScanUpTo = 0
|
|
311
346
|
// Persistent quote state so multi-call scans handle quotes spanning slices.
|
|
312
347
|
let _inSingleQuote = false
|
|
313
348
|
let _inDoubleQuote = false
|
|
314
349
|
|
|
315
350
|
// Bring _flowDepth up to date with `upTo` by scanning lex.src incrementally.
|
|
316
|
-
//
|
|
351
|
+
//
|
|
352
|
+
// The scan has to read the source the way the lexer does, or text is
|
|
353
|
+
// counted as syntax. A quoted region is skipped, and so are the regions
|
|
354
|
+
// where a bracket or a quote is ordinary text (tabnas/yaml#89):
|
|
355
|
+
// - a comment (`#` at the start of a line or after whitespace);
|
|
356
|
+
// - a block scalar's content lines (`|` / `>`);
|
|
357
|
+
// - in block context, any `{` `[` `"` `'` that does not begin a node:
|
|
358
|
+
// the `{` in `f({` or the `"` in `say "hi` is inside a plain scalar.
|
|
359
|
+
// Counting those left the depth above zero, so the rest of the document
|
|
360
|
+
// was read as flow (a `{` failed the parse), or left a quote open, so a
|
|
361
|
+
// later `[1, 2]` was read as the one string "1, 2]".
|
|
362
|
+
// The scan may run past `upTo` (to the end of a comment or a block
|
|
363
|
+
// scalar), since the lexer never stops inside either.
|
|
317
364
|
function updateFlowState(src: string, upTo: number) {
|
|
318
|
-
if (upTo <
|
|
365
|
+
if (upTo < _flowScanUpTo) {
|
|
319
366
|
_flowDepth = 0; _flowScanPos = 0
|
|
320
367
|
_inSingleQuote = false; _inDoubleQuote = false
|
|
321
368
|
}
|
|
322
|
-
|
|
369
|
+
_flowScanUpTo = upTo
|
|
370
|
+
let fi = _flowScanPos
|
|
371
|
+
for (; fi < upTo; fi++) {
|
|
372
|
+
// Fast path: only these ten characters matter in any state.
|
|
373
|
+
let cc = src.charCodeAt(fi)
|
|
374
|
+
if (cc > 125 || 0 === FLOW_SCAN_CHARS[cc]) continue
|
|
323
375
|
let fc = src[fi]
|
|
324
376
|
if (_inDoubleQuote) {
|
|
325
377
|
if (fc === '\\') fi++
|
|
@@ -333,17 +385,222 @@ const Yaml: Plugin = (tabnas: Tabnas, options: YamlOptions) => {
|
|
|
333
385
|
}
|
|
334
386
|
continue
|
|
335
387
|
}
|
|
336
|
-
if (fc === '
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
if (
|
|
342
|
-
|
|
388
|
+
if (fc === '#' && (fi === 0 || isYamlSpace(src[fi - 1]))) {
|
|
389
|
+
while (fi + 1 < src.length && src[fi + 1] !== '\n' && src[fi + 1] !== '\r') fi++
|
|
390
|
+
continue
|
|
391
|
+
}
|
|
392
|
+
if (_flowDepth > 0) {
|
|
393
|
+
if (fc === '{' || fc === '[') _flowDepth++
|
|
394
|
+
else if (fc === '}' || fc === ']') _flowDepth--
|
|
395
|
+
else if (fc === '"') _inDoubleQuote = true
|
|
396
|
+
else if (fc === "'") {
|
|
397
|
+
let pc = fi > 0 ? src.charCodeAt(fi - 1) : 0
|
|
398
|
+
if (!((pc >= 65 && pc <= 90) || (pc >= 97 && pc <= 122) || (pc >= 48 && pc <= 57))) {
|
|
399
|
+
_inSingleQuote = true
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
continue
|
|
403
|
+
}
|
|
404
|
+
// Block context: only a node's first character opens anything.
|
|
405
|
+
if (fc === '{' || fc === '[' || fc === '"' || fc === "'" ||
|
|
406
|
+
fc === '|' || fc === '>') {
|
|
407
|
+
if (!startsYamlNode(src, fi)) {
|
|
408
|
+
// Inside a plain scalar, which nothing can open until it ends at
|
|
409
|
+
// `: `, ` #` or the end of the line: skip to there.
|
|
410
|
+
fi = plainScalarRest(src, fi) - 1
|
|
411
|
+
}
|
|
412
|
+
else if (fc === '{' || fc === '[') _flowDepth++
|
|
413
|
+
else if (fc === '"') _inDoubleQuote = true
|
|
414
|
+
else if (fc === "'") _inSingleQuote = true
|
|
415
|
+
else {
|
|
416
|
+
let end = blockScalarEnd(src, fi)
|
|
417
|
+
if (end > fi) fi = end - 1
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
_flowScanPos = fi
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
// Whether a quoted scalar opens at `i` and is a block mapping key: its
|
|
425
|
+
// closing quote is followed, on the same line, by `:` and then whitespace
|
|
426
|
+
// or the end of the line.
|
|
427
|
+
function quotedKeyAt(src: string, i: number): boolean {
|
|
428
|
+
let q = src[i]
|
|
429
|
+
let j = i + 1
|
|
430
|
+
while (j < src.length && src[j] !== '\n' && src[j] !== '\r') {
|
|
431
|
+
if (q === '"' && src[j] === '\\') { j += 2; continue }
|
|
432
|
+
if (src[j] === q) {
|
|
433
|
+
if (q === "'" && src[j + 1] === "'") { j += 2; continue }
|
|
434
|
+
break
|
|
435
|
+
}
|
|
436
|
+
j++
|
|
437
|
+
}
|
|
438
|
+
if (src[j] !== q) return false
|
|
439
|
+
j++
|
|
440
|
+
while (src[j] === ' ' || src[j] === '\t') j++
|
|
441
|
+
return src[j] === ':' && (j + 1 >= src.length || isYamlSpace(src[j + 1]))
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
// `text` with its quotes removed when the whole of it is one quoted
|
|
445
|
+
// scalar on one line; otherwise `text` unchanged. Double quotes take the
|
|
446
|
+
// common escapes; single quotes take '' for a quote.
|
|
447
|
+
function unquoteWholeScalar(text: string): string {
|
|
448
|
+
let q = text[0]
|
|
449
|
+
if ((q !== '"' && q !== "'") || text.length < 2 || text[text.length - 1] !== q) {
|
|
450
|
+
return text
|
|
451
|
+
}
|
|
452
|
+
let out = ''
|
|
453
|
+
for (let j = 1; j < text.length - 1; j++) {
|
|
454
|
+
let c = text[j]
|
|
455
|
+
if (q === "'" && c === "'") {
|
|
456
|
+
if (text[j + 1] !== "'" || j + 1 === text.length - 1) return text
|
|
457
|
+
out += "'"; j++
|
|
458
|
+
} else if (q === '"' && c === '"') {
|
|
459
|
+
return text
|
|
460
|
+
} else if (q === '"' && c === '\\') {
|
|
461
|
+
let e = text[++j]
|
|
462
|
+
let m: Record<string, string> =
|
|
463
|
+
{ n: '\n', t: '\t', r: '\r', '0': '\0', '"': '"', '\\': '\\', '/': '/', ' ': ' ' }
|
|
464
|
+
if (j >= text.length - 1 || !(e in m)) return text
|
|
465
|
+
out += m[e]
|
|
466
|
+
} else {
|
|
467
|
+
out += c
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
return out
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
// Whether offset `i` begins a line: the source start, just after a line
|
|
474
|
+
// break, or just after the byte order mark that opens the stream.
|
|
475
|
+
function atLineStart(src: string, i: number): boolean {
|
|
476
|
+
return 0 === i || src[i - 1] === '\n' || src[i - 1] === '\r' ||
|
|
477
|
+
(1 === i && 0xFEFF === src.charCodeAt(0))
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
function isYamlSpace(c: string | undefined): boolean {
|
|
481
|
+
return c === ' ' || c === '\t' || c === '\n' || c === '\r'
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
// Whether the character at `i` (block context) is the first character of
|
|
485
|
+
// a node: first on its line, or after a `: ` / `- ` / `? ` indicator, a
|
|
486
|
+
// `---` marker, or a node property (`&anchor`, `!tag`) that itself starts
|
|
487
|
+
// a node. Anything else is inside a plain scalar.
|
|
488
|
+
function startsYamlNode(src: string, i: number): boolean {
|
|
489
|
+
// Char codes throughout: this runs for every bracket and quote in block
|
|
490
|
+
// text, and indexing a string allocates a one-character string. A byte
|
|
491
|
+
// order mark opening the stream counts as a line start.
|
|
492
|
+
let p = i - 1
|
|
493
|
+
let c = p >= 0 ? src.charCodeAt(p) : -1
|
|
494
|
+
if (c === 10 || c === 13 || c < 0 || (0 === p && 0xFEFF === c)) return true
|
|
495
|
+
if (c !== 32 && c !== 9) return false
|
|
496
|
+
while (c === 32 || c === 9) c = --p >= 0 ? src.charCodeAt(p) : -1
|
|
497
|
+
if (c === 10 || c === 13 || c < 0 || (0 === p && 0xFEFF === c)) return true
|
|
498
|
+
if (c === 58) return true // `:`
|
|
499
|
+
if (c === 45 || c === 63) { // `-` / `?`
|
|
500
|
+
if (c === 45 && src.charCodeAt(p - 1) === 45 && src.charCodeAt(p - 2) === 45 &&
|
|
501
|
+
(p - 3 < 0 || src.charCodeAt(p - 3) === 10 || src.charCodeAt(p - 3) === 13)) return true
|
|
502
|
+
return startsYamlNode(src, p)
|
|
503
|
+
}
|
|
504
|
+
let w = p
|
|
505
|
+
while (w > 0 && !isYamlSpaceCode(src.charCodeAt(w - 1))) w--
|
|
506
|
+
c = src.charCodeAt(w)
|
|
507
|
+
if (c === 38 || c === 33) return startsYamlNode(src, w) // `&` / `!`
|
|
508
|
+
return false
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
// From `i` inside a block-context plain scalar, the offset where it can
|
|
512
|
+
// end: the next line break, a `:` followed by whitespace or the end, or a
|
|
513
|
+
// `#` after whitespace.
|
|
514
|
+
function plainScalarRest(src: string, i: number): number {
|
|
515
|
+
let j = i + 1
|
|
516
|
+
for (; j < src.length; j++) {
|
|
517
|
+
let c = src.charCodeAt(j)
|
|
518
|
+
if (c === 10 || c === 13) break
|
|
519
|
+
if (c === 58) { // `:`
|
|
520
|
+
let n = j + 1 < src.length ? src.charCodeAt(j + 1) : -1
|
|
521
|
+
if (n < 0 || isYamlSpaceCode(n)) break
|
|
522
|
+
}
|
|
523
|
+
else if (c === 35) { // `#`
|
|
524
|
+
let b = src.charCodeAt(j - 1)
|
|
525
|
+
if (b === 32 || b === 9) break
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
return j
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
function isYamlSpaceCode(c: number): boolean {
|
|
532
|
+
return c === 32 || c === 9 || c === 10 || c === 13
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
// A block scalar indicator (`|` or `>`, optional chomping/indentation
|
|
536
|
+
// indicators, then only a comment) at `i`: the offset just past its content
|
|
537
|
+
// lines, or `i` when it is not one. Content runs while lines are blank or
|
|
538
|
+
// indented at least as far as the first content line, which must be deeper
|
|
539
|
+
// than the indicator's line (column 0 is allowed for a document's root).
|
|
540
|
+
function blockScalarEnd(src: string, i: number): number {
|
|
541
|
+
let j = i + 1
|
|
542
|
+
let explicit = 0
|
|
543
|
+
for (let k = 0; k < 2; k++) {
|
|
544
|
+
if (src[j] === '+' || src[j] === '-') j++
|
|
545
|
+
else if (src[j] >= '1' && src[j] <= '9') { explicit = +src[j]; j++ }
|
|
546
|
+
}
|
|
547
|
+
while (src[j] === ' ' || src[j] === '\t') j++
|
|
548
|
+
if (src[j] === '#') {
|
|
549
|
+
while (j < src.length && src[j] !== '\n' && src[j] !== '\r') j++
|
|
550
|
+
}
|
|
551
|
+
if (j < src.length && src[j] !== '\n' && src[j] !== '\r') return i
|
|
552
|
+
let ls = i
|
|
553
|
+
while (ls > 0 && src[ls - 1] !== '\n' && src[ls - 1] !== '\r') ls--
|
|
554
|
+
if (0 === ls && 0xFEFF === src.charCodeAt(0)) ls = 1
|
|
555
|
+
let lineIndent = 0
|
|
556
|
+
while (src[ls + lineIndent] === ' ') lineIndent++
|
|
557
|
+
let isRoot = ls + lineIndent === i ||
|
|
558
|
+
(src[ls] === '-' && src[ls + 1] === '-' && src[ls + 2] === '-')
|
|
559
|
+
let parentIndent = isRoot && lineIndent === 0 ? -1 : lineIndent
|
|
560
|
+
let pos = j
|
|
561
|
+
if (src[pos] === '\r') pos++
|
|
562
|
+
if (src[pos] === '\n') pos++
|
|
563
|
+
let blockIndent = -1
|
|
564
|
+
if (explicit > 0) {
|
|
565
|
+
// As the block scalar handler does: after a key on the same line
|
|
566
|
+
// (`- a: |2`), the indent the indicator counts from includes each
|
|
567
|
+
// `- ` before the key, not only the line's leading spaces.
|
|
568
|
+
let base = Math.max(parentIndent, 0)
|
|
569
|
+
let hasColon = false
|
|
570
|
+
for (let ci = ls + lineIndent; ci < i; ci++) {
|
|
571
|
+
if (src[ci] === ':' && (src[ci + 1] === ' ' || src[ci + 1] === '\t')) { hasColon = true; break }
|
|
572
|
+
}
|
|
573
|
+
if (hasColon) {
|
|
574
|
+
let si = ls + lineIndent
|
|
575
|
+
while (si < i && src[si] === '-' && (src[si + 1] === ' ' || src[si + 1] === '\t')) {
|
|
576
|
+
base += 2
|
|
577
|
+
si += 2
|
|
578
|
+
while (si < i && src[si] === ' ') { base++; si++ }
|
|
343
579
|
}
|
|
344
580
|
}
|
|
581
|
+
blockIndent = base + explicit
|
|
345
582
|
}
|
|
346
|
-
|
|
583
|
+
let end = pos
|
|
584
|
+
while (pos < src.length) {
|
|
585
|
+
let n = 0
|
|
586
|
+
while (src[pos + n] === ' ') n++
|
|
587
|
+
let c = src[pos + n]
|
|
588
|
+
let lineEnd = pos + n
|
|
589
|
+
while (lineEnd < src.length && src[lineEnd] !== '\n' && src[lineEnd] !== '\r') lineEnd++
|
|
590
|
+
let next = lineEnd
|
|
591
|
+
if (src[next] === '\r') next++
|
|
592
|
+
if (src[next] === '\n') next++
|
|
593
|
+
if (c === undefined || c === '\n' || c === '\r') { pos = next; continue }
|
|
594
|
+
if (blockIndent < 0) {
|
|
595
|
+
if (n <= parentIndent) break
|
|
596
|
+
blockIndent = n
|
|
597
|
+
}
|
|
598
|
+
if (n < blockIndent) break
|
|
599
|
+
if (n === 0 && (src.startsWith('---', pos) || src.startsWith('...', pos))) break
|
|
600
|
+
end = lineEnd
|
|
601
|
+
pos = next
|
|
602
|
+
}
|
|
603
|
+
return end
|
|
347
604
|
}
|
|
348
605
|
|
|
349
606
|
|
|
@@ -446,6 +703,7 @@ const Yaml: Plugin = (tabnas: Tabnas, options: YamlOptions) => {
|
|
|
446
703
|
// containingIndent and the --- check.
|
|
447
704
|
let li = pnt.sI
|
|
448
705
|
while (li > 0 && lex.src[li - 1] !== '\n' && lex.src[li - 1] !== '\r') li--
|
|
706
|
+
if (0 === li && 0xFEFF === lex.src.charCodeAt(0)) li = 1
|
|
449
707
|
let lineStart = li
|
|
450
708
|
while (li < pnt.sI && lex.src[li] === ' ') { containingIndent++; li++ }
|
|
451
709
|
// Check if this line starts with --- (document start marker).
|
|
@@ -728,7 +986,10 @@ const Yaml: Plugin = (tabnas: Tabnas, options: YamlOptions) => {
|
|
|
728
986
|
rawVal = fwd.substring(valStart + 1, valEnd - 1)
|
|
729
987
|
} else {
|
|
730
988
|
// Unquoted value — to end of line, stopping at `: ` and ` #`.
|
|
989
|
+
// A `#` straight after the tag's space starts a comment, so
|
|
990
|
+
// the value is empty (`!!str #c` is "").
|
|
731
991
|
while (valEnd < fwd.length && fwd[valEnd] !== '\n' && fwd[valEnd] !== '\r') {
|
|
992
|
+
if (valEnd === valStart && valStart > tagEnd && fwd[valEnd] === '#') break
|
|
732
993
|
if (fwd[valEnd] === ':' && (fwd[valEnd+1] === ' ' || fwd[valEnd+1] === '\n' ||
|
|
733
994
|
fwd[valEnd+1] === '\r' || fwd[valEnd+1] === undefined)) break
|
|
734
995
|
if (fwd[valEnd] === ' ' && fwd[valEnd+1] === '#') break
|
|
@@ -739,8 +1000,8 @@ const Yaml: Plugin = (tabnas: Tabnas, options: YamlOptions) => {
|
|
|
739
1000
|
// Apply tag conversion.
|
|
740
1001
|
let result: any
|
|
741
1002
|
if (tag === 'str') result = String(rawVal)
|
|
742
|
-
else if (tag === 'int') result =
|
|
743
|
-
else if (tag === 'float') result =
|
|
1003
|
+
else if (tag === 'int') result = yamlTagInt(rawVal)
|
|
1004
|
+
else if (tag === 'float') result = yamlTagFloat(rawVal)
|
|
744
1005
|
else if (tag === 'bool') result = rawVal === 'true' || rawVal === 'True' || rawVal === 'TRUE'
|
|
745
1006
|
else if (tag === 'null') result = null
|
|
746
1007
|
else result = rawVal // Unknown tag — keep as string.
|
|
@@ -778,6 +1039,7 @@ const Yaml: Plugin = (tabnas: Tabnas, options: YamlOptions) => {
|
|
|
778
1039
|
// Find key indent and determine context for multiline scalars.
|
|
779
1040
|
let lineStart = pnt.sI
|
|
780
1041
|
while (lineStart > 0 && lex.src[lineStart - 1] !== '\n' && lex.src[lineStart - 1] !== '\r') lineStart--
|
|
1042
|
+
if (0 === lineStart && 0xFEFF === lex.src.charCodeAt(0)) lineStart = 1
|
|
781
1043
|
|
|
782
1044
|
// Current line indent (indent of the line where text starts).
|
|
783
1045
|
let currentLineIndent = 0
|
|
@@ -951,6 +1213,7 @@ const Yaml: Plugin = (tabnas: Tabnas, options: YamlOptions) => {
|
|
|
951
1213
|
'on': true, 'On': true, 'ON': true,
|
|
952
1214
|
'off': false, 'Off': false, 'OFF': false,
|
|
953
1215
|
'.inf': Infinity, '.Inf': Infinity, '.INF': Infinity,
|
|
1216
|
+
'+.inf': Infinity, '+.Inf': Infinity, '+.INF': Infinity,
|
|
954
1217
|
'-.inf': -Infinity, '-.Inf': -Infinity, '-.INF': -Infinity,
|
|
955
1218
|
'.nan': NaN, '.NaN': NaN, '.NAN': NaN,
|
|
956
1219
|
}
|
|
@@ -1040,6 +1303,7 @@ const Yaml: Plugin = (tabnas: Tabnas, options: YamlOptions) => {
|
|
|
1040
1303
|
yamlStreamCurMeta = null
|
|
1041
1304
|
_flowDepth = 0
|
|
1042
1305
|
_flowScanPos = 0
|
|
1306
|
+
_flowScanUpTo = 0
|
|
1043
1307
|
_inSingleQuote = false
|
|
1044
1308
|
_inDoubleQuote = false
|
|
1045
1309
|
// Empty / whitespace-only / comments-only source: emit one
|
|
@@ -1052,6 +1316,13 @@ const Yaml: Plugin = (tabnas: Tabnas, options: YamlOptions) => {
|
|
|
1052
1316
|
lex.pnt.sI = 0
|
|
1053
1317
|
return tkn
|
|
1054
1318
|
}
|
|
1319
|
+
// A byte order mark opening the stream is not content: step
|
|
1320
|
+
// over it without counting a column. atLineStart and the
|
|
1321
|
+
// flow scan treat the offset after it as a line start.
|
|
1322
|
+
if (0 === lex.pnt.sI && 0xFEFF === src.charCodeAt(0)) {
|
|
1323
|
+
lex.pnt.sI = 1
|
|
1324
|
+
lex.refwd()
|
|
1325
|
+
}
|
|
1055
1326
|
}
|
|
1056
1327
|
// Drain any queued tokens first (from multi-token explicit keys).
|
|
1057
1328
|
if (pendingTokens.length > 0) {
|
|
@@ -1269,8 +1540,7 @@ const Yaml: Plugin = (tabnas: Tabnas, options: YamlOptions) => {
|
|
|
1269
1540
|
// #DR token whose val is the raw directive text. The stream rule
|
|
1270
1541
|
// applies the directive (e.g. %TAG handle registration) at parse
|
|
1271
1542
|
// time via the @apply-directive action.
|
|
1272
|
-
if (
|
|
1273
|
-
lex.src[pnt.sI - 1] === '\r') && fwd[0] === '%') {
|
|
1543
|
+
if (fwd[0] === '%' && atLineStart(lex.src, pnt.sI)) {
|
|
1274
1544
|
let pos = 0
|
|
1275
1545
|
while (pos < fwd.length && fwd[pos] !== '\n' && fwd[pos] !== '\r') pos++
|
|
1276
1546
|
let directiveSrc = fwd.substring(0, pos)
|
|
@@ -1446,8 +1716,8 @@ const Yaml: Plugin = (tabnas: Tabnas, options: YamlOptions) => {
|
|
|
1446
1716
|
let rawVal = fwd.substring(valStart + 1, valEnd - 1)
|
|
1447
1717
|
let result: any = rawVal
|
|
1448
1718
|
if (!tagHandles['!!']) {
|
|
1449
|
-
if (tag === 'int') result =
|
|
1450
|
-
else if (tag === 'float') result =
|
|
1719
|
+
if (tag === 'int') result = yamlTagInt(rawVal)
|
|
1720
|
+
else if (tag === 'float') result = yamlTagFloat(rawVal)
|
|
1451
1721
|
else if (tag === 'bool') result = rawVal === 'true' || rawVal === 'True' || rawVal === 'TRUE'
|
|
1452
1722
|
else if (tag === 'null') result = null
|
|
1453
1723
|
}
|
|
@@ -1477,8 +1747,11 @@ const Yaml: Plugin = (tabnas: Tabnas, options: YamlOptions) => {
|
|
|
1477
1747
|
continue yamlMatchLoop
|
|
1478
1748
|
}
|
|
1479
1749
|
// Unquoted: stop at `: `, ` #`, newline, flow indicators.
|
|
1750
|
+
// A `#` straight after the tag's space starts a comment, so
|
|
1751
|
+
// the value is empty (`!!str #c` is "").
|
|
1480
1752
|
while (valEnd < fwd.length && fwd[valEnd] !== '\n' && fwd[valEnd] !== '\r' &&
|
|
1481
1753
|
fwd[valEnd] !== ',' && fwd[valEnd] !== '}' && fwd[valEnd] !== ']') {
|
|
1754
|
+
if (valEnd === valStart && valStart > tagEnd && fwd[valEnd] === '#') break
|
|
1482
1755
|
if (fwd[valEnd] === ':' && (fwd[valEnd+1] === ' ' || fwd[valEnd+1] === '\n' ||
|
|
1483
1756
|
fwd[valEnd+1] === '\r' || fwd[valEnd+1] === undefined)) break
|
|
1484
1757
|
if (fwd[valEnd] === ' ' && fwd[valEnd+1] === '#') break
|
|
@@ -1491,8 +1764,8 @@ const Yaml: Plugin = (tabnas: Tabnas, options: YamlOptions) => {
|
|
|
1491
1764
|
// !!type is a user-defined tag, not a YAML core type.
|
|
1492
1765
|
if (!tagHandles['!!']) {
|
|
1493
1766
|
if (tag === 'str') result = String(rawVal)
|
|
1494
|
-
else if (tag === 'int') result =
|
|
1495
|
-
else if (tag === 'float') result =
|
|
1767
|
+
else if (tag === 'int') result = yamlTagInt(rawVal)
|
|
1768
|
+
else if (tag === 'float') result = yamlTagFloat(rawVal)
|
|
1496
1769
|
else if (tag === 'bool') result = rawVal === 'true' || rawVal === 'True' || rawVal === 'TRUE'
|
|
1497
1770
|
else if (tag === 'null') result = null
|
|
1498
1771
|
}
|
|
@@ -1544,6 +1817,9 @@ const Yaml: Plugin = (tabnas: Tabnas, options: YamlOptions) => {
|
|
|
1544
1817
|
explicitKeyTag = tagMatch[1]
|
|
1545
1818
|
key = tagMatch[2]
|
|
1546
1819
|
}
|
|
1820
|
+
// A quoted explicit key is the scalar inside the quotes, not
|
|
1821
|
+
// the quoted text (`? "k"` is the key k).
|
|
1822
|
+
key = unquoteWholeScalar(key)
|
|
1547
1823
|
let consumed = keyEnd
|
|
1548
1824
|
// Track position before consuming newline (for !hasValue case).
|
|
1549
1825
|
let beforeNewline = consumed
|
|
@@ -1739,14 +2015,13 @@ const Yaml: Plugin = (tabnas: Tabnas, options: YamlOptions) => {
|
|
|
1739
2015
|
// (no spurious #IN gets emitted between #DS and the content).
|
|
1740
2016
|
// Inline content on the same line as the marker (--- foo) is
|
|
1741
2017
|
// left in place for the next call.
|
|
1742
|
-
if ((
|
|
1743
|
-
lex.src[pnt.sI - 1] === '\r') &&
|
|
1744
|
-
((fwd[0] === '-' && fwd[1] === '-' && fwd[2] === '-' &&
|
|
2018
|
+
if (((fwd[0] === '-' && fwd[1] === '-' && fwd[2] === '-' &&
|
|
1745
2019
|
(fwd[3] === '\n' || fwd[3] === '\r' ||
|
|
1746
2020
|
fwd[3] === ' ' || fwd[3] === '\t' || fwd[3] === undefined)) ||
|
|
1747
2021
|
(fwd[0] === '.' && fwd[1] === '.' && fwd[2] === '.' &&
|
|
1748
2022
|
(fwd[3] === '\n' || fwd[3] === '\r' ||
|
|
1749
|
-
fwd[3] === ' ' || fwd[3] === '\t' || fwd[3] === undefined)))
|
|
2023
|
+
fwd[3] === ' ' || fwd[3] === '\t' || fwd[3] === undefined))) &&
|
|
2024
|
+
atLineStart(lex.src, pnt.sI)) {
|
|
1750
2025
|
let isEnd = fwd[0] === '.'
|
|
1751
2026
|
let pos = 3
|
|
1752
2027
|
while (pos < fwd.length && (fwd[pos] === ' ' || fwd[pos] === '\t')) pos++
|
|
@@ -1933,7 +2208,10 @@ const Yaml: Plugin = (tabnas: Tabnas, options: YamlOptions) => {
|
|
|
1933
2208
|
// trailing commas (e.g. 12,), or non-numeric text after a space
|
|
1934
2209
|
// (e.g. "64 characters, hexadecimal.") must be captured before
|
|
1935
2210
|
// jsonic's number matcher grabs just the digits.
|
|
1936
|
-
|
|
2211
|
+
// A sign before the digit counts too: `+190:20:30` is one plain
|
|
2212
|
+
// scalar, where the number matcher would take `+190`.
|
|
2213
|
+
if ((fwd[0] >= '0' && fwd[0] <= '9') ||
|
|
2214
|
+
((fwd[0] === '+' || fwd[0] === '-') && fwd[1] >= '0' && fwd[1] <= '9')) {
|
|
1937
2215
|
updateFlowState(lex.src as string, pnt.sI)
|
|
1938
2216
|
let inFlow = _flowDepth > 0
|
|
1939
2217
|
let hasEmbeddedColon = false
|
|
@@ -1941,10 +2219,16 @@ const Yaml: Plugin = (tabnas: Tabnas, options: YamlOptions) => {
|
|
|
1941
2219
|
let hasBlockComma = false
|
|
1942
2220
|
let pi = 1
|
|
1943
2221
|
while (pi < fwd.length && fwd[pi] !== '\n' && fwd[pi] !== '\r') {
|
|
2222
|
+
// An embedded colon (20:03:20) does not end the scan: the
|
|
2223
|
+
// scalar can go on past a space ("09:00 AM"), and only the
|
|
2224
|
+
// trailing-text branch below reads that as one scalar.
|
|
2225
|
+
// Stopping here took "09:00" and left "AM" to fail the
|
|
2226
|
+
// parse (tabnas/yaml#90).
|
|
1944
2227
|
if (fwd[pi] === ':' && fwd[pi + 1] !== ' ' && fwd[pi + 1] !== '\t' &&
|
|
1945
2228
|
fwd[pi + 1] !== '\n' && fwd[pi + 1] !== '\r' && fwd[pi + 1] !== undefined) {
|
|
1946
2229
|
hasEmbeddedColon = true
|
|
1947
|
-
|
|
2230
|
+
pi++
|
|
2231
|
+
continue
|
|
1948
2232
|
}
|
|
1949
2233
|
// A COMMA IS NOT A SEPARATOR IN BLOCK CONTEXT.
|
|
1950
2234
|
//
|
|
@@ -2003,10 +2287,13 @@ const Yaml: Plugin = (tabnas: Tabnas, options: YamlOptions) => {
|
|
|
2003
2287
|
}
|
|
2004
2288
|
|
|
2005
2289
|
if (hasEmbeddedColon || hasBlockComma) {
|
|
2006
|
-
// Scan to end of plain scalar token (space, tab, newline,
|
|
2290
|
+
// Scan to end of plain scalar token (space, tab, newline,
|
|
2291
|
+
// eof, or in a flow collection its `,` `]` `}`: `[1, 12:30]`
|
|
2292
|
+
// holds "12:30", not "12:30,").
|
|
2007
2293
|
let end = 0
|
|
2008
2294
|
while (end < fwd.length && fwd[end] !== ' ' && fwd[end] !== '\t' &&
|
|
2009
|
-
fwd[end] !== '\n' && fwd[end] !== '\r'
|
|
2295
|
+
fwd[end] !== '\n' && fwd[end] !== '\r' &&
|
|
2296
|
+
!(inFlow && (fwd[end] === ',' || fwd[end] === ']' || fwd[end] === '}'))) end++
|
|
2010
2297
|
let text = fwd.substring(0, end)
|
|
2011
2298
|
let tkn = lex.token('#TX', text, text, lex.pnt)
|
|
2012
2299
|
pnt.sI += end
|
|
@@ -2248,9 +2535,14 @@ const Yaml: Plugin = (tabnas: Tabnas, options: YamlOptions) => {
|
|
|
2248
2535
|
// Skip #IN when the next content is a flow indicator or quoted
|
|
2249
2536
|
// string at column 0 — there's no block to indent into. Match
|
|
2250
2537
|
// the previous behavior of the inline `--- foo` handler.
|
|
2538
|
+
// A quoted KEY at column 0 is different: it continues (or
|
|
2539
|
+
// closes back to) the root block mapping, so it needs its #IN
|
|
2540
|
+
// like a plain key does. Without it a block sequence ending
|
|
2541
|
+
// just before it swallowed the pair (tabnas/yaml#86).
|
|
2251
2542
|
if (spaces === 0 &&
|
|
2252
2543
|
(fwd[pos] === '{' || fwd[pos] === '[' ||
|
|
2253
|
-
fwd[pos] === '"' || fwd[pos] === "'")
|
|
2544
|
+
((fwd[pos] === '"' || fwd[pos] === "'") &&
|
|
2545
|
+
!quotedKeyAt(fwd, pos)))) {
|
|
2254
2546
|
pnt.sI += pos
|
|
2255
2547
|
pnt.rI += rows
|
|
2256
2548
|
pnt.cI = 0
|
|
@@ -2629,7 +2921,7 @@ Yaml.defaults = ({
|
|
|
2629
2921
|
// VERSION is this package's version. It MUST equal package.json "version":
|
|
2630
2922
|
// the release orchestrator rewrites both, and test/version.test.ts fails the
|
|
2631
2923
|
// build if they drift. Mirrors `const VERSION` in go/yaml.go.
|
|
2632
|
-
const VERSION = '0.5.
|
|
2924
|
+
const VERSION = '0.5.13'
|
|
2633
2925
|
|
|
2634
2926
|
|
|
2635
2927
|
export {
|