diff --git a/doc/api/cli.md b/doc/api/cli.md
index bddd4a5c0a5f..479d2b8b31fe 100644
--- a/doc/api/cli.md
+++ b/doc/api/cli.md
@@ -1780,6 +1780,16 @@ added:
Enable experimental support for the worker inspection with Chrome DevTools.
+### `--experimental-yaml`
+
+
+
+> Stability: 1.0 - Early development
+
+Enable the experimental [`node:yaml`][] module.
+
### `--force-context-aware`
+
+The input passed to [`yaml.parse()`][] is not valid YAML. The message names
+the problem and its line and column.
+
### `ERR_IPC_CHANNEL_CLOSED`
@@ -4858,6 +4869,7 @@ An error occurred trying to allocate memory. This should never happen.
[`util.inspect()`]: util.md#utilinspectobject-options
[`util.parseArgs()`]: util.md#utilparseargsconfig
[`v8.startupSnapshot.setDeserializeMainFunction()`]: v8.md#v8startupsnapshotsetdeserializemainfunctioncallback-data
+[`yaml.parse()`]: yaml.md#yamlparseinput
[`zlib`]: zlib.md
[crypto digest algorithm]: crypto.md#cryptogethashes
[debugger]: debugger.md
diff --git a/doc/api/index.md b/doc/api/index.md
index a30724c064e1..d865d43ef7ff 100644
--- a/doc/api/index.md
+++ b/doc/api/index.md
@@ -71,6 +71,7 @@
* [Web Crypto API](webcrypto.md)
* [Web Streams API](webstreams.md)
* [Worker threads](worker_threads.md)
+* [YAML](yaml.md)
* [Zlib](zlib.md)
diff --git a/doc/api/yaml.md b/doc/api/yaml.md
new file mode 100644
index 000000000000..0e8b69c9c75c
--- /dev/null
+++ b/doc/api/yaml.md
@@ -0,0 +1,80 @@
+# YAML
+
+
+
+
+
+> Stability: 1.0 - Early development
+
+
+
+The `node:yaml` module parses [YAML 1.2][] into JavaScript values. Enable it
+with [`--experimental-yaml`][] and import it with `node:`:
+
+```mjs
+import yaml from 'node:yaml';
+```
+
+```cjs
+const yaml = require('node:yaml');
+```
+
+Loading the module emits an `ExperimentalWarning`.
+
+## `yaml.parse(input)`
+
+
+
+* `input` {string|ArrayBuffer|SharedArrayBuffer|TypedArray|DataView} The YAML
+ text. Binary input is decoded as UTF-8.
+* Returns: {any} The value of the document, or an array with the value of each
+ document when the stream holds more than one. An empty stream yields `null`.
+
+`parse()` uses the YAML 1.2 [core schema][]. Plain scalars resolve to null,
+booleans, numbers or strings. For example, `yes` and `no` are strings. Quoted
+and block scalars are strings unless a tag changes their type, as in
+`!!int "42"`. Mappings become plain objects and sequences become arrays, as
+with [`JSON.parse()`][].
+
+```mjs
+import yaml from 'node:yaml';
+
+const config = yaml.parse(`
+name: app
+ports:
+ - 8080
+ - 8443
+tls: true
+`);
+console.log(config);
+// { name: 'app', ports: [ 8080, 8443 ], tls: true }
+
+console.log(yaml.parse('--- 1\n--- 2\n'));
+// [ 1, 2 ]
+```
+
+Aliases to a collection refer to the same object. A collection may contain an
+alias to itself, so the result can contain cycles.
+
+The parser applies the core tags `!!str`, `!!int`, `!!float`, `!!bool`,
+`!!null`, `!!map` and `!!seq`. A tag must match its value: `!!int abc` throws
+an error. Other tags leave collections unchanged and make scalars strings.
+For example, `!custom 123` becomes `'123'`.
+
+Mapping keys become property names. Scalar keys use their string form, so
+`null`, `true` and `1` become `'null'`, `'true'` and `'1'`. Collection keys use
+their JSON text and are limited to 1024 characters. Duplicate keys throw.
+
+Invalid YAML throws an [`ERR_INVALID_YAML`][] `SyntaxError` with the line and
+column. The parser rejects disallowed control characters and nesting deeper
+than 1000 levels.
+
+[YAML 1.2]: https://yaml.org/spec/1.2.2/
+[`--experimental-yaml`]: cli.md#--experimental-yaml
+[`ERR_INVALID_YAML`]: errors.md#err_invalid_yaml
+[`JSON.parse()`]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/parse
+[core schema]: https://yaml.org/spec/1.2.2/#103-core-schema
diff --git a/doc/node-config-schema.json b/doc/node-config-schema.json
index 0d2cb83b517d..6f74077b3582 100644
--- a/doc/node-config-schema.json
+++ b/doc/node-config-schema.json
@@ -418,6 +418,10 @@
"type": "boolean",
"description": "experimental Web Storage API"
},
+ "experimental-yaml": {
+ "type": "boolean",
+ "description": "experimental node:yaml module"
+ },
"extra-info-on-fatal-exception": {
"type": "boolean",
"description": "hide extra information on fatal exception that causes exit"
diff --git a/doc/node.1 b/doc/node.1
index 7a32ece46d21..c0236f96a901 100644
--- a/doc/node.1
+++ b/doc/node.1
@@ -955,6 +955,9 @@ Enable experimental support for the Web Worker API.
.It Fl -experimental-worker-inspection
Enable experimental support for the worker inspection with Chrome DevTools.
.
+.It Fl -experimental-yaml
+Enable the experimental \fBnode:yaml\fR module.
+.
.It Fl -force-context-aware
Disable loading native addons that are not context-aware.
.
@@ -2304,6 +2307,8 @@ one is included in the list below.
.It
\fB--experimental-websocket\fR
.It
+\fB--experimental-yaml\fR
+.It
\fB--force-context-aware\fR
.It
\fB--force-fips\fR
diff --git a/lib/internal/bootstrap/realm.js b/lib/internal/bootstrap/realm.js
index 3e3473b000c5..43c649f2ce1e 100644
--- a/lib/internal/bootstrap/realm.js
+++ b/lib/internal/bootstrap/realm.js
@@ -208,6 +208,7 @@ const builtinModulePolicies = [
['test', true, null],
['test/reporters', true, null],
['vfs', true, '--experimental-vfs'],
+ ['yaml', true, '--experimental-yaml'],
['zlib/iter', false, '--experimental-stream-iter'],
];
diff --git a/lib/internal/errors.js b/lib/internal/errors.js
index 88c4102eee4f..2fd6dcfe8fa1 100644
--- a/lib/internal/errors.js
+++ b/lib/internal/errors.js
@@ -1596,6 +1596,7 @@ E('ERR_INVALID_URL_SCHEME',
`of scheme ${expected[0]}`;
return `The URL must be ${res}`;
}, TypeError);
+E('ERR_INVALID_YAML', '%s', SyntaxError);
E('ERR_IPC_CHANNEL_CLOSED', 'Channel closed', Error);
E('ERR_IPC_DISCONNECTED', 'IPC channel is already disconnected', Error);
E('ERR_IPC_ONE_PIPE', 'Child process can have only one IPC pipe', Error);
diff --git a/lib/internal/yaml/parser.js b/lib/internal/yaml/parser.js
new file mode 100644
index 000000000000..efc28599994e
--- /dev/null
+++ b/lib/internal/yaml/parser.js
@@ -0,0 +1,1616 @@
+'use strict';
+
+// A YAML 1.2 parser for the core schema that builds JavaScript values
+// directly, in one pass over the text. It follows the structure of the
+// specification: documents and directives, block collections driven by
+// indentation, flow collections, and the scalar styles.
+
+const {
+ ArrayIsArray,
+ ArrayPrototypeIncludes,
+ ArrayPrototypePush,
+ JSONStringify,
+ NumberParseFloat,
+ NumberParseInt,
+ NumberPrototypeToString,
+ ObjectDefineProperty,
+ ObjectKeys,
+ ObjectPrototypeHasOwnProperty,
+ SafeMap,
+ SafeSet,
+ String,
+ StringFromCharCode,
+ StringFromCodePoint,
+ StringPrototypeCharCodeAt,
+ StringPrototypeRepeat,
+ StringPrototypeSlice,
+ Symbol,
+} = primordials;
+
+const {
+ codes: {
+ ERR_INVALID_YAML,
+ },
+} = require('internal/errors');
+
+const kMaxDepth = 1000;
+const kMaxKeyLength = 1024;
+
+const kEOF = -1;
+const kTab = 0x09;
+const kLF = 0x0A;
+const kCR = 0x0D;
+const kSpace = 0x20;
+const kBang = 0x21;
+const kDoubleQuote = 0x22;
+const kHash = 0x23;
+const kPercent = 0x25;
+const kAmpersand = 0x26;
+const kSingleQuote = 0x27;
+const kAsterisk = 0x2A;
+const kPlus = 0x2B;
+const kComma = 0x2C;
+const kMinus = 0x2D;
+const kDot = 0x2E;
+const kSlash = 0x2F;
+const kZero = 0x30;
+const kNine = 0x39;
+const kColon = 0x3A;
+const kLess = 0x3C;
+const kGreater = 0x3E;
+const kQuestion = 0x3F;
+const kAt = 0x40;
+const kLeftBracket = 0x5B;
+const kBackslash = 0x5C;
+const kRightBracket = 0x5D;
+const kBacktick = 0x60;
+const kLeftBrace = 0x7B;
+const kPipe = 0x7C;
+const kRightBrace = 0x7D;
+const kBOM = 0xFEFF;
+
+const kClip = 0;
+const kStrip = 1;
+const kKeep = 2;
+
+// How a node was written, which decides how a tag applies to it.
+const kPlain = 0;
+const kQuoted = 1;
+const kSequence = 2;
+const kMapping = 3;
+
+const kCoreTagPrefix = 'tag:yaml.org,2002:';
+
+// Returned by the compose functions when there is no node at the position.
+const kNoNode = Symbol('kNoNode');
+
+class State {
+ constructor(input) {
+ this.input = input;
+ this.length = input.length;
+ this.pos = 0;
+ this.line = 0;
+ this.lineStart = 0;
+ // Number of leading spaces of the current line.
+ this.lineIndent = 0;
+ // Whether the current line has content after a tab in its indentation.
+ this.tabIndented = false;
+ this.depth = 0;
+ this.anchors = new SafeMap();
+ this.tagHandles = new SafeMap();
+ this.version = null;
+ this.explicitEnd = false;
+ // Set by the scalar readers: whether the scalar spanned several lines.
+ this.multiline = false;
+ // Set by readFlowNode: whether the node was quoted or a flow collection,
+ // which allows ":" to follow it without a space.
+ this.lastJsonLike = false;
+ }
+}
+
+function at(s, pos) {
+ return pos < s.length ? StringPrototypeCharCodeAt(s.input, pos) : kEOF;
+}
+
+function fail(s, message, pos = s.pos) {
+ let line = s.line;
+ let lineStart = s.lineStart;
+ if (pos < lineStart) {
+ // The error is on an earlier line: recount from the start.
+ line = 0;
+ lineStart = 0;
+ for (let i = 0; i < pos; i++) {
+ const c = StringPrototypeCharCodeAt(s.input, i);
+ if (c === kLF || (c === kCR && at(s, i + 1) !== kLF)) {
+ line++;
+ lineStart = i + 1;
+ }
+ }
+ }
+ throw new ERR_INVALID_YAML(
+ `${message} (line ${line + 1}, column ${pos - lineStart + 1})`);
+}
+
+function isWhite(c) {
+ return c === kSpace || c === kTab;
+}
+
+function isBreak(c) {
+ return c === kLF || c === kCR;
+}
+
+function isBlank(c) {
+ return c === kSpace || c === kTab || c === kLF || c === kCR || c === kEOF;
+}
+
+function isFlowIndicator(c) {
+ return c === kComma || c === kLeftBracket || c === kRightBracket ||
+ c === kLeftBrace || c === kRightBrace;
+}
+
+function isDigit(c) {
+ return c >= kZero && c <= kNine;
+}
+
+function isHexDigit(c) {
+ return isDigit(c) || (c >= 0x41 && c <= 0x46) || (c >= 0x61 && c <= 0x66);
+}
+
+// Consumes one line break and the indentation of the next line.
+function consumeBreak(s) {
+ const c = at(s, s.pos);
+ s.pos++;
+ if (c === kCR && at(s, s.pos) === kLF) s.pos++;
+ s.line++;
+ s.lineStart = s.pos;
+ let indent = 0;
+ while (at(s, s.pos) === kSpace) {
+ s.pos++;
+ indent++;
+ }
+ s.lineIndent = indent;
+ s.tabIndented = false;
+ if (at(s, s.pos) === kTab) {
+ let p = s.pos;
+ while (isWhite(at(s, p))) p++;
+ const n = at(s, p);
+ s.tabIndented = n !== kEOF && !isBreak(n) && n !== kHash;
+ }
+}
+
+function skipWhite(s) {
+ while (isWhite(at(s, s.pos))) s.pos++;
+}
+
+function skipToEndOfLine(s) {
+ while (s.pos < s.length && !isBreak(at(s, s.pos))) s.pos++;
+}
+
+function atDocumentMarker(s, pos = s.pos) {
+ if (pos !== s.lineStart) return false;
+ const c = at(s, pos);
+ if (c !== kMinus && c !== kDot) return false;
+ return at(s, pos + 1) === c && at(s, pos + 2) === c && isBlank(at(s, pos + 3));
+}
+
+// Skips whitespace, comments and line breaks. Returns the number of line
+// breaks crossed. A comment must be separated from content by whitespace.
+function skipSeparation(s) {
+ let breaks = 0;
+ for (;;) {
+ const c = at(s, s.pos);
+ if (isWhite(c)) {
+ s.pos++;
+ } else if (c === kHash) {
+ if (s.pos > 0 && !isBlank(at(s, s.pos - 1))) {
+ fail(s, 'Comments must be separated from other tokens by whitespace');
+ }
+ skipToEndOfLine(s);
+ } else if (isBreak(c)) {
+ consumeBreak(s);
+ breaks++;
+ } else {
+ return breaks;
+ }
+ }
+}
+
+function setProperty(s, map, key, value, keyPos) {
+ if (ObjectPrototypeHasOwnProperty(map, key)) {
+ fail(s, `Duplicate mapping key "${key}"`, keyPos);
+ }
+ ObjectDefineProperty(map, key, {
+ __proto__: null,
+ value,
+ writable: true,
+ enumerable: true,
+ configurable: true,
+ });
+}
+
+// Mapping keys become property names: scalars as their string form,
+// collections as their JSON text, bounded in size since aliases can make a
+// small key expand to a huge text.
+function keyToString(s, key, pos) {
+ if (typeof key === 'string') return key;
+ if (key === null || typeof key === 'number' || typeof key === 'boolean') {
+ return String(key);
+ }
+ const text = { __proto__: null, value: '', budget: kMaxKeyLength };
+ appendKeyJSON(s, text, key, pos);
+ return text.value;
+}
+
+function appendKeyText(s, text, part, pos) {
+ text.budget -= part.length;
+ if (text.budget < 0) {
+ fail(s, `A collection key must not exceed ${kMaxKeyLength} characters`, pos);
+ }
+ text.value += part;
+}
+
+function appendKeyJSON(s, text, value, pos) {
+ if (value === null || typeof value !== 'object') {
+ appendKeyText(s, text, JSONStringify(value), pos);
+ } else if (ArrayIsArray(value)) {
+ appendKeyText(s, text, '[', pos);
+ for (let i = 0; i < value.length; i++) {
+ if (i > 0) appendKeyText(s, text, ',', pos);
+ appendKeyJSON(s, text, value[i], pos);
+ }
+ appendKeyText(s, text, ']', pos);
+ } else {
+ appendKeyText(s, text, '{', pos);
+ const keys = ObjectKeys(value);
+ for (let i = 0; i < keys.length; i++) {
+ if (i > 0) appendKeyText(s, text, ',', pos);
+ appendKeyText(s, text, `${JSONStringify(keys[i])}:`, pos);
+ appendKeyJSON(s, text, value[keys[i]], pos);
+ }
+ appendKeyText(s, text, '}', pos);
+ }
+}
+
+function enter(s) {
+ if (++s.depth > kMaxDepth) {
+ fail(s, `Nesting deeper than ${kMaxDepth} levels`);
+ }
+}
+
+// --- Scalars ---------------------------------------------------------------
+
+// Resolves a plain scalar with the core schema.
+function resolvePlain(text) {
+ const length = text.length;
+ if (length === 0) return null;
+ const c = StringPrototypeCharCodeAt(text, 0);
+ switch (c) {
+ case 0x7E: // ~
+ if (length === 1) return null;
+ break;
+ case 0x6E: case 0x4E: // n N
+ if (text === 'null' || text === 'Null' || text === 'NULL') return null;
+ break;
+ case 0x74: case 0x54: // t T
+ if (text === 'true' || text === 'True' || text === 'TRUE') return true;
+ break;
+ case 0x66: case 0x46: // f F
+ if (text === 'false' || text === 'False' || text === 'FALSE') return false;
+ break;
+ case kDot: case kMinus: case kPlus: {
+ const special = parseSpecialFloat(text);
+ if (special !== undefined) return special;
+ break;
+ }
+ }
+ if (c === kDot || c === kMinus || c === kPlus || isDigit(c)) {
+ const number = parseNumber(text);
+ if (number !== undefined) return number;
+ }
+ return text;
+}
+
+function parseSpecialFloat(text) {
+ let i = 0;
+ const first = StringPrototypeCharCodeAt(text, 0);
+ if (first === kMinus || first === kPlus) i = 1;
+ if (StringPrototypeCharCodeAt(text, i) !== kDot) return undefined;
+ const word = StringPrototypeSlice(text, i + 1);
+ if (word === 'inf' || word === 'Inf' || word === 'INF') {
+ return first === kMinus ? -Infinity : Infinity;
+ }
+ if (i === 0 && (word === 'nan' || word === 'NaN' || word === 'NAN')) {
+ return NaN;
+ }
+ return undefined;
+}
+
+// Core schema integers and floats, or undefined.
+function parseNumber(text) {
+ const length = text.length;
+ let i = 0;
+ let c = StringPrototypeCharCodeAt(text, 0);
+ const negative = c === kMinus;
+ if (c === kMinus || c === kPlus) {
+ i++;
+ c = StringPrototypeCharCodeAt(text, i);
+ }
+ if (c === kZero && i === 0 && length > 2) {
+ const base = StringPrototypeCharCodeAt(text, 1);
+ if (base === 0x78) { // 0x
+ for (let j = 2; j < length; j++) {
+ if (!isHexDigit(StringPrototypeCharCodeAt(text, j))) return undefined;
+ }
+ return NumberParseInt(StringPrototypeSlice(text, 2), 16);
+ }
+ if (base === 0x6F) { // 0o
+ for (let j = 2; j < length; j++) {
+ const d = StringPrototypeCharCodeAt(text, j);
+ if (d < kZero || d > 0x37) return undefined;
+ }
+ return NumberParseInt(StringPrototypeSlice(text, 2), 8);
+ }
+ }
+ let digits = 0;
+ while (i < length && isDigit(StringPrototypeCharCodeAt(text, i))) {
+ i++;
+ digits++;
+ }
+ let isFloat = false;
+ if (i < length && StringPrototypeCharCodeAt(text, i) === kDot) {
+ isFloat = true;
+ i++;
+ while (i < length && isDigit(StringPrototypeCharCodeAt(text, i))) {
+ i++;
+ digits++;
+ }
+ }
+ if (digits === 0) return undefined;
+ if (i < length) {
+ c = StringPrototypeCharCodeAt(text, i);
+ if (c !== 0x65 && c !== 0x45) return undefined; // e E
+ isFloat = true;
+ i++;
+ c = StringPrototypeCharCodeAt(text, i);
+ if (c === kMinus || c === kPlus) i++;
+ let exponentDigits = 0;
+ while (i < length && isDigit(StringPrototypeCharCodeAt(text, i))) {
+ i++;
+ exponentDigits++;
+ }
+ if (exponentDigits === 0 || i < length) return undefined;
+ }
+ if (isFloat) return NumberParseFloat(text);
+ const value = NumberParseInt(text, 10);
+ // -0 is an integer in YAML; keep it a plain 0 like JSON.parse does.
+ return negative && value === 0 ? 0 : value;
+}
+
+// Whether text has the form of a core schema integer: decimal with an
+// optional sign, 0x hexadecimal or 0o octal.
+function isIntegerText(text) {
+ const length = text.length;
+ let i = 0;
+ const c = StringPrototypeCharCodeAt(text, 0);
+ if (c === kMinus || c === kPlus) i = 1;
+ if (c === kZero && length > 2) {
+ const base = StringPrototypeCharCodeAt(text, 1);
+ if (base === 0x78 || base === 0x6F) {
+ for (let j = 2; j < length; j++) {
+ const d = StringPrototypeCharCodeAt(text, j);
+ if (base === 0x78 ? !isHexDigit(d) : (d < kZero || d > 0x37)) return false;
+ }
+ return true;
+ }
+ }
+ if (i === length) return false;
+ for (; i < length; i++) {
+ if (!isDigit(StringPrototypeCharCodeAt(text, i))) return false;
+ }
+ return true;
+}
+
+// Characters that never start a plain scalar: whitespace, the indicators of
+// the other node kinds, comments and the reserved indicators.
+const kNoPlainStart = new SafeSet([
+ kEOF, kSpace, kTab, kLF, kCR, kHash, kAmpersand, kAsterisk, kBang, kPipe,
+ kGreater, kSingleQuote, kDoubleQuote, kPercent, kAt, kBacktick,
+]);
+
+function canStartPlain(s, c, inFlow) {
+ if (kNoPlainStart.has(c)) return false;
+ if (c === kMinus || c === kQuestion || c === kColon) {
+ const next = at(s, s.pos + 1);
+ return !isBlank(next) && !(inFlow && isFlowIndicator(next));
+ }
+ if (isFlowIndicator(c)) return !inFlow;
+ return true;
+}
+
+// A line break inside a scalar folds into a space, or into one newline per
+// empty line that follows it.
+function foldBreaks(count) {
+ return count === 1 ? ' ' : StringPrototypeRepeat('\n', count - 1);
+}
+
+// Reads a plain scalar and returns its text. In block context the
+// continuation lines must be indented more than the parent; in flow context
+// the scalar also ends at flow indicators.
+function readPlain(s, parentIndent, inFlow, singleLine) {
+ const startLine = s.line;
+ let result = '';
+ let segmentStart = s.pos;
+ let segmentEnd = s.pos;
+ let pendingBreaks = 0;
+
+ const flush = () => {
+ if (segmentEnd > segmentStart) {
+ result += (pendingBreaks > 0 ? foldBreaks(pendingBreaks) : '') +
+ StringPrototypeSlice(s.input, segmentStart, segmentEnd);
+ }
+ };
+
+ for (;;) {
+ const c = at(s, s.pos);
+ if (c === kEOF || isBreak(c)) {
+ flush();
+ if (c === kEOF || singleLine) break;
+ // Look at the following lines without committing to them.
+ const savedLine = s.line;
+ const savedLineStart = s.lineStart;
+ const savedIndent = s.lineIndent;
+ const savedTab = s.tabIndented;
+ let breaks = 0;
+ let continues = false;
+ while (isBreak(at(s, s.pos))) {
+ consumeBreak(s);
+ breaks++;
+ let p = s.pos;
+ while (isWhite(at(s, p))) p++;
+ const n = at(s, p);
+ if (n === kEOF) break;
+ if (isBreak(n)) {
+ // A blank line.
+ s.pos = p;
+ continue;
+ }
+ if (atDocumentMarker(s) || n === kHash) break;
+ if (s.lineIndent <= parentIndent) break;
+ if (inFlow && isFlowIndicator(n)) break;
+ s.pos = p;
+ continues = true;
+ break;
+ }
+ if (!continues) {
+ s.line = savedLine;
+ s.lineStart = savedLineStart;
+ s.lineIndent = savedIndent;
+ s.tabIndented = savedTab;
+ break;
+ }
+ pendingBreaks = breaks;
+ segmentStart = s.pos;
+ segmentEnd = s.pos;
+ continue;
+ }
+ if (c === kColon) {
+ const next = at(s, s.pos + 1);
+ if (isBlank(next) || (inFlow && isFlowIndicator(next))) {
+ flush();
+ break;
+ }
+ } else if (c === kHash) {
+ if (isWhite(at(s, s.pos - 1))) {
+ flush();
+ break;
+ }
+ } else if (inFlow && isFlowIndicator(c)) {
+ flush();
+ break;
+ }
+ s.pos++;
+ if (!isWhite(c)) segmentEnd = s.pos;
+ }
+ // The scalar ends after its last non-blank character.
+ s.pos = segmentEnd;
+ s.multiline = s.line !== startLine;
+ return result;
+}
+
+function trimTrailingWhite(text) {
+ let end = text.length;
+ while (end > 0 && isWhite(StringPrototypeCharCodeAt(text, end - 1))) end--;
+ return StringPrototypeSlice(text, 0, end);
+}
+
+// Called at a line break inside a quoted scalar. Consumes the breaks and
+// blank lines and returns their number. Continuation lines must be indented
+// more than the parent node.
+function skipQuotedBreaks(s, parentIndent) {
+ let breaks = 0;
+ while (isBreak(at(s, s.pos))) {
+ consumeBreak(s);
+ breaks++;
+ skipWhite(s);
+ const c = at(s, s.pos);
+ if (c === kEOF) fail(s, 'Unterminated quoted scalar');
+ if (isBreak(c)) continue;
+ if (atDocumentMarker(s)) {
+ fail(s, 'Unexpected document marker inside a quoted scalar');
+ }
+ if (s.lineIndent <= parentIndent) fail(s, 'Bad indentation in a quoted scalar');
+ }
+ return breaks;
+}
+
+function readSingleQuoted(s, parentIndent) {
+ const startLine = s.line;
+ const startPos = s.pos;
+ s.pos++; // Opening quote.
+ let result = '';
+ let segmentStart = s.pos;
+ for (;;) {
+ const c = at(s, s.pos);
+ if (c === kEOF) {
+ fail(s, 'Unterminated single-quoted scalar', startPos);
+ } else if (c === kSingleQuote) {
+ result += StringPrototypeSlice(s.input, segmentStart, s.pos);
+ s.pos++;
+ if (at(s, s.pos) === kSingleQuote) {
+ result += "'";
+ s.pos++;
+ segmentStart = s.pos;
+ continue;
+ }
+ break;
+ } else if (isBreak(c)) {
+ result += trimTrailingWhite(StringPrototypeSlice(s.input, segmentStart, s.pos));
+ result += foldBreaks(skipQuotedBreaks(s, parentIndent));
+ segmentStart = s.pos;
+ } else {
+ s.pos++;
+ }
+ }
+ s.multiline = s.line !== startLine;
+ return result;
+}
+
+function readDoubleQuoted(s, parentIndent) {
+ const startLine = s.line;
+ const startPos = s.pos;
+ s.pos++; // Opening quote.
+ let result = '';
+ let segmentStart = s.pos;
+ for (;;) {
+ const c = at(s, s.pos);
+ if (c === kEOF) {
+ fail(s, 'Unterminated double-quoted scalar', startPos);
+ } else if (c === kDoubleQuote) {
+ result += StringPrototypeSlice(s.input, segmentStart, s.pos);
+ s.pos++;
+ break;
+ } else if (c === kBackslash) {
+ result += StringPrototypeSlice(s.input, segmentStart, s.pos);
+ s.pos++;
+ const e = at(s, s.pos);
+ if (isBreak(e)) {
+ // Escaped line break: join with the next line without a space. Empty
+ // lines in between are kept.
+ const breaks = skipQuotedBreaks(s, parentIndent);
+ if (breaks > 1) result += StringPrototypeRepeat('\n', breaks - 1);
+ } else {
+ result += readEscape(s, e);
+ }
+ segmentStart = s.pos;
+ } else if (isBreak(c)) {
+ result += trimTrailingWhite(StringPrototypeSlice(s.input, segmentStart, s.pos));
+ result += foldBreaks(skipQuotedBreaks(s, parentIndent));
+ segmentStart = s.pos;
+ } else {
+ s.pos++;
+ }
+ }
+ s.multiline = s.line !== startLine;
+ return result;
+}
+
+function readEscape(s, c) {
+ s.pos++;
+ switch (c) {
+ case 0x30: return '\0';
+ case 0x61: return '\x07';
+ case 0x62: return '\b';
+ case 0x74: case kTab: return '\t';
+ case 0x6E: return '\n';
+ case 0x76: return '\v';
+ case 0x66: return '\f';
+ case 0x72: return '\r';
+ case 0x65: return '\x1B';
+ case kSpace: return ' ';
+ case kDoubleQuote: return '"';
+ case kSlash: return '/';
+ case kBackslash: return '\\';
+ case 0x4E: return '\x85';
+ case 0x5F: return '\xA0';
+ case 0x4C: return '\u2028';
+ case 0x50: return '\u2029';
+ case 0x78: return readHexEscape(s, 2);
+ case 0x75: return readHexEscape(s, 4);
+ case 0x55: return readHexEscape(s, 8);
+ }
+ fail(s, `Unknown escape sequence "\\${c === kEOF ? '' : StringFromCharCode(c)}"`, s.pos - 2);
+}
+
+function readHexEscape(s, digits) {
+ let value = 0;
+ for (let i = 0; i < digits; i++) {
+ const c = at(s, s.pos);
+ if (!isHexDigit(c)) fail(s, 'Invalid hexadecimal escape sequence');
+ value = value * 16 + NumberParseInt(StringFromCharCode(c), 16);
+ s.pos++;
+ }
+ if (value > 0x10FFFF || (digits === 8 && value >= 0xD800 && value <= 0xDFFF)) {
+ fail(s, 'Invalid code point in escape sequence', s.pos - digits);
+ }
+ return value > 0xFFFF ? StringFromCodePoint(value) : StringFromCharCode(value);
+}
+
+// Reads a literal (|) or folded (>) block scalar. parentIndent is the
+// indentation of the node that owns it.
+function readBlockScalar(s, parentIndent) {
+ const folded = at(s, s.pos) === kGreater;
+ const headerPos = s.pos;
+ s.pos++;
+ let chomping = kClip;
+ let indicator = 0;
+ for (let i = 0; i < 2; i++) {
+ const c = at(s, s.pos);
+ if (c === kMinus || c === kPlus) {
+ if (chomping !== kClip) fail(s, 'Repeated chomping indicator');
+ chomping = c === kMinus ? kStrip : kKeep;
+ s.pos++;
+ } else if (isDigit(c)) {
+ if (indicator !== 0) fail(s, 'Repeated indentation indicator');
+ if (c === kZero) fail(s, 'Indentation indicator must be between 1 and 9');
+ indicator = c - kZero;
+ s.pos++;
+ }
+ }
+ skipWhite(s);
+ if (at(s, s.pos) === kHash) {
+ if (!isBlank(at(s, s.pos - 1))) {
+ fail(s, 'Comments must be separated from other tokens by whitespace');
+ }
+ skipToEndOfLine(s);
+ }
+ const afterHeader = at(s, s.pos);
+ if (afterHeader !== kEOF && !isBreak(afterHeader)) {
+ fail(s, 'Unexpected characters after block scalar header', headerPos);
+ }
+ s.multiline = true;
+ if (afterHeader === kEOF) return '';
+ consumeBreak(s);
+
+ let contentIndent = indicator > 0 ? parentIndent + indicator : -1;
+
+ // Leading empty lines: lines of spaces only. With auto-detection they must
+ // not be indented more than the content.
+ let leadingEmpty = 0;
+ let maxLeadingSpaces = 0;
+ for (;;) {
+ const c = at(s, s.pos);
+ if (c === kEOF && s.pos === s.lineStart) break;
+ if (c !== kEOF && !isBreak(c)) break;
+ if (s.lineIndent > maxLeadingSpaces) maxLeadingSpaces = s.lineIndent;
+ leadingEmpty++;
+ if (c === kEOF) break;
+ consumeBreak(s);
+ }
+ if (at(s, s.pos) === kEOF) {
+ return chomping === kKeep ? StringPrototypeRepeat('\n', leadingEmpty) : '';
+ }
+ if (contentIndent < 0) {
+ // Auto-detect from the first non-empty line.
+ if (s.lineIndent <= parentIndent || atDocumentMarker(s)) {
+ contentIndent = parentIndent + 1;
+ } else {
+ contentIndent = s.lineIndent;
+ if (maxLeadingSpaces > contentIndent) {
+ fail(s, 'A leading empty line is indented more than the block scalar content');
+ }
+ }
+ }
+
+ let result = '';
+ let emptyLines = leadingEmpty;
+ let hasContent = false;
+ let prevMoreIndented = false;
+ for (;;) {
+ const c = at(s, s.pos);
+ if (c === kEOF && s.pos === s.lineStart) break;
+ if (s.lineIndent < contentIndent) {
+ // Less indented: an empty line of spaces, or the end of the scalar.
+ if (c === kTab) fail(s, 'Tabs cannot be used for indentation');
+ if (c !== kEOF && !isBreak(c)) break;
+ emptyLines++;
+ if (c === kEOF) break;
+ consumeBreak(s);
+ continue;
+ }
+ if (atDocumentMarker(s)) break;
+ // A content line: everything after contentIndent spaces.
+ const textStart = s.lineStart + contentIndent;
+ let end = textStart;
+ while (end < s.length && !isBreak(at(s, end))) end++;
+ const text = StringPrototypeSlice(s.input, textStart, end);
+ if (text.length === 0) {
+ emptyLines++;
+ } else {
+ const moreIndented = isWhite(StringPrototypeCharCodeAt(text, 0));
+ if (hasContent) {
+ if (folded && !moreIndented && !prevMoreIndented) {
+ result += emptyLines === 0 ? ' ' : StringPrototypeRepeat('\n', emptyLines);
+ } else {
+ result += StringPrototypeRepeat('\n', emptyLines + 1);
+ }
+ } else if (emptyLines > 0) {
+ result += StringPrototypeRepeat('\n', emptyLines);
+ }
+ result += text;
+ hasContent = true;
+ emptyLines = 0;
+ prevMoreIndented = moreIndented;
+ }
+ s.pos = end;
+ if (at(s, s.pos) === kEOF) break;
+ consumeBreak(s);
+ }
+ if (!hasContent) {
+ // Only empty lines: keep yields the line breaks, the others nothing.
+ return chomping === kKeep ? StringPrototypeRepeat('\n', emptyLines) : '';
+ }
+ if (chomping === kClip) {
+ result += '\n';
+ } else if (chomping === kKeep) {
+ result += StringPrototypeRepeat('\n', emptyLines + 1);
+ }
+ return result;
+}
+
+// --- Properties, tags, anchors, aliases -------------------------------------
+
+function readAnchorName(s) {
+ const start = s.pos;
+ for (;;) {
+ const c = at(s, s.pos);
+ if (isBlank(c) || isFlowIndicator(c)) break;
+ s.pos++;
+ }
+ if (s.pos === start) fail(s, 'Anchor name must not be empty');
+ return StringPrototypeSlice(s.input, start, s.pos);
+}
+
+function decodeTagEscapes(s, text) {
+ let result = '';
+ for (let i = 0; i < text.length; i++) {
+ const c = StringPrototypeCharCodeAt(text, i);
+ if (c !== kPercent) {
+ result += StringFromCharCode(c);
+ continue;
+ }
+ const hex = StringPrototypeSlice(text, i + 1, i + 3);
+ if (hex.length !== 2 || !isHexDigit(StringPrototypeCharCodeAt(hex, 0)) ||
+ !isHexDigit(StringPrototypeCharCodeAt(hex, 1))) {
+ fail(s, 'Invalid escape in tag');
+ }
+ result += StringFromCharCode(NumberParseInt(hex, 16));
+ i += 2;
+ }
+ return result;
+}
+
+// Reads a tag property and returns the resolved tag.
+function readTag(s, inFlow) {
+ const start = s.pos;
+ s.pos++; // !
+ if (at(s, s.pos) === kLess) {
+ s.pos++;
+ const uriStart = s.pos;
+ while (at(s, s.pos) !== kGreater) {
+ if (isBlank(at(s, s.pos))) fail(s, 'Unterminated verbatim tag', start);
+ s.pos++;
+ }
+ const uri = StringPrototypeSlice(s.input, uriStart, s.pos);
+ s.pos++;
+ if (uri.length === 0 || uri === '!') fail(s, 'Verbatim tag must not be empty', start);
+ if (!isBlank(at(s, s.pos)) && !isFlowIndicator(at(s, s.pos))) {
+ fail(s, 'Unexpected characters after tag');
+ }
+ return uri;
+ }
+ // The handle is "!", "!!" or "!name!".
+ let handleEnd = s.pos;
+ if (at(s, handleEnd) === kBang) {
+ handleEnd++;
+ } else {
+ let p = handleEnd;
+ while (!isBlank(at(s, p)) && !isFlowIndicator(at(s, p)) && at(s, p) !== kBang) p++;
+ if (at(s, p) === kBang) handleEnd = p + 1;
+ }
+ const handle = StringPrototypeSlice(s.input, start, handleEnd);
+ s.pos = handleEnd;
+ const suffixStart = s.pos;
+ while (!isBlank(at(s, s.pos)) && !isFlowIndicator(at(s, s.pos))) {
+ if (at(s, s.pos) === kBang) fail(s, 'Invalid character in tag');
+ s.pos++;
+ }
+ const suffix = decodeTagEscapes(s, StringPrototypeSlice(s.input, suffixStart, s.pos));
+ if (!isBlank(at(s, s.pos)) && !(inFlow && isFlowIndicator(at(s, s.pos)))) {
+ fail(s, 'Invalid character in tag');
+ }
+ if (handle === '!') {
+ if (suffix.length === 0) return '!';
+ const prefix = s.tagHandles.get('!');
+ return prefix === undefined ? `!${suffix}` : prefix + suffix;
+ }
+ if (suffix.length === 0) fail(s, 'Tag suffix must not be empty', start);
+ if (handle === '!!') {
+ const prefix = s.tagHandles.get('!!');
+ return (prefix === undefined ? kCoreTagPrefix : prefix) + suffix;
+ }
+ const prefix = s.tagHandles.get(handle);
+ if (prefix === undefined) fail(s, `Undeclared tag handle "${handle}"`, start);
+ return prefix + suffix;
+}
+
+// Reads the optional anchor and tag before a node. Returns null when there
+// are none.
+function readProperties(s, inFlow) {
+ let anchor = null;
+ let tag = null;
+ for (;;) {
+ const c = at(s, s.pos);
+ if (c === kAmpersand) {
+ if (anchor !== null) fail(s, 'A node can have only one anchor');
+ s.pos++;
+ anchor = readAnchorName(s);
+ } else if (c === kBang) {
+ if (tag !== null) fail(s, 'A node can have only one tag');
+ tag = readTag(s, inFlow);
+ } else {
+ break;
+ }
+ skipWhite(s);
+ const n = at(s, s.pos);
+ // Properties may be on a line of their own; the caller skips on.
+ if (isBreak(n) || n === kHash || n === kEOF) break;
+ }
+ if (anchor === null && tag === null) return null;
+ return { __proto__: null, anchor, tag };
+}
+
+// Joins two groups of properties of the same node.
+function mergeProperties(s, a, b, pos) {
+ if (a === null) return b;
+ if (b === null) return a;
+ if (a.anchor !== null && b.anchor !== null) fail(s, 'A node can have only one anchor', pos);
+ if (a.tag !== null && b.tag !== null) fail(s, 'A node can have only one tag', pos);
+ return {
+ __proto__: null,
+ anchor: a.anchor !== null ? a.anchor : b.anchor,
+ tag: a.tag !== null ? a.tag : b.tag,
+ };
+}
+
+function isCoreTag(tag) {
+ return tag.length > kCoreTagPrefix.length &&
+ StringPrototypeSlice(tag, 0, kCoreTagPrefix.length) === kCoreTagPrefix;
+}
+
+// Resolves a scalar, applying its tag, and registers its anchor.
+function finishScalar(s, text, kind, props, pos) {
+ const tag = props === null ? null : props.tag;
+ let value;
+ if (tag === null) {
+ value = kind === kPlain ? resolvePlain(text) : text;
+ } else if (tag === '!' || tag === `${kCoreTagPrefix}str`) {
+ value = text;
+ } else if (tag === `${kCoreTagPrefix}int`) {
+ if (!isIntegerText(text)) fail(s, `"${text}" is not an integer`, pos);
+ value = parseNumber(text);
+ } else if (tag === `${kCoreTagPrefix}float`) {
+ value = parseSpecialFloat(text);
+ if (value === undefined) value = parseNumber(text);
+ if (typeof value !== 'number') fail(s, `"${text}" is not a float`, pos);
+ } else if (tag === `${kCoreTagPrefix}bool`) {
+ value = resolvePlain(text);
+ if (typeof value !== 'boolean') fail(s, `"${text}" is not a boolean`, pos);
+ } else if (tag === `${kCoreTagPrefix}null`) {
+ value = resolvePlain(text);
+ if (value !== null) fail(s, `"${text}" is not null`, pos);
+ } else if (tag === `${kCoreTagPrefix}map` || tag === `${kCoreTagPrefix}seq`) {
+ fail(s, `Tag ${tag} cannot be applied to a scalar`, pos);
+ } else {
+ // A tag outside the core schema: the scalar stays a string.
+ value = text;
+ }
+ if (props !== null && props.anchor !== null) s.anchors.set(props.anchor, value);
+ return value;
+}
+
+const kScalarTags = ['str', 'int', 'float', 'bool', 'null'];
+
+function checkCollectionTag(s, tag, kind, pos) {
+ if (tag === null || !isCoreTag(tag)) return;
+ const name = StringPrototypeSlice(tag, kCoreTagPrefix.length);
+ const other = kind === kSequence ? 'map' : 'seq';
+ if (name === other || ArrayPrototypeIncludes(kScalarTags, name)) {
+ fail(s, `Tag ${tag} cannot be applied to a ${kind === kSequence ? 'sequence' : 'mapping'}`, pos);
+ }
+}
+
+// Applies properties that were read on a line before a flow collection.
+function applyCollectionProps(s, value, props, kind, pos) {
+ if (props === null) return value;
+ checkCollectionTag(s, props.tag, kind, pos);
+ if (props.anchor !== null) s.anchors.set(props.anchor, value);
+ return value;
+}
+
+function readAlias(s) {
+ const start = s.pos;
+ s.pos++; // *
+ const name = readAnchorName(s);
+ if (!s.anchors.has(name)) fail(s, `Unknown anchor "${name}"`, start);
+ return s.anchors.get(name);
+}
+
+// --- Flow collections ---------------------------------------------------------
+
+// Skips whitespace, breaks and comments inside a flow collection. The lines
+// of the collection must be indented more than the enclosing block node.
+function skipFlowSeparation(s, parentIndent) {
+ const breaks = skipSeparation(s);
+ if (at(s, s.pos) === kEOF) fail(s, 'Unterminated flow collection');
+ if (atDocumentMarker(s)) fail(s, 'Unexpected document marker inside a flow collection');
+ if (breaks > 0 && s.lineIndent <= parentIndent) fail(s, 'Bad indentation in a flow collection');
+}
+
+function readFlowSequence(s, parentIndent, props, lineAnchor = null) {
+ enter(s);
+ const start = s.pos;
+ s.pos++; // [
+ const result = [];
+ if (lineAnchor !== null) s.anchors.set(lineAnchor, result);
+ if (props !== null && props.anchor !== null) s.anchors.set(props.anchor, result);
+ for (;;) {
+ skipFlowSeparation(s, parentIndent);
+ if (at(s, s.pos) === kRightBracket) {
+ s.pos++;
+ break;
+ }
+ const entryPos = s.pos;
+ let explicit = false;
+ if (at(s, s.pos) === kQuestion && isBlank(at(s, s.pos + 1))) {
+ explicit = true;
+ s.pos++;
+ skipFlowSeparation(s, parentIndent);
+ }
+ const entry = readFlowPair(s, parentIndent, explicit);
+ if (entry === kNoNode) fail(s, 'Unexpected character in flow sequence', entryPos);
+ ArrayPrototypePush(result, entry);
+ skipFlowSeparation(s, parentIndent);
+ const c = at(s, s.pos);
+ if (c === kComma) {
+ s.pos++;
+ continue;
+ }
+ if (c === kRightBracket) {
+ s.pos++;
+ break;
+ }
+ fail(s, 'Expected "," or "]" in flow sequence');
+ }
+ if (props !== null) checkCollectionTag(s, props.tag, kSequence, start);
+ s.depth--;
+ s.multiline = false;
+ return result;
+}
+
+// Reads one flow sequence entry: a node, or a single-pair mapping "key: value".
+function readFlowPair(s, parentIndent, explicit) {
+ const keyPos = s.pos;
+ let key = readFlowNode(s, parentIndent);
+ const jsonLike = s.lastJsonLike;
+ const multiline = s.multiline;
+ skipWhite(s);
+ if (at(s, s.pos) === kColon) {
+ const next = at(s, s.pos + 1);
+ if (isBlank(next) || isFlowIndicator(next) || jsonLike) {
+ if (multiline && !jsonLike && !explicit) {
+ fail(s, 'Implicit mapping keys must be on a single line', keyPos);
+ }
+ if (key === kNoNode) key = null;
+ s.pos++;
+ skipFlowSeparation(s, parentIndent);
+ let value = kNoNode;
+ if (at(s, s.pos) !== kComma && at(s, s.pos) !== kRightBracket) {
+ value = readFlowNode(s, parentIndent);
+ }
+ if (value === kNoNode) value = null;
+ const pair = {};
+ setProperty(s, pair, keyToString(s, key, keyPos), value, keyPos);
+ return pair;
+ }
+ }
+ if (explicit) {
+ const pair = {};
+ setProperty(s, pair, keyToString(s, key === kNoNode ? null : key, keyPos), null, keyPos);
+ return pair;
+ }
+ return key;
+}
+
+function readFlowMapping(s, parentIndent, props, lineAnchor = null) {
+ enter(s);
+ const start = s.pos;
+ s.pos++; // {
+ const result = {};
+ if (lineAnchor !== null) s.anchors.set(lineAnchor, result);
+ if (props !== null && props.anchor !== null) s.anchors.set(props.anchor, result);
+ for (;;) {
+ skipFlowSeparation(s, parentIndent);
+ if (at(s, s.pos) === kRightBrace) {
+ s.pos++;
+ break;
+ }
+ let explicit = false;
+ if (at(s, s.pos) === kQuestion && isBlank(at(s, s.pos + 1))) {
+ explicit = true;
+ s.pos++;
+ skipFlowSeparation(s, parentIndent);
+ }
+ const keyPos = s.pos;
+ let key = kNoNode;
+ const c0 = at(s, s.pos);
+ const c1 = at(s, s.pos + 1);
+ if (!(c0 === kColon && (isBlank(c1) || isFlowIndicator(c1)))) {
+ key = readFlowNode(s, parentIndent);
+ }
+ const jsonLike = s.lastJsonLike;
+ if (key === kNoNode) {
+ if (!explicit && at(s, s.pos) !== kColon) {
+ fail(s, 'Unexpected character in flow mapping', keyPos);
+ }
+ key = null;
+ }
+ skipFlowSeparation(s, parentIndent);
+ let value = null;
+ if (at(s, s.pos) === kColon) {
+ const next = at(s, s.pos + 1);
+ if (!(isBlank(next) || isFlowIndicator(next) || jsonLike)) {
+ fail(s, 'Expected whitespace after ":" in flow mapping');
+ }
+ s.pos++;
+ skipFlowSeparation(s, parentIndent);
+ const c = at(s, s.pos);
+ if (c !== kComma && c !== kRightBrace) {
+ value = readFlowNode(s, parentIndent);
+ if (value === kNoNode) value = null;
+ }
+ }
+ setProperty(s, result, keyToString(s, key, keyPos), value, keyPos);
+ skipFlowSeparation(s, parentIndent);
+ const c = at(s, s.pos);
+ if (c === kComma) {
+ s.pos++;
+ continue;
+ }
+ if (c === kRightBrace) {
+ s.pos++;
+ break;
+ }
+ fail(s, 'Expected "," or "}" in flow mapping');
+ }
+ if (props !== null) checkCollectionTag(s, props.tag, kMapping, start);
+ s.depth--;
+ s.multiline = false;
+ return result;
+}
+
+// Reads a node inside a flow collection. Returns kNoNode when the position
+// holds no node (an empty entry or value).
+function readFlowNode(s, parentIndent) {
+ s.lastJsonLike = false;
+ const props = readProperties(s, true);
+ if (props !== null) skipFlowSeparation(s, parentIndent);
+ const pos = s.pos;
+ const c = at(s, s.pos);
+ switch (c) {
+ case kAsterisk:
+ if (props !== null) fail(s, 'An alias cannot have properties');
+ return readAlias(s);
+ case kLeftBracket: {
+ const value = readFlowSequence(s, parentIndent, props);
+ s.lastJsonLike = true;
+ return value;
+ }
+ case kLeftBrace: {
+ const value = readFlowMapping(s, parentIndent, props);
+ s.lastJsonLike = true;
+ return value;
+ }
+ case kSingleQuote:
+ s.lastJsonLike = true;
+ return finishScalar(s, readSingleQuoted(s, parentIndent), kQuoted, props, pos);
+ case kDoubleQuote:
+ s.lastJsonLike = true;
+ return finishScalar(s, readDoubleQuoted(s, parentIndent), kQuoted, props, pos);
+ case kPipe: case kGreater:
+ fail(s, 'Block scalars are not allowed in flow collections');
+ }
+ if (canStartPlain(s, c, true)) {
+ return finishScalar(s, readPlain(s, parentIndent, true, false), kPlain, props, pos);
+ }
+ if (props !== null) {
+ // Properties with no content: an empty scalar.
+ return finishScalar(s, '', kPlain, props, pos);
+ }
+ return kNoNode;
+}
+
+// --- Block collections -----------------------------------------------------
+
+// Whether the current position starts a block sequence entry.
+function atSequenceEntry(s) {
+ return at(s, s.pos) === kMinus && isBlank(at(s, s.pos + 1));
+}
+
+// Whether the current line is indented enough for a node of a collection at
+// parentIndent. Only spaces count as indentation.
+function indentedEnough(s, parentIndent, allowSameIndentSequence) {
+ if (s.lineIndent > parentIndent) return true;
+ return allowSameIndentSequence && s.lineIndent === parentIndent && atSequenceEntry(s);
+}
+
+// Whether the whitespace before pos on its line holds a tab.
+function tabBefore(s, pos) {
+ for (let p = pos - 1; p >= s.lineStart && isWhite(at(s, p)); p--) {
+ if (at(s, p) === kTab) return true;
+ }
+ return false;
+}
+
+// Composes a node in block context. parentIndent is the indentation of the
+// enclosing collection (-1 at the document root). The node may start on the
+// current line or on a following line indented more than the parent. With
+// allowSameIndentSequence, a block sequence may start at the parent's own
+// indentation, as a mapping value. With compact, a block collection may start
+// on the current line, after "-", "?" or an explicit ":". Returns kNoNode
+// when there is no node.
+function composeBlock(s, parentIndent, allowSameIndentSequence, compact) {
+ let breaks = skipSeparation(s);
+ if (at(s, s.pos) === kEOF || atDocumentMarker(s)) return kNoNode;
+ if (breaks > 0 && !indentedEnough(s, parentIndent, allowSameIndentSequence)) {
+ return kNoNode;
+ }
+ // Properties followed by a line break belong to the node on the next line,
+ // or to the collection whose first key follows them. Properties on the
+ // node's own line belong to that node, or to the first key.
+ let lineProps = null;
+ let propsStart = s.pos;
+ let props = readProperties(s, false);
+ while (props !== null) {
+ const more = skipSeparation(s);
+ if (more === 0 && at(s, s.pos) !== kEOF) break;
+ breaks += more;
+ lineProps = mergeProperties(s, lineProps, props, propsStart);
+ props = null;
+ if (at(s, s.pos) === kEOF || atDocumentMarker(s) ||
+ !indentedEnough(s, parentIndent, allowSameIndentSequence)) {
+ // Properties with no content: an empty scalar node.
+ return finishScalar(s, '', kPlain, lineProps, propsStart);
+ }
+ propsStart = s.pos;
+ props = readProperties(s, false);
+ }
+ const pos = s.pos;
+ const startLine = s.line;
+ const nodeStart = props === null ? pos : propsStart;
+ const indent = nodeStart - s.lineStart;
+ // A block collection cannot start on the line of the parent indicator
+ // unless the compact form is allowed, and never after a tab.
+ const collectionAllowed = breaks > 0 || compact;
+ const collectionAfterTab = tabBefore(s, nodeStart);
+ const c = at(s, s.pos);
+ let value;
+ switch (c) {
+ case kAsterisk: {
+ if (props !== null) fail(s, 'An alias cannot have properties', propsStart);
+ value = readAlias(s);
+ let p = s.pos;
+ while (isWhite(at(s, p))) p++;
+ if (at(s, p) === kColon && isBlank(at(s, p + 1))) {
+ if (collectionAfterTab) fail(s, 'Tabs cannot be used for indentation', pos);
+ if (!collectionAllowed) fail(s, 'A block mapping is not allowed here', pos);
+ s.pos = p;
+ return readBlockMapping(s, indent, lineProps, value, pos);
+ }
+ if (lineProps !== null) fail(s, 'An alias cannot have properties', pos);
+ return value;
+ }
+ case kLeftBracket:
+ value = readFlowSequence(s, parentIndent, props, lineProps === null ? null : lineProps.anchor);
+ break;
+ case kLeftBrace:
+ value = readFlowMapping(s, parentIndent, props, lineProps === null ? null : lineProps.anchor);
+ break;
+ case kSingleQuote:
+ value = readSingleQuoted(s, parentIndent);
+ break;
+ case kDoubleQuote:
+ value = readDoubleQuoted(s, parentIndent);
+ break;
+ case kPipe: case kGreater:
+ return finishScalar(s, readBlockScalar(s, parentIndent),
+ kQuoted, mergeProperties(s, lineProps, props, pos), pos);
+ case kMinus:
+ if (isBlank(at(s, s.pos + 1))) {
+ if (collectionAfterTab) fail(s, 'Tabs cannot be used for indentation', pos);
+ if (!collectionAllowed || props !== null) {
+ fail(s, 'A block sequence is not allowed here', pos);
+ }
+ return readBlockSequence(s, indent, lineProps);
+ }
+ break;
+ case kQuestion:
+ if (isBlank(at(s, s.pos + 1))) {
+ if (collectionAfterTab) fail(s, 'Tabs cannot be used for indentation', pos);
+ if (!collectionAllowed || props !== null) {
+ fail(s, 'A block mapping is not allowed here', pos);
+ }
+ return readBlockMapping(s, indent, lineProps, kNoNode, pos);
+ }
+ break;
+ case kColon:
+ if (isBlank(at(s, s.pos + 1))) {
+ // An empty key, which takes the properties on its line.
+ if (collectionAfterTab) fail(s, 'Tabs cannot be used for indentation', pos);
+ if (!collectionAllowed) fail(s, 'A block mapping is not allowed here', pos);
+ const key = finishScalar(s, '', kPlain, props, pos);
+ return readBlockMapping(s, indent, lineProps, key, pos);
+ }
+ break;
+ }
+ if (value === undefined) {
+ if (!canStartPlain(s, c, false)) {
+ fail(s, `Unexpected character "${StringFromCharCode(c)}"`);
+ }
+ value = readPlain(s, parentIndent, false, false);
+ }
+ // A scalar or flow collection on one line followed by ":" is the first
+ // key of a block mapping.
+ let p = s.pos;
+ while (isWhite(at(s, p))) p++;
+ if (at(s, p) === kColon && isBlank(at(s, p + 1))) {
+ if (s.line !== startLine) fail(s, 'Implicit mapping keys must be on a single line', pos);
+ if (collectionAfterTab) fail(s, 'Tabs cannot be used for indentation', pos);
+ if (!collectionAllowed) fail(s, 'A block mapping is not allowed here', pos);
+ s.pos = p;
+ const key = finishNode(s, value, c, props, pos);
+ return readBlockMapping(s, indent, lineProps, key, pos);
+ }
+ if (c === kLeftBracket || c === kLeftBrace) {
+ const kind = c === kLeftBracket ? kSequence : kMapping;
+ return applyCollectionProps(s, value, lineProps, kind, pos);
+ }
+ return finishNode(s, value, c, mergeProperties(s, lineProps, props, pos), pos);
+}
+
+// Resolves a node that was read as raw scalar text or as a flow collection.
+function finishNode(s, value, c, props, pos) {
+ if (c === kLeftBracket || c === kLeftBrace) return value;
+ const kind = c === kSingleQuote || c === kDoubleQuote ? kQuoted : kPlain;
+ return finishScalar(s, value, kind, props, pos);
+}
+
+// Reads a block sequence whose entries are at the given indentation.
+function readBlockSequence(s, indent, props) {
+ enter(s);
+ const start = s.pos;
+ const result = [];
+ if (props !== null && props.anchor !== null) s.anchors.set(props.anchor, result);
+ for (;;) {
+ // At "-".
+ s.pos++;
+ const entry = composeBlock(s, indent, false, true);
+ ArrayPrototypePush(result, entry === kNoNode ? null : entry);
+ if (!nextLineAt(s, indent) || !atSequenceEntry(s)) break;
+ }
+ if (props !== null) checkCollectionTag(s, props.tag, kSequence, start);
+ s.depth--;
+ s.multiline = true;
+ return result;
+}
+
+// After a collection entry, moves to the next line at the collection's
+// indentation. Returns false when the collection ends there. Lines indented
+// more than the collection are an error.
+function nextLineAt(s, indent) {
+ const pos = s.pos;
+ const breaks = skipSeparation(s);
+ if (at(s, s.pos) === kEOF || atDocumentMarker(s)) return false;
+ const atLineContent = s.pos === s.lineStart + s.lineIndent;
+ if (!atLineContent) {
+ if (tabBefore(s, s.pos)) fail(s, 'Tabs cannot be used for indentation');
+ if (breaks === 0) fail(s, 'Unexpected content after a collection entry', pos);
+ }
+ if (s.lineIndent < indent) return false;
+ if (s.lineIndent > indent) fail(s, 'Bad indentation');
+ return true;
+}
+
+// Reads a block mapping at the given indentation. firstKey is the already
+// read first key, with the position at its ":", or kNoNode when the mapping
+// starts with "?".
+function readBlockMapping(s, indent, props, firstKey, start) {
+ enter(s);
+ const result = {};
+ if (props !== null && props.anchor !== null) s.anchors.set(props.anchor, result);
+ let key = firstKey;
+ let keyPos = start;
+ let first = true;
+ for (;;) {
+ if (!first || key === kNoNode) {
+ keyPos = s.pos;
+ if (at(s, s.pos) === kQuestion && isBlank(at(s, s.pos + 1))) {
+ // Explicit key, possibly a collection on the following lines. Its
+ // value, if any, starts with ":" at the mapping indentation.
+ s.pos++;
+ key = composeBlock(s, indent, true, true);
+ if (key === kNoNode) key = null;
+ if (!nextLineAt(s, indent)) {
+ setProperty(s, result, keyToString(s, key, keyPos), null, keyPos);
+ break;
+ }
+ if (!(at(s, s.pos) === kColon && isBlank(at(s, s.pos + 1)))) {
+ setProperty(s, result, keyToString(s, key, keyPos), null, keyPos);
+ first = false;
+ continue;
+ }
+ s.pos++;
+ let value = composeBlock(s, indent, true, true);
+ if (value === kNoNode) value = null;
+ setProperty(s, result, keyToString(s, key, keyPos), value, keyPos);
+ if (!nextLineAt(s, indent)) break;
+ first = false;
+ continue;
+ }
+ key = readImplicitKey(s, indent);
+ }
+ first = false;
+ // At ":".
+ s.pos++;
+ let value = composeBlock(s, indent, true, false);
+ if (value === kNoNode) value = null;
+ setProperty(s, result, keyToString(s, key, keyPos), value, keyPos);
+ if (!nextLineAt(s, indent)) break;
+ if (atSequenceEntry(s)) fail(s, 'Unexpected sequence entry in a mapping');
+ }
+ if (props !== null) checkCollectionTag(s, props.tag, kMapping, start);
+ s.depth--;
+ s.multiline = true;
+ return result;
+}
+
+// Reads an implicit key: a single-line node followed by ":", and leaves the
+// position at the ":".
+function readImplicitKey(s, indent) {
+ const propsStart = s.pos;
+ const props = readProperties(s, false);
+ const pos = s.pos;
+ const c = at(s, s.pos);
+ let value;
+ switch (c) {
+ case kAsterisk:
+ if (props !== null) fail(s, 'An alias cannot have properties', propsStart);
+ value = readAlias(s);
+ skipWhite(s);
+ if (at(s, s.pos) !== kColon || !isBlank(at(s, s.pos + 1))) {
+ fail(s, 'Expected ":" after mapping key', pos);
+ }
+ return value;
+ case kColon:
+ // An empty key.
+ if (isBlank(at(s, s.pos + 1))) return finishScalar(s, '', kPlain, props, pos);
+ break;
+ case kLeftBracket:
+ value = readFlowSequence(s, indent, props);
+ break;
+ case kLeftBrace:
+ value = readFlowMapping(s, indent, props);
+ break;
+ case kSingleQuote:
+ value = readSingleQuoted(s, indent);
+ break;
+ case kDoubleQuote:
+ value = readDoubleQuoted(s, indent);
+ break;
+ }
+ if (value === undefined) {
+ if (!canStartPlain(s, c, false)) fail(s, 'Expected a mapping key', pos);
+ value = readPlain(s, indent, false, true);
+ }
+ if (s.multiline) fail(s, 'Implicit mapping keys must be on a single line', pos);
+ skipWhite(s);
+ if (at(s, s.pos) !== kColon || !isBlank(at(s, s.pos + 1))) {
+ fail(s, 'Expected ":" after mapping key', pos);
+ }
+ return finishNode(s, value, c, props, pos);
+}
+
+// --- Documents -------------------------------------------------------------
+
+function readDirective(s) {
+ const start = s.pos;
+ s.pos++; // %
+ const nameStart = s.pos;
+ while (!isBlank(at(s, s.pos))) s.pos++;
+ const name = StringPrototypeSlice(s.input, nameStart, s.pos);
+ const args = [];
+ for (;;) {
+ skipWhite(s);
+ const c = at(s, s.pos);
+ if (c === kEOF || isBreak(c) || c === kHash) break;
+ const argStart = s.pos;
+ while (!isBlank(at(s, s.pos))) s.pos++;
+ ArrayPrototypePush(args, StringPrototypeSlice(s.input, argStart, s.pos));
+ }
+ if (at(s, s.pos) === kHash) skipToEndOfLine(s);
+ if (name === 'YAML') {
+ if (s.version !== null) fail(s, 'Repeated %YAML directive', start);
+ if (args.length !== 1) fail(s, 'The %YAML directive takes one argument', start);
+ const version = args[0];
+ let valid = version.length >= 3 &&
+ isDigit(StringPrototypeCharCodeAt(version, 0)) &&
+ StringPrototypeCharCodeAt(version, 1) === kDot;
+ for (let i = 2; valid && i < version.length; i++) {
+ valid = isDigit(StringPrototypeCharCodeAt(version, i));
+ }
+ if (!valid) fail(s, `Invalid YAML version "${version}"`, start);
+ if (StringPrototypeCharCodeAt(version, 0) !== 0x31) {
+ fail(s, `Unsupported YAML version "${version}"`, start);
+ }
+ s.version = version;
+ } else if (name === 'TAG') {
+ if (args.length !== 2) fail(s, 'The %TAG directive takes two arguments', start);
+ const { 0: handle, 1: prefix } = args;
+ if (StringPrototypeCharCodeAt(handle, 0) !== kBang ||
+ (handle.length > 1 && StringPrototypeCharCodeAt(handle, handle.length - 1) !== kBang)) {
+ fail(s, `Invalid tag handle "${handle}"`, start);
+ }
+ if (s.tagHandles.has(handle)) fail(s, `Repeated tag handle "${handle}"`, start);
+ s.tagHandles.set(handle, decodeTagEscapes(s, prefix));
+ }
+ // Other directives are reserved and ignored.
+}
+
+// Reads one document. Returns kNoNode at the end of the stream.
+function readDocument(s) {
+ s.anchors.clear();
+ s.tagHandles.clear();
+ s.version = null;
+ let hasDirectives = false;
+ for (;;) {
+ skipSeparation(s);
+ if (at(s, s.pos) === kPercent && s.pos === s.lineStart) {
+ readDirective(s);
+ hasDirectives = true;
+ continue;
+ }
+ break;
+ }
+ let explicitStart = false;
+ if (at(s, s.pos) === kEOF) {
+ if (hasDirectives) fail(s, 'Directives must be followed by "---"');
+ return kNoNode;
+ }
+ if (atDocumentMarker(s)) {
+ if (at(s, s.pos) === kDot) {
+ if (hasDirectives) fail(s, 'Directives must be followed by "---"');
+ skipDocumentEnd(s);
+ return kNoNode;
+ }
+ s.pos += 3;
+ explicitStart = true;
+ } else if (hasDirectives) {
+ fail(s, 'Directives must be followed by "---"');
+ }
+ let root = composeBlock(s, -1, true, !explicitStart);
+ if (root === kNoNode) root = null;
+ // After the root node: whitespace, comments, then a document marker or EOF.
+ skipSeparation(s);
+ const c = at(s, s.pos);
+ if (c !== kEOF && !atDocumentMarker(s)) {
+ fail(s, 'Unexpected content after the document root');
+ }
+ s.explicitEnd = false;
+ if (c === kDot) {
+ skipDocumentEnd(s);
+ s.explicitEnd = true;
+ }
+ return root;
+}
+
+// Consumes "..." markers. Only a comment may follow one on its line.
+function skipDocumentEnd(s) {
+ for (;;) {
+ s.pos += 3;
+ skipWhite(s);
+ const c = at(s, s.pos);
+ if (c === kHash) skipToEndOfLine(s);
+ else if (c !== kEOF && !isBreak(c)) fail(s, 'Unexpected content after "..."');
+ skipSeparation(s);
+ if (!(atDocumentMarker(s) && at(s, s.pos) === kDot)) break;
+ }
+}
+
+function skipBOM(s) {
+ if (at(s, s.pos) === kBOM) {
+ s.pos++;
+ s.lineStart = s.pos;
+ while (at(s, s.pos) === kSpace) {
+ s.pos++;
+ s.lineIndent++;
+ }
+ }
+}
+
+// YAML allows tab, line feed, carriage return, the printable characters of
+// the BMP except the C1 controls other than NEL, and the supplementary planes,
+// which are surrogate pairs here.
+function checkPrintable(s) {
+ for (let i = 0; i < s.length; i++) {
+ const c = StringPrototypeCharCodeAt(s.input, i);
+ if (c >= kSpace && c < 0x7F) continue;
+ if (c === kTab || c === kLF || c === kCR || c === 0x85) continue;
+ if ((c >= 0xA0 && c < 0xD800) || (c >= 0xE000 && c < 0xFFFE)) continue;
+ if (c >= 0xD800 && c < 0xDC00) {
+ const next = at(s, i + 1);
+ if (next >= 0xDC00 && next < 0xE000) {
+ i++;
+ continue;
+ }
+ }
+ fail(s, `Character U+${NumberPrototypeToString(c, 16)} is not allowed in YAML`, i);
+ }
+}
+
+// Parses a YAML stream and returns its documents as an array.
+function parseDocuments(text) {
+ const s = new State(text);
+ checkPrintable(s);
+ const documents = [];
+ while (at(s, s.pos) === kSpace) {
+ s.pos++;
+ s.lineIndent++;
+ }
+ skipBOM(s);
+ for (;;) {
+ const document = readDocument(s);
+ if (document === kNoNode) {
+ if (at(s, s.pos) === kEOF) break;
+ // A "..." before any document.
+ continue;
+ }
+ ArrayPrototypePush(documents, document);
+ if (at(s, s.pos) === kEOF) break;
+ // A byte order mark may also precede each document.
+ if (s.explicitEnd) skipBOM(s);
+ }
+ return documents;
+}
+
+module.exports = {
+ parseDocuments,
+};
diff --git a/lib/yaml.js b/lib/yaml.js
new file mode 100644
index 000000000000..316de289b920
--- /dev/null
+++ b/lib/yaml.js
@@ -0,0 +1,47 @@
+'use strict';
+
+const {
+ codes: {
+ ERR_INVALID_ARG_TYPE,
+ },
+} = require('internal/errors');
+const { emitExperimentalWarning } = require('internal/util');
+const {
+ isAnyArrayBuffer,
+ isArrayBufferView,
+} = require('internal/util/types');
+const { parseDocuments } = require('internal/yaml/parser');
+
+emitExperimentalWarning('YAML');
+
+let TextDecoder;
+function decode(input) {
+ TextDecoder ??= require('internal/encoding').TextDecoder;
+ return new TextDecoder('utf-8').decode(input);
+}
+
+/**
+ * Parses a YAML stream. A stream with one document yields its value, a
+ * stream with several documents yields an array of their values.
+ * @param {string | ArrayBuffer | SharedArrayBuffer | ArrayBufferView} input
+ * @returns {unknown}
+ */
+function parse(input) {
+ let text;
+ if (typeof input === 'string') {
+ text = input;
+ } else if (isArrayBufferView(input) || isAnyArrayBuffer(input)) {
+ text = decode(input);
+ } else {
+ throw new ERR_INVALID_ARG_TYPE(
+ 'input', ['string', 'ArrayBuffer', 'SharedArrayBuffer', 'ArrayBufferView'], input);
+ }
+ const documents = parseDocuments(text);
+ if (documents.length === 0) return null;
+ if (documents.length === 1) return documents[0];
+ return documents;
+}
+
+module.exports = {
+ parse,
+};
diff --git a/src/node_builtins.cc b/src/node_builtins.cc
index 21af988c95d8..97a716a64bda 100644
--- a/src/node_builtins.cc
+++ b/src/node_builtins.cc
@@ -168,6 +168,7 @@ BuiltinLoader::BuiltinCategories BuiltinLoader::GetBuiltinCategories() const {
"sys", // Deprecated.
"vfs", // Experimental.
"wasi", // Experimental.
+ "yaml", // Experimental.
"zlib/iter", // Experimental.
#if !HAVE_SQLITE
"internal/webstorage", // Experimental.
diff --git a/src/node_options.cc b/src/node_options.cc
index 4ba4801ad1dc..e2e7584cab5c 100644
--- a/src/node_options.cc
+++ b/src/node_options.cc
@@ -814,6 +814,10 @@ EnvironmentOptionsParser::EnvironmentOptionsParser() {
BOOL_FIELD(experimental_vfs),
kAllowedInEnvvar,
true);
+ AddOption("--experimental-yaml",
+ "experimental node:yaml module",
+ BOOL_FIELD(experimental_yaml),
+ kAllowedInEnvvar);
// Choosing the entry point is the command line's alone: an environment
// variable must not be able to redirect what a `node ` invocation runs,
// so this is rejected in NODE_OPTIONS. The source and whether to run from it
diff --git a/src/node_options.h b/src/node_options.h
index f8aa58253862..b04d6a097ce6 100644
--- a/src/node_options.h
+++ b/src/node_options.h
@@ -216,6 +216,7 @@ class EnvironmentOptions : public Options {
DEFINE_BOOL_FIELD(experimental_sqlite) = HAVE_SQLITE;
DEFINE_BOOL_FIELD(experimental_stream_iter) = EXPERIMENTALS_DEFAULT_VALUE;
DEFINE_BOOL_FIELD(experimental_vfs) = true;
+ DEFINE_BOOL_FIELD(experimental_yaml) = EXPERIMENTALS_DEFAULT_VALUE;
DEFINE_BOOL_FIELD(vfs_load) = false;
DEFINE_BOOL_FIELD(sea_vfs_archive) = false;
DEFINE_BOOL_FIELD(webstorage) = HAVE_SQLITE;
diff --git a/test/fixtures/yaml-test-suite.json b/test/fixtures/yaml-test-suite.json
new file mode 100644
index 000000000000..564d5515e20b
--- /dev/null
+++ b/test/fixtures/yaml-test-suite.json
@@ -0,0 +1,4330 @@
+[
+ {
+ "id": "229Q",
+ "name": "Spec Example 2.4. Sequence of Mappings",
+ "yaml": "-\n name: Mark McGwire\n hr: 65\n avg: 0.278\n-\n name: Sammy Sosa\n hr: 63\n avg: 0.288\n",
+ "error": false,
+ "json": [
+ [
+ {
+ "name": "Mark McGwire",
+ "hr": 65,
+ "avg": 0.278
+ },
+ {
+ "name": "Sammy Sosa",
+ "hr": 63,
+ "avg": 0.288
+ }
+ ]
+ ]
+ },
+ {
+ "id": "236B",
+ "name": "Invalid value after mapping",
+ "yaml": "foo:\n bar\ninvalid\n",
+ "error": true
+ },
+ {
+ "id": "26DV",
+ "name": "Whitespace around colon in mappings",
+ "yaml": "\"top1\" : \n \"key1\" : &alias1 scalar1\n'top2' : \n 'key2' : &alias2 scalar2\ntop3: &node3 \n *alias1 : scalar3\ntop4: \n *alias2 : scalar4\ntop5 : \n scalar5\ntop6: \n &anchor6 'key6' : scalar6\n",
+ "error": false,
+ "json": [
+ {
+ "top1": {
+ "key1": "scalar1"
+ },
+ "top2": {
+ "key2": "scalar2"
+ },
+ "top3": {
+ "scalar1": "scalar3"
+ },
+ "top4": {
+ "scalar2": "scalar4"
+ },
+ "top5": "scalar5",
+ "top6": {
+ "key6": "scalar6"
+ }
+ }
+ ]
+ },
+ {
+ "id": "27NA",
+ "name": "Spec Example 5.9. Directive Indicator",
+ "yaml": "%YAML 1.2\n--- text\n",
+ "error": false,
+ "json": [
+ "text"
+ ]
+ },
+ {
+ "id": "2AUY",
+ "name": "Tags in Block Sequence",
+ "yaml": " - !!str a\n - b\n - !!int 42\n - d\n",
+ "error": false,
+ "json": [
+ [
+ "a",
+ "b",
+ 42,
+ "d"
+ ]
+ ]
+ },
+ {
+ "id": "2CMS",
+ "name": "Invalid mapping in plain multiline",
+ "yaml": "this\n is\n invalid: x\n",
+ "error": true
+ },
+ {
+ "id": "2EBW",
+ "name": "Allowed characters in keys",
+ "yaml": "a!\"#$%&'()*+,-./09:;<=>?@AZ[\\]^_`az{|}~: safe\n?foo: safe question mark\n:foo: safe colon\n-foo: safe dash\nthis is#not: a comment\n",
+ "error": false,
+ "json": [
+ {
+ "a!\"#$%&'()*+,-./09:;<=>?@AZ[\\]^_`az{|}~": "safe",
+ "?foo": "safe question mark",
+ ":foo": "safe colon",
+ "-foo": "safe dash",
+ "this is#not": "a comment"
+ }
+ ]
+ },
+ {
+ "id": "2G84/00",
+ "name": "Literal modifers",
+ "yaml": "--- |0\n",
+ "error": true
+ },
+ {
+ "id": "2G84/01",
+ "name": "Literal modifers",
+ "yaml": "--- |10\n",
+ "error": true
+ },
+ {
+ "id": "2G84/02",
+ "name": "Literal modifers",
+ "yaml": "--- |1-",
+ "error": false,
+ "json": [
+ ""
+ ]
+ },
+ {
+ "id": "2G84/03",
+ "name": "Literal modifers",
+ "yaml": "--- |1+",
+ "error": false,
+ "json": [
+ ""
+ ]
+ },
+ {
+ "id": "2JQS",
+ "name": "Block Mapping with Missing Keys",
+ "yaml": ": a\n: b\n",
+ "error": false
+ },
+ {
+ "id": "2LFX",
+ "name": "Spec Example 6.13. Reserved Directives [1.3]",
+ "yaml": "%FOO bar baz # Should be ignored\n # with a warning.\n---\n\"foo\"\n",
+ "error": false,
+ "json": [
+ "foo"
+ ]
+ },
+ {
+ "id": "2SXE",
+ "name": "Anchors With Colon in Name",
+ "yaml": "&a: key: &a value\nfoo:\n *a:\n",
+ "error": false,
+ "json": [
+ {
+ "key": "value",
+ "foo": "key"
+ }
+ ]
+ },
+ {
+ "id": "2XXW",
+ "name": "Spec Example 2.25. Unordered Sets",
+ "yaml": "# Sets are represented as a\n# Mapping where each key is\n# associated with a null value\n--- !!set\n? Mark McGwire\n? Sammy Sosa\n? Ken Griff\n",
+ "error": false,
+ "json": [
+ {
+ "Mark McGwire": null,
+ "Sammy Sosa": null,
+ "Ken Griff": null
+ }
+ ]
+ },
+ {
+ "id": "33X3",
+ "name": "Three explicit integers in a block sequence",
+ "yaml": "---\n- !!int 1\n- !!int -2\n- !!int 33\n",
+ "error": false,
+ "json": [
+ [
+ 1,
+ -2,
+ 33
+ ]
+ ]
+ },
+ {
+ "id": "35KP",
+ "name": "Tags for Root Objects",
+ "yaml": "--- !!map\n? a\n: b\n--- !!seq\n- !!str c\n--- !!str\nd\ne\n",
+ "error": false,
+ "json": [
+ {
+ "a": "b"
+ },
+ [
+ "c"
+ ],
+ "d e"
+ ]
+ },
+ {
+ "id": "36F6",
+ "name": "Multiline plain scalar with empty line",
+ "yaml": "---\nplain: a\n b\n\n c\n",
+ "error": false,
+ "json": [
+ {
+ "plain": "a b\nc"
+ }
+ ]
+ },
+ {
+ "id": "3ALJ",
+ "name": "Block Sequence in Block Sequence",
+ "yaml": "- - s1_i1\n - s1_i2\n- s2\n",
+ "error": false,
+ "json": [
+ [
+ [
+ "s1_i1",
+ "s1_i2"
+ ],
+ "s2"
+ ]
+ ]
+ },
+ {
+ "id": "3GZX",
+ "name": "Spec Example 7.1. Alias Nodes",
+ "yaml": "First occurrence: &anchor Foo\nSecond occurrence: *anchor\nOverride anchor: &anchor Bar\nReuse anchor: *anchor\n",
+ "error": false,
+ "json": [
+ {
+ "First occurrence": "Foo",
+ "Second occurrence": "Foo",
+ "Override anchor": "Bar",
+ "Reuse anchor": "Bar"
+ }
+ ]
+ },
+ {
+ "id": "3HFZ",
+ "name": "Invalid content after document end marker",
+ "yaml": "---\nkey: value\n... invalid\n",
+ "error": true
+ },
+ {
+ "id": "3MYT",
+ "name": "Plain Scalar looking like key, comment, anchor and tag",
+ "yaml": "---\nk:#foo\n &a !t s\n",
+ "error": false,
+ "json": [
+ "k:#foo &a !t s"
+ ]
+ },
+ {
+ "id": "3R3P",
+ "name": "Single block sequence with anchor",
+ "yaml": "&sequence\n- a\n",
+ "error": false,
+ "json": [
+ [
+ "a"
+ ]
+ ]
+ },
+ {
+ "id": "3RLN/00",
+ "name": "Leading tabs in double quoted",
+ "yaml": "\"1 leading\n \\ttab\"\n",
+ "error": false,
+ "json": [
+ "1 leading \ttab"
+ ]
+ },
+ {
+ "id": "3RLN/01",
+ "name": "Leading tabs in double quoted",
+ "yaml": "\"2 leading\n \\\ttab\"\n",
+ "error": false,
+ "json": [
+ "2 leading \ttab"
+ ]
+ },
+ {
+ "id": "3RLN/02",
+ "name": "Leading tabs in double quoted",
+ "yaml": "\"3 leading\n \ttab\"\n",
+ "error": false,
+ "json": [
+ "3 leading tab"
+ ]
+ },
+ {
+ "id": "3RLN/03",
+ "name": "Leading tabs in double quoted",
+ "yaml": "\"4 leading\n \\t tab\"\n",
+ "error": false,
+ "json": [
+ "4 leading \t tab"
+ ]
+ },
+ {
+ "id": "3RLN/04",
+ "name": "Leading tabs in double quoted",
+ "yaml": "\"5 leading\n \\\t tab\"\n",
+ "error": false,
+ "json": [
+ "5 leading \t tab"
+ ]
+ },
+ {
+ "id": "3RLN/05",
+ "name": "Leading tabs in double quoted",
+ "yaml": "\"6 leading\n \t tab\"\n",
+ "error": false,
+ "json": [
+ "6 leading tab"
+ ]
+ },
+ {
+ "id": "3UYS",
+ "name": "Escaped slash in double quotes",
+ "yaml": "escaped slash: \"a\\/b\"\n",
+ "error": false,
+ "json": [
+ {
+ "escaped slash": "a/b"
+ }
+ ]
+ },
+ {
+ "id": "4ABK",
+ "name": "Flow Mapping Separate Values",
+ "yaml": "{\nunquoted : \"separate\",\nhttp://foo.com,\nomitted value:,\n}\n",
+ "error": false
+ },
+ {
+ "id": "4CQQ",
+ "name": "Spec Example 2.18. Multi-line Flow Scalars",
+ "yaml": "plain:\n This unquoted scalar\n spans many lines.\n\nquoted: \"So does this\n quoted scalar.\\n\"\n",
+ "error": false,
+ "json": [
+ {
+ "plain": "This unquoted scalar spans many lines.",
+ "quoted": "So does this quoted scalar.\n"
+ }
+ ]
+ },
+ {
+ "id": "4EJS",
+ "name": "Invalid tabs as indendation in a mapping",
+ "yaml": "---\na:\n\tb:\n\t\tc: value\n",
+ "error": true
+ },
+ {
+ "id": "4FJ6",
+ "name": "Nested implicit complex keys",
+ "yaml": "---\n[\n [ a, [ [[b,c]]: d, e]]: 23\n]\n",
+ "error": false
+ },
+ {
+ "id": "4GC6",
+ "name": "Spec Example 7.7. Single Quoted Characters",
+ "yaml": "'here''s to \"quotes\"'\n",
+ "error": false,
+ "json": [
+ "here's to \"quotes\""
+ ]
+ },
+ {
+ "id": "4H7K",
+ "name": "Flow sequence with invalid extra closing bracket",
+ "yaml": "---\n[ a, b, c ] ]\n",
+ "error": true
+ },
+ {
+ "id": "4HVU",
+ "name": "Wrong indendation in Sequence",
+ "yaml": "key:\n - ok\n - also ok\n - wrong\n",
+ "error": true
+ },
+ {
+ "id": "4JVG",
+ "name": "Scalar value with two anchors",
+ "yaml": "top1: &node1\n &k1 key1: val1\ntop2: &node2\n &v2 val2\n",
+ "error": true
+ },
+ {
+ "id": "4MUZ/00",
+ "name": "Flow mapping colon on line after key",
+ "yaml": "{\"foo\"\n: \"bar\"}\n",
+ "error": false,
+ "json": [
+ {
+ "foo": "bar"
+ }
+ ]
+ },
+ {
+ "id": "4MUZ/01",
+ "name": "Flow mapping colon on line after key",
+ "yaml": "{\"foo\"\n: bar}\n",
+ "error": false,
+ "json": [
+ {
+ "foo": "bar"
+ }
+ ]
+ },
+ {
+ "id": "4MUZ/02",
+ "name": "Flow mapping colon on line after key",
+ "yaml": "{foo\n: bar}\n",
+ "error": false,
+ "json": [
+ {
+ "foo": "bar"
+ }
+ ]
+ },
+ {
+ "id": "4Q9F",
+ "name": "Folded Block Scalar [1.3]",
+ "yaml": "--- >\n ab\n cd\n \n ef\n\n\n gh\n",
+ "error": false,
+ "json": [
+ "ab cd\nef\n\ngh\n"
+ ]
+ },
+ {
+ "id": "4QFQ",
+ "name": "Spec Example 8.2. Block Indentation Indicator [1.3]",
+ "yaml": "- |\n detected\n- >\n \n \n # detected\n- |1\n explicit\n- >\n detected\n",
+ "error": false,
+ "json": [
+ [
+ "detected\n",
+ "\n\n# detected\n",
+ " explicit\n",
+ "detected\n"
+ ]
+ ]
+ },
+ {
+ "id": "4RWC",
+ "name": "Trailing spaces after flow collection",
+ "yaml": " [1, 2, 3] \n ",
+ "error": false,
+ "json": [
+ [
+ 1,
+ 2,
+ 3
+ ]
+ ]
+ },
+ {
+ "id": "4UYU",
+ "name": "Colon in Double Quoted String",
+ "yaml": "\"foo: bar\\\": baz\"\n",
+ "error": false,
+ "json": [
+ "foo: bar\": baz"
+ ]
+ },
+ {
+ "id": "4V8U",
+ "name": "Plain scalar with backslashes",
+ "yaml": "---\nplain\\value\\with\\backslashes\n",
+ "error": false,
+ "json": [
+ "plain\\value\\with\\backslashes"
+ ]
+ },
+ {
+ "id": "4WA9",
+ "name": "Literal scalars",
+ "yaml": "- aaa: |2\n xxx\n bbb: |\n xxx\n",
+ "error": false,
+ "json": [
+ [
+ {
+ "aaa": "xxx\n",
+ "bbb": "xxx\n"
+ }
+ ]
+ ]
+ },
+ {
+ "id": "4ZYM",
+ "name": "Spec Example 6.4. Line Prefixes",
+ "yaml": "plain: text\n lines\nquoted: \"text\n \tlines\"\nblock: |\n text\n \tlines\n",
+ "error": false,
+ "json": [
+ {
+ "plain": "text lines",
+ "quoted": "text lines",
+ "block": "text\n \tlines\n"
+ }
+ ]
+ },
+ {
+ "id": "52DL",
+ "name": "Explicit Non-Specific Tag [1.3]",
+ "yaml": "---\n! a\n",
+ "error": false,
+ "json": [
+ "a"
+ ]
+ },
+ {
+ "id": "54T7",
+ "name": "Flow Mapping",
+ "yaml": "{foo: you, bar: far}\n",
+ "error": false,
+ "json": [
+ {
+ "foo": "you",
+ "bar": "far"
+ }
+ ]
+ },
+ {
+ "id": "55WF",
+ "name": "Invalid escape in double quoted string",
+ "yaml": "---\n\"\\.\"\n",
+ "error": true
+ },
+ {
+ "id": "565N",
+ "name": "Construct Binary",
+ "yaml": "canonical: !!binary \"\\\n R0lGODlhDAAMAIQAAP//9/X17unp5WZmZgAAAOfn515eXvPz7Y6OjuDg4J+fn5\\\n OTk6enp56enmlpaWNjY6Ojo4SEhP/++f/++f/++f/++f/++f/++f/++f/++f/+\\\n +f/++f/++f/++f/++f/++SH+Dk1hZGUgd2l0aCBHSU1QACwAAAAADAAMAAAFLC\\\n AgjoEwnuNAFOhpEMTRiggcz4BNJHrv/zCFcLiwMWYNG84BwwEeECcgggoBADs=\"\ngeneric: !!binary |\n R0lGODlhDAAMAIQAAP//9/X17unp5WZmZgAAAOfn515eXvPz7Y6OjuDg4J+fn5\n OTk6enp56enmlpaWNjY6Ojo4SEhP/++f/++f/++f/++f/++f/++f/++f/++f/+\n +f/++f/++f/++f/++f/++SH+Dk1hZGUgd2l0aCBHSU1QACwAAAAADAAMAAAFLC\n AgjoEwnuNAFOhpEMTRiggcz4BNJHrv/zCFcLiwMWYNG84BwwEeECcgggoBADs=\ndescription:\n The binary value above is a tiny arrow encoded as a gif image.\n",
+ "error": false,
+ "json": [
+ {
+ "canonical": "R0lGODlhDAAMAIQAAP//9/X17unp5WZmZgAAAOfn515eXvPz7Y6OjuDg4J+fn5OTk6enp56enmlpaWNjY6Ojo4SEhP/++f/++f/++f/++f/++f/++f/++f/++f/++f/++f/++f/++f/++f/++SH+Dk1hZGUgd2l0aCBHSU1QACwAAAAADAAMAAAFLCAgjoEwnuNAFOhpEMTRiggcz4BNJHrv/zCFcLiwMWYNG84BwwEeECcgggoBADs=",
+ "generic": "R0lGODlhDAAMAIQAAP//9/X17unp5WZmZgAAAOfn515eXvPz7Y6OjuDg4J+fn5\nOTk6enp56enmlpaWNjY6Ojo4SEhP/++f/++f/++f/++f/++f/++f/++f/++f/+\n+f/++f/++f/++f/++f/++SH+Dk1hZGUgd2l0aCBHSU1QACwAAAAADAAMAAAFLC\nAgjoEwnuNAFOhpEMTRiggcz4BNJHrv/zCFcLiwMWYNG84BwwEeECcgggoBADs=\n",
+ "description": "The binary value above is a tiny arrow encoded as a gif image."
+ }
+ ]
+ },
+ {
+ "id": "57H4",
+ "name": "Spec Example 8.22. Block Collection Nodes",
+ "yaml": "sequence: !!seq\n- entry\n- !!seq\n - nested\nmapping: !!map\n foo: bar\n",
+ "error": false,
+ "json": [
+ {
+ "sequence": [
+ "entry",
+ [
+ "nested"
+ ]
+ ],
+ "mapping": {
+ "foo": "bar"
+ }
+ }
+ ]
+ },
+ {
+ "id": "58MP",
+ "name": "Flow mapping edge cases",
+ "yaml": "{x: :x}\n",
+ "error": false,
+ "json": [
+ {
+ "x": ":x"
+ }
+ ]
+ },
+ {
+ "id": "5BVJ",
+ "name": "Spec Example 5.7. Block Scalar Indicators",
+ "yaml": "literal: |\n some\n text\nfolded: >\n some\n text\n",
+ "error": false,
+ "json": [
+ {
+ "literal": "some\ntext\n",
+ "folded": "some text\n"
+ }
+ ]
+ },
+ {
+ "id": "5C5M",
+ "name": "Spec Example 7.15. Flow Mappings",
+ "yaml": "- { one : two , three: four , }\n- {five: six,seven : eight}\n",
+ "error": false,
+ "json": [
+ [
+ {
+ "one": "two",
+ "three": "four"
+ },
+ {
+ "five": "six",
+ "seven": "eight"
+ }
+ ]
+ ]
+ },
+ {
+ "id": "5GBF",
+ "name": "Spec Example 6.5. Empty Lines",
+ "yaml": "Folding:\n \"Empty line\n \t\n as a line feed\"\nChomping: |\n Clipped empty lines\n \n\n",
+ "error": false,
+ "json": [
+ {
+ "Folding": "Empty line\nas a line feed",
+ "Chomping": "Clipped empty lines\n"
+ }
+ ]
+ },
+ {
+ "id": "5KJE",
+ "name": "Spec Example 7.13. Flow Sequence",
+ "yaml": "- [ one, two, ]\n- [three ,four]\n",
+ "error": false,
+ "json": [
+ [
+ [
+ "one",
+ "two"
+ ],
+ [
+ "three",
+ "four"
+ ]
+ ]
+ ]
+ },
+ {
+ "id": "5LLU",
+ "name": "Block scalar with wrong indented line after spaces only",
+ "yaml": "block scalar: >\n \n \n \n invalid\n",
+ "error": true
+ },
+ {
+ "id": "5MUD",
+ "name": "Colon and adjacent value on next line",
+ "yaml": "---\n{ \"foo\"\n :bar }\n",
+ "error": false,
+ "json": [
+ {
+ "foo": "bar"
+ }
+ ]
+ },
+ {
+ "id": "5NYZ",
+ "name": "Spec Example 6.9. Separated Comment",
+ "yaml": "key: # Comment\n value\n",
+ "error": false,
+ "json": [
+ {
+ "key": "value"
+ }
+ ]
+ },
+ {
+ "id": "5T43",
+ "name": "Colon at the beginning of adjacent flow scalar",
+ "yaml": "- { \"key\":value }\n- { \"key\"::value }\n",
+ "error": false,
+ "json": [
+ [
+ {
+ "key": "value"
+ },
+ {
+ "key": ":value"
+ }
+ ]
+ ]
+ },
+ {
+ "id": "5TRB",
+ "name": "Invalid document-start marker in doublequoted tring",
+ "yaml": "---\n\"\n---\n\"\n",
+ "error": true
+ },
+ {
+ "id": "5TYM",
+ "name": "Spec Example 6.21. Local Tag Prefix",
+ "yaml": "%TAG !m! !my-\n--- # Bulb here\n!m!light fluorescent\n...\n%TAG !m! !my-\n--- # Color here\n!m!light green\n",
+ "error": false,
+ "json": [
+ "fluorescent",
+ "green"
+ ]
+ },
+ {
+ "id": "5U3A",
+ "name": "Sequence on same Line as Mapping Key",
+ "yaml": "key: - a\n - b\n",
+ "error": true
+ },
+ {
+ "id": "5WE3",
+ "name": "Spec Example 8.17. Explicit Block Mapping Entries",
+ "yaml": "? explicit key # Empty value\n? |\n block key\n: - one # Explicit compact\n - two # block value\n",
+ "error": false,
+ "json": [
+ {
+ "explicit key": null,
+ "block key\n": [
+ "one",
+ "two"
+ ]
+ }
+ ]
+ },
+ {
+ "id": "62EZ",
+ "name": "Invalid block mapping key on same line as previous key",
+ "yaml": "---\nx: { y: z }in: valid\n",
+ "error": true
+ },
+ {
+ "id": "652Z",
+ "name": "Question mark at start of flow key",
+ "yaml": "{ ?foo: bar,\nbar: 42\n}\n",
+ "error": false,
+ "json": [
+ {
+ "?foo": "bar",
+ "bar": 42
+ }
+ ]
+ },
+ {
+ "id": "65WH",
+ "name": "Single Entry Block Sequence",
+ "yaml": "- foo\n",
+ "error": false,
+ "json": [
+ [
+ "foo"
+ ]
+ ]
+ },
+ {
+ "id": "6BCT",
+ "name": "Spec Example 6.3. Separation Spaces",
+ "yaml": "- foo:\t bar\n- - baz\n -\tbaz\n",
+ "error": false,
+ "json": [
+ [
+ {
+ "foo": "bar"
+ },
+ [
+ "baz",
+ "baz"
+ ]
+ ]
+ ]
+ },
+ {
+ "id": "6BFJ",
+ "name": "Mapping, key and flow sequence item anchors",
+ "yaml": "---\n&mapping\n&key [ &item a, b, c ]: value\n",
+ "error": false
+ },
+ {
+ "id": "6CA3",
+ "name": "Tab indented top flow",
+ "yaml": "\t[\n\t]\n",
+ "error": false,
+ "json": [
+ []
+ ]
+ },
+ {
+ "id": "6CK3",
+ "name": "Spec Example 6.26. Tag Shorthands",
+ "yaml": "%TAG !e! tag:example.com,2000:app/\n---\n- !local foo\n- !!str bar\n- !e!tag%21 baz\n",
+ "error": false,
+ "json": [
+ [
+ "foo",
+ "bar",
+ "baz"
+ ]
+ ]
+ },
+ {
+ "id": "6FWR",
+ "name": "Block Scalar Keep",
+ "yaml": "--- |+\n ab\n \n \n...\n",
+ "error": false,
+ "json": [
+ "ab\n\n \n"
+ ]
+ },
+ {
+ "id": "6H3V",
+ "name": "Backslashes in singlequotes",
+ "yaml": "'foo: bar\\': baz'\n",
+ "error": false,
+ "json": [
+ {
+ "foo: bar\\": "baz'"
+ }
+ ]
+ },
+ {
+ "id": "6HB6",
+ "name": "Spec Example 6.1. Indentation Spaces",
+ "yaml": " # Leading comment line spaces are\n # neither content nor indentation.\n \nNot indented:\n By one space: |\n By four\n spaces\n Flow style: [ # Leading spaces\n By two, # in flow style\n Also by two, # are neither\n \tStill by two # content nor\n ] # indentation.\n",
+ "error": false,
+ "json": [
+ {
+ "Not indented": {
+ "By one space": "By four\n spaces\n",
+ "Flow style": [
+ "By two",
+ "Also by two",
+ "Still by two"
+ ]
+ }
+ }
+ ]
+ },
+ {
+ "id": "6JQW",
+ "name": "Spec Example 2.13. In literals, newlines are preserved",
+ "yaml": "# ASCII Art\n--- |\n \\//||\\/||\n // || ||__\n",
+ "error": false,
+ "json": [
+ "\\//||\\/||\n// || ||__\n"
+ ]
+ },
+ {
+ "id": "6JTT",
+ "name": "Flow sequence without closing bracket",
+ "yaml": "---\n[ [ a, b, c ]\n",
+ "error": true
+ },
+ {
+ "id": "6JWB",
+ "name": "Tags for Block Objects",
+ "yaml": "foo: !!seq\n - !!str a\n - !!map\n key: !!str value\n",
+ "error": false,
+ "json": [
+ {
+ "foo": [
+ "a",
+ {
+ "key": "value"
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "id": "6KGN",
+ "name": "Anchor for empty node",
+ "yaml": "---\na: &anchor\nb: *anchor\n",
+ "error": false,
+ "json": [
+ {
+ "a": null,
+ "b": null
+ }
+ ]
+ },
+ {
+ "id": "6LVF",
+ "name": "Spec Example 6.13. Reserved Directives",
+ "yaml": "%FOO bar baz # Should be ignored\n # with a warning.\n--- \"foo\"\n",
+ "error": false,
+ "json": [
+ "foo"
+ ]
+ },
+ {
+ "id": "6M2F",
+ "name": "Aliases in Explicit Block Mapping",
+ "yaml": "? &a a\n: &b b\n: *a\n",
+ "error": false
+ },
+ {
+ "id": "6PBE",
+ "name": "Zero-indented sequences in explicit mapping keys",
+ "yaml": "---\n?\n- a\n- b\n:\n- c\n- d\n",
+ "error": false
+ },
+ {
+ "id": "6S55",
+ "name": "Invalid scalar at the end of sequence",
+ "yaml": "key:\n - bar\n - baz\n invalid\n",
+ "error": true
+ },
+ {
+ "id": "6SLA",
+ "name": "Allowed characters in quoted mapping key",
+ "yaml": "\"foo\\nbar:baz\\tx \\\\$%^&*()x\": 23\n'x\\ny:z\\tx $%^&*()x': 24\n",
+ "error": false,
+ "json": [
+ {
+ "foo\nbar:baz\tx \\$%^&*()x": 23,
+ "x\\ny:z\\tx $%^&*()x": 24
+ }
+ ]
+ },
+ {
+ "id": "6VJK",
+ "name": "Spec Example 2.15. Folded newlines are preserved for \"more indented\" and blank lines",
+ "yaml": ">\n Sammy Sosa completed another\n fine season with great stats.\n\n 63 Home Runs\n 0.288 Batting Average\n\n What a year!\n",
+ "error": false,
+ "json": [
+ "Sammy Sosa completed another fine season with great stats.\n\n 63 Home Runs\n 0.288 Batting Average\n\nWhat a year!\n"
+ ]
+ },
+ {
+ "id": "6WLZ",
+ "name": "Spec Example 6.18. Primary Tag Handle [1.3]",
+ "yaml": "# Private\n---\n!foo \"bar\"\n...\n# Global\n%TAG ! tag:example.com,2000:app/\n---\n!foo \"bar\"\n",
+ "error": false,
+ "json": [
+ "bar",
+ "bar"
+ ]
+ },
+ {
+ "id": "6WPF",
+ "name": "Spec Example 6.8. Flow Folding [1.3]",
+ "yaml": "---\n\"\n foo \n \n bar\n\n baz\n\"\n",
+ "error": false,
+ "json": [
+ " foo\nbar\nbaz "
+ ]
+ },
+ {
+ "id": "6XDY",
+ "name": "Two document start markers",
+ "yaml": "---\n---\n",
+ "error": false,
+ "json": [
+ null,
+ null
+ ]
+ },
+ {
+ "id": "6ZKB",
+ "name": "Spec Example 9.6. Stream",
+ "yaml": "Document\n---\n# Empty\n...\n%YAML 1.2\n---\nmatches %: 20\n",
+ "error": false,
+ "json": [
+ "Document",
+ null,
+ {
+ "matches %": 20
+ }
+ ]
+ },
+ {
+ "id": "735Y",
+ "name": "Spec Example 8.20. Block Node Types",
+ "yaml": "-\n \"flow in block\"\n- >\n Block scalar\n- !!map # Block collection\n foo : bar\n",
+ "error": false,
+ "json": [
+ [
+ "flow in block",
+ "Block scalar\n",
+ {
+ "foo": "bar"
+ }
+ ]
+ ]
+ },
+ {
+ "id": "74H7",
+ "name": "Tags in Implicit Mapping",
+ "yaml": "!!str a: b\nc: !!int 42\ne: !!str f\ng: h\n!!str 23: !!bool false\n",
+ "error": false,
+ "json": [
+ {
+ "a": "b",
+ "c": 42,
+ "e": "f",
+ "g": "h",
+ "23": false
+ }
+ ]
+ },
+ {
+ "id": "753E",
+ "name": "Block Scalar Strip [1.3]",
+ "yaml": "--- |-\n ab\n \n \n...\n",
+ "error": false,
+ "json": [
+ "ab"
+ ]
+ },
+ {
+ "id": "7A4E",
+ "name": "Spec Example 7.6. Double Quoted Lines",
+ "yaml": "\" 1st non-empty\n\n 2nd non-empty \n\t3rd non-empty \"\n",
+ "error": false,
+ "json": [
+ " 1st non-empty\n2nd non-empty 3rd non-empty "
+ ]
+ },
+ {
+ "id": "7BMT",
+ "name": "Node and Mapping Key Anchors [1.3]",
+ "yaml": "---\ntop1: &node1\n &k1 key1: one\ntop2: &node2 # comment\n key2: two\ntop3:\n &k3 key3: three\ntop4: &node4\n &k4 key4: four\ntop5: &node5\n key5: five\ntop6: &val6\n six\ntop7:\n &val7 seven\n",
+ "error": false,
+ "json": [
+ {
+ "top1": {
+ "key1": "one"
+ },
+ "top2": {
+ "key2": "two"
+ },
+ "top3": {
+ "key3": "three"
+ },
+ "top4": {
+ "key4": "four"
+ },
+ "top5": {
+ "key5": "five"
+ },
+ "top6": "six",
+ "top7": "seven"
+ }
+ ]
+ },
+ {
+ "id": "7BUB",
+ "name": "Spec Example 2.10. Node for \u201cSammy Sosa\u201d appears twice in this document",
+ "yaml": "---\nhr:\n - Mark McGwire\n # Following node labeled SS\n - &SS Sammy Sosa\nrbi:\n - *SS # Subsequent occurrence\n - Ken Griffey\n",
+ "error": false,
+ "json": [
+ {
+ "hr": [
+ "Mark McGwire",
+ "Sammy Sosa"
+ ],
+ "rbi": [
+ "Sammy Sosa",
+ "Ken Griffey"
+ ]
+ }
+ ]
+ },
+ {
+ "id": "7FWL",
+ "name": "Spec Example 6.24. Verbatim Tags",
+ "yaml": "! foo :\n ! baz\n",
+ "error": false,
+ "json": [
+ {
+ "foo": "baz"
+ }
+ ]
+ },
+ {
+ "id": "7LBH",
+ "name": "Multiline double quoted implicit keys",
+ "yaml": "\"a\\nb\": 1\n\"c\n d\": 1\n",
+ "error": true
+ },
+ {
+ "id": "7MNF",
+ "name": "Missing colon",
+ "yaml": "top1:\n key1: val1\ntop2\n",
+ "error": true
+ },
+ {
+ "id": "7T8X",
+ "name": "Spec Example 8.10. Folded Lines - 8.13. Final Empty Lines",
+ "yaml": ">\n\n folded\n line\n\n next\n line\n * bullet\n\n * list\n * lines\n\n last\n line\n\n# Comment\n",
+ "error": false,
+ "json": [
+ "\nfolded line\nnext line\n * bullet\n\n * list\n * lines\n\nlast line\n"
+ ]
+ },
+ {
+ "id": "7TMG",
+ "name": "Comment in flow sequence before comma",
+ "yaml": "---\n[ word1\n# comment\n, word2]\n",
+ "error": false,
+ "json": [
+ [
+ "word1",
+ "word2"
+ ]
+ ]
+ },
+ {
+ "id": "7W2P",
+ "name": "Block Mapping with Missing Values",
+ "yaml": "? a\n? b\nc:\n",
+ "error": false,
+ "json": [
+ {
+ "a": null,
+ "b": null,
+ "c": null
+ }
+ ]
+ },
+ {
+ "id": "7Z25",
+ "name": "Bare document after document end marker",
+ "yaml": "---\nscalar1\n...\nkey: value\n",
+ "error": false,
+ "json": [
+ "scalar1",
+ {
+ "key": "value"
+ }
+ ]
+ },
+ {
+ "id": "7ZZ5",
+ "name": "Empty flow collections",
+ "yaml": "---\nnested sequences:\n- - - []\n- - - {}\nkey1: []\nkey2: {}\n",
+ "error": false,
+ "json": [
+ {
+ "nested sequences": [
+ [
+ [
+ []
+ ]
+ ],
+ [
+ [
+ {}
+ ]
+ ]
+ ],
+ "key1": [],
+ "key2": {}
+ }
+ ]
+ },
+ {
+ "id": "82AN",
+ "name": "Three dashes and content without space",
+ "yaml": "---word1\nword2\n",
+ "error": false,
+ "json": [
+ "---word1 word2"
+ ]
+ },
+ {
+ "id": "87E4",
+ "name": "Spec Example 7.8. Single Quoted Implicit Keys",
+ "yaml": "'implicit block key' : [\n 'implicit flow key' : value,\n ]\n",
+ "error": false,
+ "json": [
+ {
+ "implicit block key": [
+ {
+ "implicit flow key": "value"
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "id": "8CWC",
+ "name": "Plain mapping key ending with colon",
+ "yaml": "---\nkey ends with two colons::: value\n",
+ "error": false,
+ "json": [
+ {
+ "key ends with two colons::": "value"
+ }
+ ]
+ },
+ {
+ "id": "8G76",
+ "name": "Spec Example 6.10. Comment Lines",
+ "yaml": " # Comment\n \n\n\n",
+ "error": false,
+ "json": []
+ },
+ {
+ "id": "8KB6",
+ "name": "Multiline plain flow mapping key without value",
+ "yaml": "---\n- { single line, a: b}\n- { multi\n line, a: b}\n",
+ "error": false,
+ "json": [
+ [
+ {
+ "single line": null,
+ "a": "b"
+ },
+ {
+ "multi line": null,
+ "a": "b"
+ }
+ ]
+ ]
+ },
+ {
+ "id": "8MK2",
+ "name": "Explicit Non-Specific Tag",
+ "yaml": "! a\n",
+ "error": false,
+ "json": [
+ "a"
+ ]
+ },
+ {
+ "id": "8QBE",
+ "name": "Block Sequence in Block Mapping",
+ "yaml": "key:\n - item1\n - item2\n",
+ "error": false,
+ "json": [
+ {
+ "key": [
+ "item1",
+ "item2"
+ ]
+ }
+ ]
+ },
+ {
+ "id": "8UDB",
+ "name": "Spec Example 7.14. Flow Sequence Entries",
+ "yaml": "[\n\"double\n quoted\", 'single\n quoted',\nplain\n text, [ nested ],\nsingle: pair,\n]\n",
+ "error": false,
+ "json": [
+ [
+ "double quoted",
+ "single quoted",
+ "plain text",
+ [
+ "nested"
+ ],
+ {
+ "single": "pair"
+ }
+ ]
+ ]
+ },
+ {
+ "id": "8XDJ",
+ "name": "Comment in plain multiline value",
+ "yaml": "key: word1\n# xxx\n word2\n",
+ "error": true
+ },
+ {
+ "id": "8XYN",
+ "name": "Anchor with unicode character",
+ "yaml": "---\n- &\ud83d\ude01 unicode anchor\n",
+ "error": false,
+ "json": [
+ [
+ "unicode anchor"
+ ]
+ ]
+ },
+ {
+ "id": "93JH",
+ "name": "Block Mappings in Block Sequence",
+ "yaml": " - key: value\n key2: value2\n -\n key3: value3\n",
+ "error": false,
+ "json": [
+ [
+ {
+ "key": "value",
+ "key2": "value2"
+ },
+ {
+ "key3": "value3"
+ }
+ ]
+ ]
+ },
+ {
+ "id": "93WF",
+ "name": "Spec Example 6.6. Line Folding [1.3]",
+ "yaml": "--- >-\n trimmed\n \n \n\n as\n space\n",
+ "error": false,
+ "json": [
+ "trimmed\n\n\nas space"
+ ]
+ },
+ {
+ "id": "96L6",
+ "name": "Spec Example 2.14. In the folded scalars, newlines become spaces",
+ "yaml": "--- >\n Mark McGwire's\n year was crippled\n by a knee injury.\n",
+ "error": false,
+ "json": [
+ "Mark McGwire's year was crippled by a knee injury.\n"
+ ]
+ },
+ {
+ "id": "96NN/00",
+ "name": "Leading tab content in literals",
+ "yaml": "foo: |-\n \tbar\n",
+ "error": false,
+ "json": [
+ {
+ "foo": "\tbar"
+ }
+ ]
+ },
+ {
+ "id": "96NN/01",
+ "name": "Leading tab content in literals",
+ "yaml": "foo: |-\n \tbar",
+ "error": false,
+ "json": [
+ {
+ "foo": "\tbar"
+ }
+ ]
+ },
+ {
+ "id": "98YD",
+ "name": "Spec Example 5.5. Comment Indicator",
+ "yaml": "# Comment only.\n",
+ "error": false,
+ "json": []
+ },
+ {
+ "id": "9BXH",
+ "name": "Multiline doublequoted flow mapping key without value",
+ "yaml": "---\n- { \"single line\", a: b}\n- { \"multi\n line\", a: b}\n",
+ "error": false,
+ "json": [
+ [
+ {
+ "single line": null,
+ "a": "b"
+ },
+ {
+ "multi line": null,
+ "a": "b"
+ }
+ ]
+ ]
+ },
+ {
+ "id": "9C9N",
+ "name": "Wrong indented flow sequence",
+ "yaml": "---\nflow: [a,\nb,\nc]\n",
+ "error": true
+ },
+ {
+ "id": "9CWY",
+ "name": "Invalid scalar at the end of mapping",
+ "yaml": "key:\n - item1\n - item2\ninvalid\n",
+ "error": true
+ },
+ {
+ "id": "9DXL",
+ "name": "Spec Example 9.6. Stream [1.3]",
+ "yaml": "Mapping: Document\n---\n# Empty\n...\n%YAML 1.2\n---\nmatches %: 20\n",
+ "error": false,
+ "json": [
+ {
+ "Mapping": "Document"
+ },
+ null,
+ {
+ "matches %": 20
+ }
+ ]
+ },
+ {
+ "id": "9FMG",
+ "name": "Multi-level Mapping Indent",
+ "yaml": "a:\n b:\n c: d\n e:\n f: g\nh: i\n",
+ "error": false,
+ "json": [
+ {
+ "a": {
+ "b": {
+ "c": "d"
+ },
+ "e": {
+ "f": "g"
+ }
+ },
+ "h": "i"
+ }
+ ]
+ },
+ {
+ "id": "9HCY",
+ "name": "Need document footer before directives",
+ "yaml": "!foo \"bar\"\n%TAG ! tag:example.com,2000:app/\n---\n!foo \"bar\"\n",
+ "error": true
+ },
+ {
+ "id": "9J7A",
+ "name": "Simple Mapping Indent",
+ "yaml": "foo:\n bar: baz\n",
+ "error": false,
+ "json": [
+ {
+ "foo": {
+ "bar": "baz"
+ }
+ }
+ ]
+ },
+ {
+ "id": "9JBA",
+ "name": "Invalid comment after end of flow sequence",
+ "yaml": "---\n[ a, b, c, ]#invalid\n",
+ "error": true
+ },
+ {
+ "id": "9KAX",
+ "name": "Various combinations of tags and anchors",
+ "yaml": "---\n&a1\n!!str\nscalar1\n---\n!!str\n&a2\nscalar2\n---\n&a3\n!!str scalar3\n---\n&a4 !!map\n&a5 !!str key5: value4\n---\na6: 1\n&anchor6 b6: 2\n---\n!!map\n&a8 !!str key8: value7\n---\n!!map\n!!str &a10 key10: value9\n---\n!!str &a11\nvalue11\n",
+ "error": false,
+ "json": [
+ "scalar1",
+ "scalar2",
+ "scalar3",
+ {
+ "key5": "value4"
+ },
+ {
+ "a6": 1,
+ "b6": 2
+ },
+ {
+ "key8": "value7"
+ },
+ {
+ "key10": "value9"
+ },
+ "value11"
+ ]
+ },
+ {
+ "id": "9KBC",
+ "name": "Mapping starting at --- line",
+ "yaml": "--- key1: value1\n key2: value2\n",
+ "error": true
+ },
+ {
+ "id": "9MAG",
+ "name": "Flow sequence with invalid comma at the beginning",
+ "yaml": "---\n[ , a, b, c ]\n",
+ "error": true
+ },
+ {
+ "id": "9MMA",
+ "name": "Directive by itself with no document",
+ "yaml": "%YAML 1.2\n",
+ "error": true
+ },
+ {
+ "id": "9MMW",
+ "name": "Single Pair Implicit Entries",
+ "yaml": "- [ YAML : separate ]\n- [ \"JSON like\":adjacent ]\n- [ {JSON: like}:adjacent ]\n",
+ "error": false
+ },
+ {
+ "id": "9MQT/00",
+ "name": "Scalar doc with '...' in content",
+ "yaml": "--- \"a\n...x\nb\"\n",
+ "error": false,
+ "json": [
+ "a ...x b"
+ ]
+ },
+ {
+ "id": "9MQT/01",
+ "name": "Scalar doc with '...' in content",
+ "yaml": "--- \"a\n... x\nb\"\n",
+ "error": true,
+ "json": [
+ "a ...x b"
+ ]
+ },
+ {
+ "id": "9SA2",
+ "name": "Multiline double quoted flow mapping key",
+ "yaml": "---\n- { \"single line\": value}\n- { \"multi\n line\": value}\n",
+ "error": false,
+ "json": [
+ [
+ {
+ "single line": "value"
+ },
+ {
+ "multi line": "value"
+ }
+ ]
+ ]
+ },
+ {
+ "id": "9SHH",
+ "name": "Spec Example 5.8. Quoted Scalar Indicators",
+ "yaml": "single: 'text'\ndouble: \"text\"\n",
+ "error": false,
+ "json": [
+ {
+ "single": "text",
+ "double": "text"
+ }
+ ]
+ },
+ {
+ "id": "9TFX",
+ "name": "Spec Example 7.6. Double Quoted Lines [1.3]",
+ "yaml": "---\n\" 1st non-empty\n\n 2nd non-empty \n 3rd non-empty \"\n",
+ "error": false,
+ "json": [
+ " 1st non-empty\n2nd non-empty 3rd non-empty "
+ ]
+ },
+ {
+ "id": "9U5K",
+ "name": "Spec Example 2.12. Compact Nested Mapping",
+ "yaml": "---\n# Products purchased\n- item : Super Hoop\n quantity: 1\n- item : Basketball\n quantity: 4\n- item : Big Shoes\n quantity: 1\n",
+ "error": false,
+ "json": [
+ [
+ {
+ "item": "Super Hoop",
+ "quantity": 1
+ },
+ {
+ "item": "Basketball",
+ "quantity": 4
+ },
+ {
+ "item": "Big Shoes",
+ "quantity": 1
+ }
+ ]
+ ]
+ },
+ {
+ "id": "9WXW",
+ "name": "Spec Example 6.18. Primary Tag Handle",
+ "yaml": "# Private\n!foo \"bar\"\n...\n# Global\n%TAG ! tag:example.com,2000:app/\n---\n!foo \"bar\"\n",
+ "error": false,
+ "json": [
+ "bar",
+ "bar"
+ ]
+ },
+ {
+ "id": "9YRD",
+ "name": "Multiline Scalar at Top Level",
+ "yaml": "a\nb \n c\nd\n\ne\n",
+ "error": false,
+ "json": [
+ "a b c d\ne"
+ ]
+ },
+ {
+ "id": "A2M4",
+ "name": "Spec Example 6.2. Indentation Indicators",
+ "yaml": "? a\n: -\tb\n - -\tc\n - d\n",
+ "error": false,
+ "json": [
+ {
+ "a": [
+ "b",
+ [
+ "c",
+ "d"
+ ]
+ ]
+ }
+ ]
+ },
+ {
+ "id": "A6F9",
+ "name": "Spec Example 8.4. Chomping Final Line Break",
+ "yaml": "strip: |-\n text\nclip: |\n text\nkeep: |+\n text\n",
+ "error": false,
+ "json": [
+ {
+ "strip": "text",
+ "clip": "text\n",
+ "keep": "text\n"
+ }
+ ]
+ },
+ {
+ "id": "A984",
+ "name": "Multiline Scalar in Mapping",
+ "yaml": "a: b\n c\nd:\n e\n f\n",
+ "error": false,
+ "json": [
+ {
+ "a": "b c",
+ "d": "e f"
+ }
+ ]
+ },
+ {
+ "id": "AB8U",
+ "name": "Sequence entry that looks like two with wrong indentation",
+ "yaml": "- single multiline\n - sequence entry\n",
+ "error": false,
+ "json": [
+ [
+ "single multiline - sequence entry"
+ ]
+ ]
+ },
+ {
+ "id": "AVM7",
+ "name": "Empty Stream",
+ "yaml": "",
+ "error": false,
+ "json": []
+ },
+ {
+ "id": "AZ63",
+ "name": "Sequence With Same Indentation as Parent Mapping",
+ "yaml": "one:\n- 2\n- 3\nfour: 5\n",
+ "error": false,
+ "json": [
+ {
+ "one": [
+ 2,
+ 3
+ ],
+ "four": 5
+ }
+ ]
+ },
+ {
+ "id": "AZW3",
+ "name": "Lookahead test cases",
+ "yaml": "- bla\"keks: foo\n- bla]keks: foo\n",
+ "error": false,
+ "json": [
+ [
+ {
+ "bla\"keks": "foo"
+ },
+ {
+ "bla]keks": "foo"
+ }
+ ]
+ ]
+ },
+ {
+ "id": "B3HG",
+ "name": "Spec Example 8.9. Folded Scalar [1.3]",
+ "yaml": "--- >\n folded\n text\n\n\n",
+ "error": false,
+ "json": [
+ "folded text\n"
+ ]
+ },
+ {
+ "id": "B63P",
+ "name": "Directive without document",
+ "yaml": "%YAML 1.2\n...\n",
+ "error": true
+ },
+ {
+ "id": "BD7L",
+ "name": "Invalid mapping after sequence",
+ "yaml": "- item1\n- item2\ninvalid: x\n",
+ "error": true
+ },
+ {
+ "id": "BEC7",
+ "name": "Spec Example 6.14. \u201cYAML\u201d directive",
+ "yaml": "%YAML 1.3 # Attempt parsing\n # with a warning\n---\n\"foo\"\n",
+ "error": false,
+ "json": [
+ "foo"
+ ]
+ },
+ {
+ "id": "BF9H",
+ "name": "Trailing comment in multiline plain scalar",
+ "yaml": "---\nplain: a\n b # end of scalar\n c\n",
+ "error": true
+ },
+ {
+ "id": "BS4K",
+ "name": "Comment between plain scalar lines",
+ "yaml": "word1 # comment\nword2\n",
+ "error": true
+ },
+ {
+ "id": "BU8L",
+ "name": "Node Anchor and Tag on Seperate Lines",
+ "yaml": "key: &anchor\n !!map\n a: b\n",
+ "error": false,
+ "json": [
+ {
+ "key": {
+ "a": "b"
+ }
+ }
+ ]
+ },
+ {
+ "id": "C2DT",
+ "name": "Spec Example 7.18. Flow Mapping Adjacent Values",
+ "yaml": "{\n\"adjacent\":value,\n\"readable\": value,\n\"empty\":\n}\n",
+ "error": false,
+ "json": [
+ {
+ "adjacent": "value",
+ "readable": "value",
+ "empty": null
+ }
+ ]
+ },
+ {
+ "id": "C2SP",
+ "name": "Flow Mapping Key on two lines",
+ "yaml": "[23\n]: 42\n",
+ "error": true
+ },
+ {
+ "id": "C4HZ",
+ "name": "Spec Example 2.24. Global Tags",
+ "yaml": "%TAG ! tag:clarkevans.com,2002:\n--- !shape\n # Use the ! handle for presenting\n # tag:clarkevans.com,2002:circle\n- !circle\n center: &ORIGIN {x: 73, y: 129}\n radius: 7\n- !line\n start: *ORIGIN\n finish: { x: 89, y: 102 }\n- !label\n start: *ORIGIN\n color: 0xFFEEBB\n text: Pretty vector drawing.\n",
+ "error": false,
+ "json": [
+ [
+ {
+ "center": {
+ "x": 73,
+ "y": 129
+ },
+ "radius": 7
+ },
+ {
+ "start": {
+ "x": 73,
+ "y": 129
+ },
+ "finish": {
+ "x": 89,
+ "y": 102
+ }
+ },
+ {
+ "start": {
+ "x": 73,
+ "y": 129
+ },
+ "color": 16772795,
+ "text": "Pretty vector drawing."
+ }
+ ]
+ ]
+ },
+ {
+ "id": "CC74",
+ "name": "Spec Example 6.20. Tag Handles",
+ "yaml": "%TAG !e! tag:example.com,2000:app/\n---\n!e!foo \"bar\"\n",
+ "error": false,
+ "json": [
+ "bar"
+ ]
+ },
+ {
+ "id": "CFD4",
+ "name": "Empty implicit key in single pair flow sequences",
+ "yaml": "- [ : empty key ]\n- [: another empty key]\n",
+ "error": false
+ },
+ {
+ "id": "CML9",
+ "name": "Missing comma in flow",
+ "yaml": "key: [ word1\n# xxx\n word2 ]\n",
+ "error": true
+ },
+ {
+ "id": "CN3R",
+ "name": "Various location of anchors in flow sequence",
+ "yaml": "&flowseq [\n a: b,\n &c c: d,\n { &e e: f },\n &g { g: h }\n]\n",
+ "error": false,
+ "json": [
+ [
+ {
+ "a": "b"
+ },
+ {
+ "c": "d"
+ },
+ {
+ "e": "f"
+ },
+ {
+ "g": "h"
+ }
+ ]
+ ]
+ },
+ {
+ "id": "CPZ3",
+ "name": "Doublequoted scalar starting with a tab",
+ "yaml": "---\ntab: \"\\tstring\"\n",
+ "error": false,
+ "json": [
+ {
+ "tab": "\tstring"
+ }
+ ]
+ },
+ {
+ "id": "CQ3W",
+ "name": "Double quoted string without closing quote",
+ "yaml": "---\nkey: \"missing closing quote\n",
+ "error": true
+ },
+ {
+ "id": "CT4Q",
+ "name": "Spec Example 7.20. Single Pair Explicit Entry",
+ "yaml": "[\n? foo\n bar : baz\n]\n",
+ "error": false,
+ "json": [
+ [
+ {
+ "foo bar": "baz"
+ }
+ ]
+ ]
+ },
+ {
+ "id": "CTN5",
+ "name": "Flow sequence with invalid extra comma",
+ "yaml": "---\n[ a, b, c, , ]\n",
+ "error": true
+ },
+ {
+ "id": "CUP7",
+ "name": "Spec Example 5.6. Node Property Indicators",
+ "yaml": "anchored: !local &anchor value\nalias: *anchor\n",
+ "error": false,
+ "json": [
+ {
+ "anchored": "value",
+ "alias": "value"
+ }
+ ]
+ },
+ {
+ "id": "CVW2",
+ "name": "Invalid comment after comma",
+ "yaml": "---\n[ a, b, c,#invalid\n]\n",
+ "error": true
+ },
+ {
+ "id": "CXX2",
+ "name": "Mapping with anchor on document start line",
+ "yaml": "--- &anchor a: b\n",
+ "error": true
+ },
+ {
+ "id": "D49Q",
+ "name": "Multiline single quoted implicit keys",
+ "yaml": "'a\\nb': 1\n'c\n d': 1\n",
+ "error": true
+ },
+ {
+ "id": "D83L",
+ "name": "Block scalar indicator order",
+ "yaml": "- |2-\n explicit indent and chomp\n- |-2\n chomp and explicit indent\n",
+ "error": false,
+ "json": [
+ [
+ "explicit indent and chomp",
+ "chomp and explicit indent"
+ ]
+ ]
+ },
+ {
+ "id": "D88J",
+ "name": "Flow Sequence in Block Mapping",
+ "yaml": "a: [b, c]\n",
+ "error": false,
+ "json": [
+ {
+ "a": [
+ "b",
+ "c"
+ ]
+ }
+ ]
+ },
+ {
+ "id": "D9TU",
+ "name": "Single Pair Block Mapping",
+ "yaml": "foo: bar\n",
+ "error": false,
+ "json": [
+ {
+ "foo": "bar"
+ }
+ ]
+ },
+ {
+ "id": "DBG4",
+ "name": "Spec Example 7.10. Plain Characters",
+ "yaml": "# Outside flow collection:\n- ::vector\n- \": - ()\"\n- Up, up, and away!\n- -123\n- http://example.com/foo#bar\n# Inside flow collection:\n- [ ::vector,\n \": - ()\",\n \"Up, up and away!\",\n -123,\n http://example.com/foo#bar ]\n",
+ "error": false,
+ "json": [
+ [
+ "::vector",
+ ": - ()",
+ "Up, up, and away!",
+ -123,
+ "http://example.com/foo#bar",
+ [
+ "::vector",
+ ": - ()",
+ "Up, up and away!",
+ -123,
+ "http://example.com/foo#bar"
+ ]
+ ]
+ ]
+ },
+ {
+ "id": "DC7X",
+ "name": "Various trailing tabs",
+ "yaml": "a: b\t\nseq:\t\n - a\t\nc: d\t#X\n",
+ "error": false,
+ "json": [
+ {
+ "a": "b",
+ "seq": [
+ "a"
+ ],
+ "c": "d"
+ }
+ ]
+ },
+ {
+ "id": "DE56/00",
+ "name": "Trailing tabs in double quoted",
+ "yaml": "\"1 trailing\\t\n tab\"\n",
+ "error": false,
+ "json": [
+ "1 trailing\t tab"
+ ]
+ },
+ {
+ "id": "DE56/01",
+ "name": "Trailing tabs in double quoted",
+ "yaml": "\"2 trailing\\t \n tab\"\n",
+ "error": false,
+ "json": [
+ "2 trailing\t tab"
+ ]
+ },
+ {
+ "id": "DE56/02",
+ "name": "Trailing tabs in double quoted",
+ "yaml": "\"3 trailing\\\t\n tab\"\n",
+ "error": false,
+ "json": [
+ "3 trailing\t tab"
+ ]
+ },
+ {
+ "id": "DE56/03",
+ "name": "Trailing tabs in double quoted",
+ "yaml": "\"4 trailing\\\t \n tab\"\n",
+ "error": false,
+ "json": [
+ "4 trailing\t tab"
+ ]
+ },
+ {
+ "id": "DE56/04",
+ "name": "Trailing tabs in double quoted",
+ "yaml": "\"5 trailing\t\n tab\"\n",
+ "error": false,
+ "json": [
+ "5 trailing tab"
+ ]
+ },
+ {
+ "id": "DE56/05",
+ "name": "Trailing tabs in double quoted",
+ "yaml": "\"6 trailing\t \n tab\"\n",
+ "error": false,
+ "json": [
+ "6 trailing tab"
+ ]
+ },
+ {
+ "id": "DFF7",
+ "name": "Spec Example 7.16. Flow Mapping Entries",
+ "yaml": "{\n? explicit: entry,\nimplicit: entry,\n?\n}\n",
+ "error": false
+ },
+ {
+ "id": "DHP8",
+ "name": "Flow Sequence",
+ "yaml": "[foo, bar, 42]\n",
+ "error": false,
+ "json": [
+ [
+ "foo",
+ "bar",
+ 42
+ ]
+ ]
+ },
+ {
+ "id": "DK3J",
+ "name": "Zero indented block scalar with line that looks like a comment",
+ "yaml": "--- >\nline1\n# no comment\nline3\n",
+ "error": false,
+ "json": [
+ "line1 # no comment line3\n"
+ ]
+ },
+ {
+ "id": "DK4H",
+ "name": "Implicit key followed by newline",
+ "yaml": "---\n[ key\n : value ]\n",
+ "error": true
+ },
+ {
+ "id": "DK95/00",
+ "name": "Tabs that look like indentation",
+ "yaml": "foo:\n \tbar\n",
+ "error": false,
+ "json": [
+ {
+ "foo": "bar"
+ }
+ ]
+ },
+ {
+ "id": "DK95/01",
+ "name": "Tabs that look like indentation",
+ "yaml": "foo: \"bar\n\tbaz\"\n",
+ "error": true,
+ "json": [
+ {
+ "foo": "bar"
+ }
+ ]
+ },
+ {
+ "id": "DK95/02",
+ "name": "Tabs that look like indentation",
+ "yaml": "foo: \"bar\n \tbaz\"\n",
+ "error": false,
+ "json": [
+ {
+ "foo": "bar baz"
+ }
+ ]
+ },
+ {
+ "id": "DK95/03",
+ "name": "Tabs that look like indentation",
+ "yaml": " \t\nfoo: 1\n",
+ "error": false,
+ "json": [
+ {
+ "foo": 1
+ }
+ ]
+ },
+ {
+ "id": "DK95/04",
+ "name": "Tabs that look like indentation",
+ "yaml": "foo: 1\n\t\nbar: 2\n",
+ "error": false,
+ "json": [
+ {
+ "foo": 1,
+ "bar": 2
+ }
+ ]
+ },
+ {
+ "id": "DK95/05",
+ "name": "Tabs that look like indentation",
+ "yaml": "foo: 1\n \t\nbar: 2\n",
+ "error": false,
+ "json": [
+ {
+ "foo": 1,
+ "bar": 2
+ }
+ ]
+ },
+ {
+ "id": "DK95/06",
+ "name": "Tabs that look like indentation",
+ "yaml": "foo:\n a: 1\n \tb: 2\n",
+ "error": true,
+ "json": [
+ {
+ "foo": 1,
+ "bar": 2
+ }
+ ]
+ },
+ {
+ "id": "DK95/07",
+ "name": "Tabs that look like indentation",
+ "yaml": "%YAML 1.2\n\t\n---\n",
+ "error": false,
+ "json": [
+ null
+ ]
+ },
+ {
+ "id": "DK95/08",
+ "name": "Tabs that look like indentation",
+ "yaml": "foo: \"bar\n \t \t baz \t \t \"\n",
+ "error": false,
+ "json": [
+ {
+ "foo": "bar baz \t \t "
+ }
+ ]
+ },
+ {
+ "id": "DMG6",
+ "name": "Wrong indendation in Map",
+ "yaml": "key:\n ok: 1\n wrong: 2\n",
+ "error": true
+ },
+ {
+ "id": "DWX9",
+ "name": "Spec Example 8.8. Literal Content",
+ "yaml": "|\n \n \n literal\n \n \n text\n\n # Comment\n",
+ "error": false,
+ "json": [
+ "\n\nliteral\n \n\ntext\n"
+ ]
+ },
+ {
+ "id": "E76Z",
+ "name": "Aliases in Implicit Block Mapping",
+ "yaml": "&a a: &b b\n*b : *a\n",
+ "error": false,
+ "json": [
+ {
+ "a": "b",
+ "b": "a"
+ }
+ ]
+ },
+ {
+ "id": "EB22",
+ "name": "Missing document-end marker before directive",
+ "yaml": "---\nscalar1 # comment\n%YAML 1.2\n---\nscalar2\n",
+ "error": true
+ },
+ {
+ "id": "EHF6",
+ "name": "Tags for Flow Objects",
+ "yaml": "!!map {\n k: !!seq\n [ a, !!str b]\n}\n",
+ "error": false,
+ "json": [
+ {
+ "k": [
+ "a",
+ "b"
+ ]
+ }
+ ]
+ },
+ {
+ "id": "EW3V",
+ "name": "Wrong indendation in mapping",
+ "yaml": "k1: v1\n k2: v2\n",
+ "error": true
+ },
+ {
+ "id": "EX5H",
+ "name": "Multiline Scalar at Top Level [1.3]",
+ "yaml": "---\na\nb \n c\nd\n\ne\n",
+ "error": false,
+ "json": [
+ "a b c d\ne"
+ ]
+ },
+ {
+ "id": "EXG3",
+ "name": "Three dashes and content without space [1.3]",
+ "yaml": "---\n---word1\nword2\n",
+ "error": false,
+ "json": [
+ "---word1 word2"
+ ]
+ },
+ {
+ "id": "F2C7",
+ "name": "Anchors and Tags",
+ "yaml": " - &a !!str a\n - !!int 2\n - !!int &c 4\n - &d d\n",
+ "error": false,
+ "json": [
+ [
+ "a",
+ 2,
+ 4,
+ "d"
+ ]
+ ]
+ },
+ {
+ "id": "F3CP",
+ "name": "Nested flow collections on one line",
+ "yaml": "---\n{ a: [b, c, { d: [e, f] } ] }\n",
+ "error": false,
+ "json": [
+ {
+ "a": [
+ "b",
+ "c",
+ {
+ "d": [
+ "e",
+ "f"
+ ]
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "id": "F6MC",
+ "name": "More indented lines at the beginning of folded block scalars",
+ "yaml": "---\na: >2\n more indented\n regular\nb: >2\n\n\n more indented\n regular\n",
+ "error": false,
+ "json": [
+ {
+ "a": " more indented\nregular\n",
+ "b": "\n\n more indented\nregular\n"
+ }
+ ]
+ },
+ {
+ "id": "F8F9",
+ "name": "Spec Example 8.5. Chomping Trailing Lines",
+ "yaml": " # Strip\n # Comments:\nstrip: |-\n # text\n \n # Clip\n # comments:\n\nclip: |\n # text\n \n # Keep\n # comments:\n\nkeep: |+\n # text\n\n # Trail\n # comments.\n",
+ "error": false,
+ "json": [
+ {
+ "strip": "# text",
+ "clip": "# text\n",
+ "keep": "# text\n\n"
+ }
+ ]
+ },
+ {
+ "id": "FBC9",
+ "name": "Allowed characters in plain scalars",
+ "yaml": "safe: a!\"#$%&'()*+,-./09:;<=>?@AZ[\\]^_`az{|}~\n !\"#$%&'()*+,-./09:;<=>?@AZ[\\]^_`az{|}~\nsafe question mark: ?foo\nsafe colon: :foo\nsafe dash: -foo\n",
+ "error": false,
+ "json": [
+ {
+ "safe": "a!\"#$%&'()*+,-./09:;<=>?@AZ[\\]^_`az{|}~ !\"#$%&'()*+,-./09:;<=>?@AZ[\\]^_`az{|}~",
+ "safe question mark": "?foo",
+ "safe colon": ":foo",
+ "safe dash": "-foo"
+ }
+ ]
+ },
+ {
+ "id": "FH7J",
+ "name": "Tags on Empty Scalars",
+ "yaml": "- !!str\n-\n !!null : a\n b: !!str\n- !!str : !!null\n",
+ "error": false
+ },
+ {
+ "id": "FP8R",
+ "name": "Zero indented block scalar",
+ "yaml": "--- >\nline1\nline2\nline3\n",
+ "error": false,
+ "json": [
+ "line1 line2 line3\n"
+ ]
+ },
+ {
+ "id": "FQ7F",
+ "name": "Spec Example 2.1. Sequence of Scalars",
+ "yaml": "- Mark McGwire\n- Sammy Sosa\n- Ken Griffey\n",
+ "error": false,
+ "json": [
+ [
+ "Mark McGwire",
+ "Sammy Sosa",
+ "Ken Griffey"
+ ]
+ ]
+ },
+ {
+ "id": "FRK4",
+ "name": "Spec Example 7.3. Completely Empty Flow Nodes",
+ "yaml": "{\n ? foo :,\n : bar,\n}\n",
+ "error": false
+ },
+ {
+ "id": "FTA2",
+ "name": "Single block sequence with anchor and explicit document start",
+ "yaml": "--- &sequence\n- a\n",
+ "error": false,
+ "json": [
+ [
+ "a"
+ ]
+ ]
+ },
+ {
+ "id": "FUP4",
+ "name": "Flow Sequence in Flow Sequence",
+ "yaml": "[a, [b, c]]\n",
+ "error": false,
+ "json": [
+ [
+ "a",
+ [
+ "b",
+ "c"
+ ]
+ ]
+ ]
+ },
+ {
+ "id": "G4RS",
+ "name": "Spec Example 2.17. Quoted Scalars",
+ "yaml": "unicode: \"Sosa did fine.\\u263A\"\ncontrol: \"\\b1998\\t1999\\t2000\\n\"\nhex esc: \"\\x0d\\x0a is \\r\\n\"\n\nsingle: '\"Howdy!\" he cried.'\nquoted: ' # Not a ''comment''.'\ntie-fighter: '|\\-*-/|'\n",
+ "error": false,
+ "json": [
+ {
+ "unicode": "Sosa did fine.\u263a",
+ "control": "\b1998\t1999\t2000\n",
+ "hex esc": "\r\n is \r\n",
+ "single": "\"Howdy!\" he cried.",
+ "quoted": " # Not a 'comment'.",
+ "tie-fighter": "|\\-*-/|"
+ }
+ ]
+ },
+ {
+ "id": "G5U8",
+ "name": "Plain dashes in flow sequence",
+ "yaml": "---\n- [-, -]\n",
+ "error": true
+ },
+ {
+ "id": "G7JE",
+ "name": "Multiline implicit keys",
+ "yaml": "a\\nb: 1\nc\n d: 1\n",
+ "error": true
+ },
+ {
+ "id": "G992",
+ "name": "Spec Example 8.9. Folded Scalar",
+ "yaml": ">\n folded\n text\n\n\n",
+ "error": false,
+ "json": [
+ "folded text\n"
+ ]
+ },
+ {
+ "id": "G9HC",
+ "name": "Invalid anchor in zero indented sequence",
+ "yaml": "---\nseq:\n&anchor\n- a\n- b\n",
+ "error": true
+ },
+ {
+ "id": "GDY7",
+ "name": "Comment that looks like a mapping key",
+ "yaml": "key: value\nthis is #not a: key\n",
+ "error": true
+ },
+ {
+ "id": "GH63",
+ "name": "Mixed Block Mapping (explicit to implicit)",
+ "yaml": "? a\n: 1.3\nfifteen: d\n",
+ "error": false,
+ "json": [
+ {
+ "a": 1.3,
+ "fifteen": "d"
+ }
+ ]
+ },
+ {
+ "id": "GT5M",
+ "name": "Node anchor in sequence",
+ "yaml": "- item1\n&node\n- item2\n",
+ "error": true
+ },
+ {
+ "id": "H2RW",
+ "name": "Blank lines",
+ "yaml": "foo: 1\n\nbar: 2\n \ntext: |\n a\n \n b\n\n c\n \n d\n",
+ "error": false,
+ "json": [
+ {
+ "foo": 1,
+ "bar": 2,
+ "text": "a\n \nb\n\nc\n\nd\n"
+ }
+ ]
+ },
+ {
+ "id": "H3Z8",
+ "name": "Literal unicode",
+ "yaml": "---\nwanted: love \u2665 and peace \u262e\n",
+ "error": false,
+ "json": [
+ {
+ "wanted": "love \u2665 and peace \u262e"
+ }
+ ]
+ },
+ {
+ "id": "H7J7",
+ "name": "Node anchor not indented",
+ "yaml": "key: &x\n!!map\n a: b\n",
+ "error": true
+ },
+ {
+ "id": "H7TQ",
+ "name": "Extra words on %YAML directive",
+ "yaml": "%YAML 1.2 foo\n---\n",
+ "error": true
+ },
+ {
+ "id": "HM87/00",
+ "name": "Scalars in flow start with syntax char",
+ "yaml": "[:x]\n",
+ "error": false,
+ "json": [
+ [
+ ":x"
+ ]
+ ]
+ },
+ {
+ "id": "HM87/01",
+ "name": "Scalars in flow start with syntax char",
+ "yaml": "[?x]\n",
+ "error": false,
+ "json": [
+ [
+ "?x"
+ ]
+ ]
+ },
+ {
+ "id": "HMK4",
+ "name": "Spec Example 2.16. Indentation determines scope",
+ "yaml": "name: Mark McGwire\naccomplishment: >\n Mark set a major league\n home run record in 1998.\nstats: |\n 65 Home Runs\n 0.278 Batting Average\n",
+ "error": false,
+ "json": [
+ {
+ "name": "Mark McGwire",
+ "accomplishment": "Mark set a major league home run record in 1998.\n",
+ "stats": "65 Home Runs\n0.278 Batting Average\n"
+ }
+ ]
+ },
+ {
+ "id": "HMQ5",
+ "name": "Spec Example 6.23. Node Properties",
+ "yaml": "!!str &a1 \"foo\":\n !!str bar\n&a2 baz : *a1\n",
+ "error": false,
+ "json": [
+ {
+ "foo": "bar",
+ "baz": "foo"
+ }
+ ]
+ },
+ {
+ "id": "HRE5",
+ "name": "Double quoted scalar with escaped single quote",
+ "yaml": "---\ndouble: \"quoted \\' scalar\"\n",
+ "error": true
+ },
+ {
+ "id": "HS5T",
+ "name": "Spec Example 7.12. Plain Lines",
+ "yaml": "1st non-empty\n\n 2nd non-empty \n\t3rd non-empty\n",
+ "error": false,
+ "json": [
+ "1st non-empty\n2nd non-empty 3rd non-empty"
+ ]
+ },
+ {
+ "id": "HU3P",
+ "name": "Invalid Mapping in plain scalar",
+ "yaml": "key:\n word1 word2\n no: key\n",
+ "error": true
+ },
+ {
+ "id": "HWV9",
+ "name": "Document-end marker",
+ "yaml": "...\n",
+ "error": false,
+ "json": []
+ },
+ {
+ "id": "J3BT",
+ "name": "Spec Example 5.12. Tabs and Spaces",
+ "yaml": "# Tabs and spaces\nquoted: \"Quoted \t\"\nblock:\t|\n void main() {\n \tprintf(\"Hello, world!\\n\");\n }\n",
+ "error": false,
+ "json": [
+ {
+ "quoted": "Quoted \t",
+ "block": "void main() {\n\tprintf(\"Hello, world!\\n\");\n}\n"
+ }
+ ]
+ },
+ {
+ "id": "J5UC",
+ "name": "Multiple Pair Block Mapping",
+ "yaml": "foo: blue\nbar: arrr\nbaz: jazz\n",
+ "error": false,
+ "json": [
+ {
+ "foo": "blue",
+ "bar": "arrr",
+ "baz": "jazz"
+ }
+ ]
+ },
+ {
+ "id": "J7PZ",
+ "name": "Spec Example 2.26. Ordered Mappings",
+ "yaml": "# The !!omap tag is one of the optional types\n# introduced for YAML 1.1. In 1.2, it is not\n# part of the standard tags and should not be\n# enabled by default.\n# Ordered maps are represented as\n# A sequence of mappings, with\n# each mapping having one key\n--- !!omap\n- Mark McGwire: 65\n- Sammy Sosa: 63\n- Ken Griffy: 58\n",
+ "error": false,
+ "json": [
+ [
+ {
+ "Mark McGwire": 65
+ },
+ {
+ "Sammy Sosa": 63
+ },
+ {
+ "Ken Griffy": 58
+ }
+ ]
+ ]
+ },
+ {
+ "id": "J7VC",
+ "name": "Empty Lines Between Mapping Elements",
+ "yaml": "one: 2\n\n\nthree: 4\n",
+ "error": false,
+ "json": [
+ {
+ "one": 2,
+ "three": 4
+ }
+ ]
+ },
+ {
+ "id": "J9HZ",
+ "name": "Spec Example 2.9. Single Document with Two Comments",
+ "yaml": "---\nhr: # 1998 hr ranking\n - Mark McGwire\n - Sammy Sosa\nrbi:\n # 1998 rbi ranking\n - Sammy Sosa\n - Ken Griffey\n",
+ "error": false,
+ "json": [
+ {
+ "hr": [
+ "Mark McGwire",
+ "Sammy Sosa"
+ ],
+ "rbi": [
+ "Sammy Sosa",
+ "Ken Griffey"
+ ]
+ }
+ ]
+ },
+ {
+ "id": "JEF9/00",
+ "name": "Trailing whitespace in streams",
+ "yaml": "- |+\n\n\n",
+ "error": false,
+ "json": [
+ [
+ "\n\n"
+ ]
+ ]
+ },
+ {
+ "id": "JEF9/01",
+ "name": "Trailing whitespace in streams",
+ "yaml": "- |+\n \n",
+ "error": false,
+ "json": [
+ [
+ "\n"
+ ]
+ ]
+ },
+ {
+ "id": "JEF9/02",
+ "name": "Trailing whitespace in streams",
+ "yaml": "- |+\n ",
+ "error": false,
+ "json": [
+ [
+ "\n"
+ ]
+ ]
+ },
+ {
+ "id": "JHB9",
+ "name": "Spec Example 2.7. Two Documents in a Stream",
+ "yaml": "# Ranking of 1998 home runs\n---\n- Mark McGwire\n- Sammy Sosa\n- Ken Griffey\n\n# Team ranking\n---\n- Chicago Cubs\n- St Louis Cardinals\n",
+ "error": false,
+ "json": [
+ [
+ "Mark McGwire",
+ "Sammy Sosa",
+ "Ken Griffey"
+ ],
+ [
+ "Chicago Cubs",
+ "St Louis Cardinals"
+ ]
+ ]
+ },
+ {
+ "id": "JKF3",
+ "name": "Multiline unidented double quoted block key",
+ "yaml": "- - \"bar\nbar\": x\n",
+ "error": true
+ },
+ {
+ "id": "JQ4R",
+ "name": "Spec Example 8.14. Block Sequence",
+ "yaml": "block sequence:\n - one\n - two : three\n",
+ "error": false,
+ "json": [
+ {
+ "block sequence": [
+ "one",
+ {
+ "two": "three"
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "id": "JR7V",
+ "name": "Question marks in scalars",
+ "yaml": "- a?string\n- another ? string\n- key: value?\n- [a?string]\n- [another ? string]\n- {key: value? }\n- {key: value?}\n- {key?: value }\n",
+ "error": false,
+ "json": [
+ [
+ "a?string",
+ "another ? string",
+ {
+ "key": "value?"
+ },
+ [
+ "a?string"
+ ],
+ [
+ "another ? string"
+ ],
+ {
+ "key": "value?"
+ },
+ {
+ "key": "value?"
+ },
+ {
+ "key?": "value"
+ }
+ ]
+ ]
+ },
+ {
+ "id": "JS2J",
+ "name": "Spec Example 6.29. Node Anchors",
+ "yaml": "First occurrence: &anchor Value\nSecond occurrence: *anchor\n",
+ "error": false,
+ "json": [
+ {
+ "First occurrence": "Value",
+ "Second occurrence": "Value"
+ }
+ ]
+ },
+ {
+ "id": "JTV5",
+ "name": "Block Mapping with Multiline Scalars",
+ "yaml": "? a\n true\n: null\n d\n? e\n 42\n",
+ "error": false,
+ "json": [
+ {
+ "a true": "null d",
+ "e 42": null
+ }
+ ]
+ },
+ {
+ "id": "JY7Z",
+ "name": "Trailing content that looks like a mapping",
+ "yaml": "key1: \"quoted1\"\nkey2: \"quoted2\" no key: nor value\nkey3: \"quoted3\"\n",
+ "error": true
+ },
+ {
+ "id": "K3WX",
+ "name": "Colon and adjacent value after comment on next line",
+ "yaml": "---\n{ \"foo\" # comment\n :bar }\n",
+ "error": false,
+ "json": [
+ {
+ "foo": "bar"
+ }
+ ]
+ },
+ {
+ "id": "K4SU",
+ "name": "Multiple Entry Block Sequence",
+ "yaml": "- foo\n- bar\n- 42\n",
+ "error": false,
+ "json": [
+ [
+ "foo",
+ "bar",
+ 42
+ ]
+ ]
+ },
+ {
+ "id": "K527",
+ "name": "Spec Example 6.6. Line Folding",
+ "yaml": ">-\n trimmed\n \n \n\n as\n space\n",
+ "error": false,
+ "json": [
+ "trimmed\n\n\nas space"
+ ]
+ },
+ {
+ "id": "K54U",
+ "name": "Tab after document header",
+ "yaml": "---\tscalar\n",
+ "error": false,
+ "json": [
+ "scalar"
+ ]
+ },
+ {
+ "id": "K858",
+ "name": "Spec Example 8.6. Empty Scalar Chomping",
+ "yaml": "strip: >-\n\nclip: >\n\nkeep: |+\n\n",
+ "error": false,
+ "json": [
+ {
+ "strip": "",
+ "clip": "",
+ "keep": "\n"
+ }
+ ]
+ },
+ {
+ "id": "KH5V/00",
+ "name": "Inline tabs in double quoted",
+ "yaml": "\"1 inline\\ttab\"\n",
+ "error": false,
+ "json": [
+ "1 inline\ttab"
+ ]
+ },
+ {
+ "id": "KH5V/01",
+ "name": "Inline tabs in double quoted",
+ "yaml": "\"2 inline\\\ttab\"\n",
+ "error": false,
+ "json": [
+ "2 inline\ttab"
+ ]
+ },
+ {
+ "id": "KH5V/02",
+ "name": "Inline tabs in double quoted",
+ "yaml": "\"3 inline\ttab\"\n",
+ "error": false,
+ "json": [
+ "3 inline\ttab"
+ ]
+ },
+ {
+ "id": "KK5P",
+ "name": "Various combinations of explicit block mappings",
+ "yaml": "complex1:\n ? - a\ncomplex2:\n ? - a\n : b\ncomplex3:\n ? - a\n : >\n b\ncomplex4:\n ? >\n a\n :\ncomplex5:\n ? - a\n : - b\n",
+ "error": false
+ },
+ {
+ "id": "KMK3",
+ "name": "Block Submapping",
+ "yaml": "foo:\n bar: 1\nbaz: 2\n",
+ "error": false,
+ "json": [
+ {
+ "foo": {
+ "bar": 1
+ },
+ "baz": 2
+ }
+ ]
+ },
+ {
+ "id": "KS4U",
+ "name": "Invalid item after end of flow sequence",
+ "yaml": "---\n[\nsequence item\n]\ninvalid item\n",
+ "error": true
+ },
+ {
+ "id": "KSS4",
+ "name": "Scalars on --- line",
+ "yaml": "--- \"quoted\nstring\"\n--- &node foo\n",
+ "error": false,
+ "json": [
+ "quoted string",
+ "foo"
+ ]
+ },
+ {
+ "id": "L24T/00",
+ "name": "Trailing line of spaces",
+ "yaml": "foo: |\n x\n \n",
+ "error": false,
+ "json": [
+ {
+ "foo": "x\n \n"
+ }
+ ]
+ },
+ {
+ "id": "L24T/01",
+ "name": "Trailing line of spaces",
+ "yaml": "foo: |\n x\n ",
+ "error": false,
+ "json": [
+ {
+ "foo": "x\n \n"
+ }
+ ]
+ },
+ {
+ "id": "L383",
+ "name": "Two scalar docs with trailing comments",
+ "yaml": "--- foo # comment\n--- foo # comment\n",
+ "error": false,
+ "json": [
+ "foo",
+ "foo"
+ ]
+ },
+ {
+ "id": "L94M",
+ "name": "Tags in Explicit Mapping",
+ "yaml": "? !!str a\n: !!int 47\n? c\n: !!str d\n",
+ "error": false,
+ "json": [
+ {
+ "a": 47,
+ "c": "d"
+ }
+ ]
+ },
+ {
+ "id": "L9U5",
+ "name": "Spec Example 7.11. Plain Implicit Keys",
+ "yaml": "implicit block key : [\n implicit flow key : value,\n ]\n",
+ "error": false,
+ "json": [
+ {
+ "implicit block key": [
+ {
+ "implicit flow key": "value"
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "id": "LE5A",
+ "name": "Spec Example 7.24. Flow Nodes",
+ "yaml": "- !!str \"a\"\n- 'b'\n- &anchor \"c\"\n- *anchor\n- !!str\n",
+ "error": false,
+ "json": [
+ [
+ "a",
+ "b",
+ "c",
+ "c",
+ ""
+ ]
+ ]
+ },
+ {
+ "id": "LHL4",
+ "name": "Invalid tag",
+ "yaml": "---\n!invalid{}tag scalar\n",
+ "error": true
+ },
+ {
+ "id": "LP6E",
+ "name": "Whitespace After Scalars in Flow",
+ "yaml": "- [a, b , c ]\n- { \"a\" : b\n , c : 'd' ,\n e : \"f\"\n }\n- [ ]\n",
+ "error": false,
+ "json": [
+ [
+ [
+ "a",
+ "b",
+ "c"
+ ],
+ {
+ "a": "b",
+ "c": "d",
+ "e": "f"
+ },
+ []
+ ]
+ ]
+ },
+ {
+ "id": "LQZ7",
+ "name": "Spec Example 7.4. Double Quoted Implicit Keys",
+ "yaml": "\"implicit block key\" : [\n \"implicit flow key\" : value,\n ]\n",
+ "error": false,
+ "json": [
+ {
+ "implicit block key": [
+ {
+ "implicit flow key": "value"
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "id": "LX3P",
+ "name": "Implicit Flow Mapping Key on one line",
+ "yaml": "[flow]: block\n",
+ "error": false
+ },
+ {
+ "id": "M29M",
+ "name": "Literal Block Scalar",
+ "yaml": "a: |\n ab\n \n cd\n ef\n \n\n...\n",
+ "error": false,
+ "json": [
+ {
+ "a": "ab\n\ncd\nef\n"
+ }
+ ]
+ },
+ {
+ "id": "M2N8/00",
+ "name": "Question mark edge cases",
+ "yaml": "- ? : x\n",
+ "error": false
+ },
+ {
+ "id": "M2N8/01",
+ "name": "Question mark edge cases",
+ "yaml": "? []: x\n",
+ "error": false
+ },
+ {
+ "id": "M5C3",
+ "name": "Spec Example 8.21. Block Scalar Nodes",
+ "yaml": "literal: |2\n value\nfolded:\n !foo\n >1\n value\n",
+ "error": false,
+ "json": [
+ {
+ "literal": "value\n",
+ "folded": "value\n"
+ }
+ ]
+ },
+ {
+ "id": "M5DY",
+ "name": "Spec Example 2.11. Mapping between Sequences",
+ "yaml": "? - Detroit Tigers\n - Chicago cubs\n:\n - 2001-07-23\n\n? [ New York Yankees,\n Atlanta Braves ]\n: [ 2001-07-02, 2001-08-12,\n 2001-08-14 ]\n",
+ "error": false
+ },
+ {
+ "id": "M6YH",
+ "name": "Block sequence indentation",
+ "yaml": "- |\n x\n-\n foo: bar\n-\n - 42\n",
+ "error": false,
+ "json": [
+ [
+ "x\n",
+ {
+ "foo": "bar"
+ },
+ [
+ 42
+ ]
+ ]
+ ]
+ },
+ {
+ "id": "M7A3",
+ "name": "Spec Example 9.3. Bare Documents",
+ "yaml": "Bare\ndocument\n...\n# No document\n...\n|\n%!PS-Adobe-2.0 # Not the first line\n",
+ "error": false,
+ "json": [
+ "Bare document",
+ "%!PS-Adobe-2.0 # Not the first line\n"
+ ]
+ },
+ {
+ "id": "M7NX",
+ "name": "Nested flow collections",
+ "yaml": "---\n{\n a: [\n b, c, {\n d: [e, f]\n }\n ]\n}\n",
+ "error": false,
+ "json": [
+ {
+ "a": [
+ "b",
+ "c",
+ {
+ "d": [
+ "e",
+ "f"
+ ]
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "id": "M9B4",
+ "name": "Spec Example 8.7. Literal Scalar",
+ "yaml": "|\n literal\n \ttext\n\n\n",
+ "error": false,
+ "json": [
+ "literal\n\ttext\n"
+ ]
+ },
+ {
+ "id": "MJS9",
+ "name": "Spec Example 6.7. Block Folding",
+ "yaml": ">\n foo \n \n \t bar\n\n baz\n",
+ "error": false,
+ "json": [
+ "foo \n\n\t bar\n\nbaz\n"
+ ]
+ },
+ {
+ "id": "MUS6/00",
+ "name": "Directive variants",
+ "yaml": "%YAML 1.1#...\n---\n",
+ "error": true
+ },
+ {
+ "id": "MUS6/01",
+ "name": "Directive variants",
+ "yaml": "%YAML 1.2\n---\n%YAML 1.2\n---\n",
+ "error": true
+ },
+ {
+ "id": "MUS6/02",
+ "name": "Directive variants",
+ "yaml": "%YAML 1.1\n---\n",
+ "error": false,
+ "json": [
+ null
+ ]
+ },
+ {
+ "id": "MUS6/03",
+ "name": "Directive variants",
+ "yaml": "%YAML \t 1.1\n---\n",
+ "error": false,
+ "json": [
+ null
+ ]
+ },
+ {
+ "id": "MUS6/04",
+ "name": "Directive variants",
+ "yaml": "%YAML 1.1 # comment\n---\n",
+ "error": false,
+ "json": [
+ null
+ ]
+ },
+ {
+ "id": "MUS6/05",
+ "name": "Directive variants",
+ "yaml": "%YAM 1.1\n---\n",
+ "error": false,
+ "json": [
+ null
+ ]
+ },
+ {
+ "id": "MUS6/06",
+ "name": "Directive variants",
+ "yaml": "%YAMLL 1.1\n---\n",
+ "error": false,
+ "json": [
+ null
+ ]
+ },
+ {
+ "id": "MXS3",
+ "name": "Flow Mapping in Block Sequence",
+ "yaml": "- {a: b}\n",
+ "error": false,
+ "json": [
+ [
+ {
+ "a": "b"
+ }
+ ]
+ ]
+ },
+ {
+ "id": "MYW6",
+ "name": "Block Scalar Strip",
+ "yaml": "|-\n ab\n \n \n...\n",
+ "error": false,
+ "json": [
+ "ab"
+ ]
+ },
+ {
+ "id": "MZX3",
+ "name": "Non-Specific Tags on Scalars",
+ "yaml": "- plain\n- \"double quoted\"\n- 'single quoted'\n- >\n block\n- plain again\n",
+ "error": false,
+ "json": [
+ [
+ "plain",
+ "double quoted",
+ "single quoted",
+ "block\n",
+ "plain again"
+ ]
+ ]
+ },
+ {
+ "id": "N4JP",
+ "name": "Bad indentation in mapping",
+ "yaml": "map:\n key1: \"quoted1\"\n key2: \"bad indentation\"\n",
+ "error": true
+ },
+ {
+ "id": "N782",
+ "name": "Invalid document markers in flow style",
+ "yaml": "[\n--- ,\n...\n]\n",
+ "error": true
+ },
+ {
+ "id": "NAT4",
+ "name": "Various empty or newline only quoted strings",
+ "yaml": "---\na: '\n '\nb: ' \n '\nc: \"\n \"\nd: \" \n \"\ne: '\n\n '\nf: \"\n\n \"\ng: '\n\n\n '\nh: \"\n\n\n \"\n",
+ "error": false,
+ "json": [
+ {
+ "a": " ",
+ "b": " ",
+ "c": " ",
+ "d": " ",
+ "e": "\n",
+ "f": "\n",
+ "g": "\n\n",
+ "h": "\n\n"
+ }
+ ]
+ },
+ {
+ "id": "NB6Z",
+ "name": "Multiline plain value with tabs on empty lines",
+ "yaml": "key:\n value\n with\n \t\n tabs\n",
+ "error": false,
+ "json": [
+ {
+ "key": "value with\ntabs"
+ }
+ ]
+ },
+ {
+ "id": "NHX8",
+ "name": "Empty Lines at End of Document",
+ "yaml": ":\n\n\n",
+ "error": false
+ },
+ {
+ "id": "NJ66",
+ "name": "Multiline plain flow mapping key",
+ "yaml": "---\n- { single line: value}\n- { multi\n line: value}\n",
+ "error": false,
+ "json": [
+ [
+ {
+ "single line": "value"
+ },
+ {
+ "multi line": "value"
+ }
+ ]
+ ]
+ },
+ {
+ "id": "NKF9",
+ "name": "Empty keys in block and flow mapping",
+ "yaml": "---\nkey: value\n: empty key\n---\n{\n key: value, : empty key\n}\n---\n# empty key and value\n:\n---\n# empty key and value\n{ : }\n",
+ "error": false
+ },
+ {
+ "id": "NP9H",
+ "name": "Spec Example 7.5. Double Quoted Line Breaks",
+ "yaml": "\"folded \nto a space,\t\n \nto a line feed, or \t\\\n \\ \tnon-content\"\n",
+ "error": false,
+ "json": [
+ "folded to a space,\nto a line feed, or \t \tnon-content"
+ ]
+ },
+ {
+ "id": "P2AD",
+ "name": "Spec Example 8.1. Block Scalar Header",
+ "yaml": "- | # Empty header\u2193\n literal\n- >1 # Indentation indicator\u2193\n folded\n- |+ # Chomping indicator\u2193\n keep\n\n- >1- # Both indicators\u2193\n strip\n",
+ "error": false,
+ "json": [
+ [
+ "literal\n",
+ " folded\n",
+ "keep\n\n",
+ " strip"
+ ]
+ ]
+ },
+ {
+ "id": "P2EQ",
+ "name": "Invalid sequene item on same line as previous item",
+ "yaml": "---\n- { y: z }- invalid\n",
+ "error": true
+ },
+ {
+ "id": "P76L",
+ "name": "Spec Example 6.19. Secondary Tag Handle",
+ "yaml": "%TAG !! tag:example.com,2000:app/\n---\n!!int 1 - 3 # Interval, not integer\n",
+ "error": false,
+ "json": [
+ "1 - 3"
+ ]
+ },
+ {
+ "id": "P94K",
+ "name": "Spec Example 6.11. Multi-Line Comments",
+ "yaml": "key: # Comment\n # lines\n value\n\n\n",
+ "error": false,
+ "json": [
+ {
+ "key": "value"
+ }
+ ]
+ },
+ {
+ "id": "PBJ2",
+ "name": "Spec Example 2.3. Mapping Scalars to Sequences",
+ "yaml": "american:\n - Boston Red Sox\n - Detroit Tigers\n - New York Yankees\nnational:\n - New York Mets\n - Chicago Cubs\n - Atlanta Braves\n",
+ "error": false,
+ "json": [
+ {
+ "american": [
+ "Boston Red Sox",
+ "Detroit Tigers",
+ "New York Yankees"
+ ],
+ "national": [
+ "New York Mets",
+ "Chicago Cubs",
+ "Atlanta Braves"
+ ]
+ }
+ ]
+ },
+ {
+ "id": "PRH3",
+ "name": "Spec Example 7.9. Single Quoted Lines",
+ "yaml": "' 1st non-empty\n\n 2nd non-empty \n\t3rd non-empty '\n",
+ "error": false,
+ "json": [
+ " 1st non-empty\n2nd non-empty 3rd non-empty "
+ ]
+ },
+ {
+ "id": "PUW8",
+ "name": "Document start on last line",
+ "yaml": "---\na: b\n---\n",
+ "error": false,
+ "json": [
+ {
+ "a": "b"
+ },
+ null
+ ]
+ },
+ {
+ "id": "PW8X",
+ "name": "Anchors on Empty Scalars",
+ "yaml": "- &a\n- a\n-\n &a : a\n b: &b\n-\n &c : &a\n-\n ? &d\n-\n ? &e\n : &a\n",
+ "error": false
+ },
+ {
+ "id": "Q4CL",
+ "name": "Trailing content after quoted value",
+ "yaml": "key1: \"quoted1\"\nkey2: \"quoted2\" trailing content\nkey3: \"quoted3\"\n",
+ "error": true
+ },
+ {
+ "id": "Q5MG",
+ "name": "Tab at beginning of line followed by a flow mapping",
+ "yaml": "\t{}\n",
+ "error": false,
+ "json": [
+ {}
+ ]
+ },
+ {
+ "id": "Q88A",
+ "name": "Spec Example 7.23. Flow Content",
+ "yaml": "- [ a, b ]\n- { a: b }\n- \"a\"\n- 'b'\n- c\n",
+ "error": false,
+ "json": [
+ [
+ [
+ "a",
+ "b"
+ ],
+ {
+ "a": "b"
+ },
+ "a",
+ "b",
+ "c"
+ ]
+ ]
+ },
+ {
+ "id": "Q8AD",
+ "name": "Spec Example 7.5. Double Quoted Line Breaks [1.3]",
+ "yaml": "---\n\"folded \nto a space,\n \nto a line feed, or \t\\\n \\ \tnon-content\"\n",
+ "error": false,
+ "json": [
+ "folded to a space,\nto a line feed, or \t \tnon-content"
+ ]
+ },
+ {
+ "id": "Q9WF",
+ "name": "Spec Example 6.12. Separation Spaces",
+ "yaml": "{ first: Sammy, last: Sosa }:\n# Statistics:\n hr: # Home runs\n 65\n avg: # Average\n 0.278\n",
+ "error": false
+ },
+ {
+ "id": "QB6E",
+ "name": "Wrong indented multiline quoted scalar",
+ "yaml": "---\nquoted: \"a\nb\nc\"\n",
+ "error": true
+ },
+ {
+ "id": "QF4Y",
+ "name": "Spec Example 7.19. Single Pair Flow Mappings",
+ "yaml": "[\nfoo: bar\n]\n",
+ "error": false,
+ "json": [
+ [
+ {
+ "foo": "bar"
+ }
+ ]
+ ]
+ },
+ {
+ "id": "QLJ7",
+ "name": "Tag shorthand used in documents but only defined in the first",
+ "yaml": "%TAG !prefix! tag:example.com,2011:\n--- !prefix!A\na: b\n--- !prefix!B\nc: d\n--- !prefix!C\ne: f\n",
+ "error": true
+ },
+ {
+ "id": "QT73",
+ "name": "Comment and document-end marker",
+ "yaml": "# comment\n...\n",
+ "error": false,
+ "json": []
+ },
+ {
+ "id": "R4YG",
+ "name": "Spec Example 8.2. Block Indentation Indicator",
+ "yaml": "- |\n detected\n- >\n \n \n # detected\n- |1\n explicit\n- >\n \t\n detected\n",
+ "error": false,
+ "json": [
+ [
+ "detected\n",
+ "\n\n# detected\n",
+ " explicit\n",
+ "\t\ndetected\n"
+ ]
+ ]
+ },
+ {
+ "id": "R52L",
+ "name": "Nested flow mapping sequence and mappings",
+ "yaml": "---\n{ top1: [item1, {key2: value2}, item3], top2: value2 }\n",
+ "error": false,
+ "json": [
+ {
+ "top1": [
+ "item1",
+ {
+ "key2": "value2"
+ },
+ "item3"
+ ],
+ "top2": "value2"
+ }
+ ]
+ },
+ {
+ "id": "RHX7",
+ "name": "YAML directive without document end marker",
+ "yaml": "---\nkey: value\n%YAML 1.2\n---\n",
+ "error": true
+ },
+ {
+ "id": "RLU9",
+ "name": "Sequence Indent",
+ "yaml": "foo:\n- 42\nbar:\n - 44\n",
+ "error": false,
+ "json": [
+ {
+ "foo": [
+ 42
+ ],
+ "bar": [
+ 44
+ ]
+ }
+ ]
+ },
+ {
+ "id": "RR7F",
+ "name": "Mixed Block Mapping (implicit to explicit)",
+ "yaml": "a: 4.2\n? d\n: 23\n",
+ "error": false,
+ "json": [
+ {
+ "d": 23,
+ "a": 4.2
+ }
+ ]
+ },
+ {
+ "id": "RTP8",
+ "name": "Spec Example 9.2. Document Markers",
+ "yaml": "%YAML 1.2\n---\nDocument\n... # Suffix\n",
+ "error": false,
+ "json": [
+ "Document"
+ ]
+ },
+ {
+ "id": "RXY3",
+ "name": "Invalid document-end marker in single quoted string",
+ "yaml": "---\n'\n...\n'\n",
+ "error": true
+ },
+ {
+ "id": "RZP5",
+ "name": "Various Trailing Comments [1.3]",
+ "yaml": "a: \"double\n quotes\" # lala\nb: plain\n value # lala\nc : #lala\n d\n? # lala\n - seq1\n: # lala\n - #lala\n seq2\ne: &node # lala\n - x: y\nblock: > # lala\n abcde\n",
+ "error": false
+ },
+ {
+ "id": "RZT7",
+ "name": "Spec Example 2.28. Log File",
+ "yaml": "---\nTime: 2001-11-23 15:01:42 -5\nUser: ed\nWarning:\n This is an error message\n for the log file\n---\nTime: 2001-11-23 15:02:31 -5\nUser: ed\nWarning:\n A slightly different error\n message.\n---\nDate: 2001-11-23 15:03:17 -5\nUser: ed\nFatal:\n Unknown variable \"bar\"\nStack:\n - file: TopClass.py\n line: 23\n code: |\n x = MoreObject(\"345\\n\")\n - file: MoreClass.py\n line: 58\n code: |-\n foo = bar\n",
+ "error": false,
+ "json": [
+ {
+ "Time": "2001-11-23 15:01:42 -5",
+ "User": "ed",
+ "Warning": "This is an error message for the log file"
+ },
+ {
+ "Time": "2001-11-23 15:02:31 -5",
+ "User": "ed",
+ "Warning": "A slightly different error message."
+ },
+ {
+ "Date": "2001-11-23 15:03:17 -5",
+ "User": "ed",
+ "Fatal": "Unknown variable \"bar\"",
+ "Stack": [
+ {
+ "file": "TopClass.py",
+ "line": 23,
+ "code": "x = MoreObject(\"345\\n\")\n"
+ },
+ {
+ "file": "MoreClass.py",
+ "line": 58,
+ "code": "foo = bar"
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "id": "S3PD",
+ "name": "Spec Example 8.18. Implicit Block Mapping Entries",
+ "yaml": "plain key: in-line value\n: # Both empty\n\"quoted key\":\n- entry\n",
+ "error": false
+ },
+ {
+ "id": "S4GJ",
+ "name": "Invalid text after block scalar indicator",
+ "yaml": "---\nfolded: > first line\n second line\n",
+ "error": true
+ },
+ {
+ "id": "S4JQ",
+ "name": "Spec Example 6.28. Non-Specific Tags",
+ "yaml": "# Assuming conventional resolution:\n- \"12\"\n- 12\n- ! 12\n",
+ "error": false,
+ "json": [
+ [
+ "12",
+ 12,
+ "12"
+ ]
+ ]
+ },
+ {
+ "id": "S4T7",
+ "name": "Document with footer",
+ "yaml": "aaa: bbb\n...\n",
+ "error": false,
+ "json": [
+ {
+ "aaa": "bbb"
+ }
+ ]
+ },
+ {
+ "id": "S7BG",
+ "name": "Colon followed by comma",
+ "yaml": "---\n- :,\n",
+ "error": false,
+ "json": [
+ [
+ ":,"
+ ]
+ ]
+ },
+ {
+ "id": "S98Z",
+ "name": "Block scalar with more spaces than first content line",
+ "yaml": "empty block scalar: >\n \n \n \n # comment\n",
+ "error": true
+ },
+ {
+ "id": "S9E8",
+ "name": "Spec Example 5.3. Block Structure Indicators",
+ "yaml": "sequence:\n- one\n- two\nmapping:\n ? sky\n : blue\n sea : green\n",
+ "error": false,
+ "json": [
+ {
+ "sequence": [
+ "one",
+ "two"
+ ],
+ "mapping": {
+ "sky": "blue",
+ "sea": "green"
+ }
+ }
+ ]
+ },
+ {
+ "id": "SBG9",
+ "name": "Flow Sequence in Flow Mapping",
+ "yaml": "{a: [b, c], [d, e]: f}\n",
+ "error": false
+ },
+ {
+ "id": "SF5V",
+ "name": "Duplicate YAML directive",
+ "yaml": "%YAML 1.2\n%YAML 1.2\n---\n",
+ "error": true
+ },
+ {
+ "id": "SKE5",
+ "name": "Anchor before zero indented sequence",
+ "yaml": "---\nseq:\n &anchor\n- a\n- b\n",
+ "error": false,
+ "json": [
+ {
+ "seq": [
+ "a",
+ "b"
+ ]
+ }
+ ]
+ },
+ {
+ "id": "SM9W/00",
+ "name": "Single character streams",
+ "yaml": "-",
+ "error": false,
+ "json": [
+ [
+ null
+ ]
+ ]
+ },
+ {
+ "id": "SM9W/01",
+ "name": "Single character streams",
+ "yaml": ":",
+ "error": false
+ },
+ {
+ "id": "SR86",
+ "name": "Anchor plus Alias",
+ "yaml": "key1: &a value\nkey2: &b *a\n",
+ "error": true
+ },
+ {
+ "id": "SSW6",
+ "name": "Spec Example 7.7. Single Quoted Characters [1.3]",
+ "yaml": "---\n'here''s to \"quotes\"'\n",
+ "error": false,
+ "json": [
+ "here's to \"quotes\""
+ ]
+ },
+ {
+ "id": "SU5Z",
+ "name": "Comment without whitespace after doublequoted scalar",
+ "yaml": "key: \"value\"# invalid comment\n",
+ "error": true
+ },
+ {
+ "id": "SU74",
+ "name": "Anchor and alias as mapping key",
+ "yaml": "key1: &alias value1\n&b *alias : value2\n",
+ "error": true
+ },
+ {
+ "id": "SY6V",
+ "name": "Anchor before sequence entry on same line",
+ "yaml": "&anchor - sequence entry\n",
+ "error": true
+ },
+ {
+ "id": "SYW4",
+ "name": "Spec Example 2.2. Mapping Scalars to Scalars",
+ "yaml": "hr: 65 # Home runs\navg: 0.278 # Batting average\nrbi: 147 # Runs Batted In\n",
+ "error": false,
+ "json": [
+ {
+ "hr": 65,
+ "avg": 0.278,
+ "rbi": 147
+ }
+ ]
+ },
+ {
+ "id": "T26H",
+ "name": "Spec Example 8.8. Literal Content [1.3]",
+ "yaml": "--- |\n \n \n literal\n \n \n text\n\n # Comment\n",
+ "error": false,
+ "json": [
+ "\n\nliteral\n \n\ntext\n"
+ ]
+ },
+ {
+ "id": "T4YY",
+ "name": "Spec Example 7.9. Single Quoted Lines [1.3]",
+ "yaml": "---\n' 1st non-empty\n\n 2nd non-empty \n 3rd non-empty '\n",
+ "error": false,
+ "json": [
+ " 1st non-empty\n2nd non-empty 3rd non-empty "
+ ]
+ },
+ {
+ "id": "T5N4",
+ "name": "Spec Example 8.7. Literal Scalar [1.3]",
+ "yaml": "--- |\n literal\n \ttext\n\n\n",
+ "error": false,
+ "json": [
+ "literal\n\ttext\n"
+ ]
+ },
+ {
+ "id": "T833",
+ "name": "Flow mapping missing a separating comma",
+ "yaml": "---\n{\n foo: 1\n bar: 2 }\n",
+ "error": true
+ },
+ {
+ "id": "TD5N",
+ "name": "Invalid scalar after sequence",
+ "yaml": "- item1\n- item2\ninvalid\n",
+ "error": true
+ },
+ {
+ "id": "TE2A",
+ "name": "Spec Example 8.16. Block Mappings",
+ "yaml": "block mapping:\n key: value\n",
+ "error": false,
+ "json": [
+ {
+ "block mapping": {
+ "key": "value"
+ }
+ }
+ ]
+ },
+ {
+ "id": "TL85",
+ "name": "Spec Example 6.8. Flow Folding",
+ "yaml": "\"\n foo \n \n \t bar\n\n baz\n\"\n",
+ "error": false,
+ "json": [
+ " foo\nbar\nbaz "
+ ]
+ },
+ {
+ "id": "TS54",
+ "name": "Folded Block Scalar",
+ "yaml": ">\n ab\n cd\n \n ef\n\n\n gh\n",
+ "error": false,
+ "json": [
+ "ab cd\nef\n\ngh\n"
+ ]
+ },
+ {
+ "id": "U3C3",
+ "name": "Spec Example 6.16. \u201cTAG\u201d directive",
+ "yaml": "%TAG !yaml! tag:yaml.org,2002:\n---\n!yaml!str \"foo\"\n",
+ "error": false,
+ "json": [
+ "foo"
+ ]
+ },
+ {
+ "id": "U3XV",
+ "name": "Node and Mapping Key Anchors",
+ "yaml": "---\ntop1: &node1\n &k1 key1: one\ntop2: &node2 # comment\n key2: two\ntop3:\n &k3 key3: three\ntop4:\n &node4\n &k4 key4: four\ntop5:\n &node5\n key5: five\ntop6: &val6\n six\ntop7:\n &val7 seven\n",
+ "error": false,
+ "json": [
+ {
+ "top1": {
+ "key1": "one"
+ },
+ "top2": {
+ "key2": "two"
+ },
+ "top3": {
+ "key3": "three"
+ },
+ "top4": {
+ "key4": "four"
+ },
+ "top5": {
+ "key5": "five"
+ },
+ "top6": "six",
+ "top7": "seven"
+ }
+ ]
+ },
+ {
+ "id": "U44R",
+ "name": "Bad indentation in mapping (2)",
+ "yaml": "map:\n key1: \"quoted1\"\n key2: \"bad indentation\"\n",
+ "error": true
+ },
+ {
+ "id": "U99R",
+ "name": "Invalid comma in tag",
+ "yaml": "- !!str, xxx\n",
+ "error": true
+ },
+ {
+ "id": "U9NS",
+ "name": "Spec Example 2.8. Play by Play Feed from a Game",
+ "yaml": "---\ntime: 20:03:20\nplayer: Sammy Sosa\naction: strike (miss)\n...\n---\ntime: 20:03:47\nplayer: Sammy Sosa\naction: grand slam\n...\n",
+ "error": false,
+ "json": [
+ {
+ "time": "20:03:20",
+ "player": "Sammy Sosa",
+ "action": "strike (miss)"
+ },
+ {
+ "time": "20:03:47",
+ "player": "Sammy Sosa",
+ "action": "grand slam"
+ }
+ ]
+ },
+ {
+ "id": "UDM2",
+ "name": "Plain URL in flow mapping",
+ "yaml": "- { url: http://example.org }\n",
+ "error": false,
+ "json": [
+ [
+ {
+ "url": "http://example.org"
+ }
+ ]
+ ]
+ },
+ {
+ "id": "UDR7",
+ "name": "Spec Example 5.4. Flow Collection Indicators",
+ "yaml": "sequence: [ one, two, ]\nmapping: { sky: blue, sea: green }\n",
+ "error": false,
+ "json": [
+ {
+ "sequence": [
+ "one",
+ "two"
+ ],
+ "mapping": {
+ "sky": "blue",
+ "sea": "green"
+ }
+ }
+ ]
+ },
+ {
+ "id": "UGM3",
+ "name": "Spec Example 2.27. Invoice",
+ "yaml": "--- !\ninvoice: 34843\ndate : 2001-01-23\nbill-to: &id001\n given : Chris\n family : Dumars\n address:\n lines: |\n 458 Walkman Dr.\n Suite #292\n city : Royal Oak\n state : MI\n postal : 48046\nship-to: *id001\nproduct:\n - sku : BL394D\n quantity : 4\n description : Basketball\n price : 450.00\n - sku : BL4438H\n quantity : 1\n description : Super Hoop\n price : 2392.00\ntax : 251.42\ntotal: 4443.52\ncomments:\n Late afternoon is best.\n Backup contact is Nancy\n Billsmer @ 338-4338.\n",
+ "error": false,
+ "json": [
+ {
+ "invoice": 34843,
+ "date": "2001-01-23",
+ "bill-to": {
+ "given": "Chris",
+ "family": "Dumars",
+ "address": {
+ "lines": "458 Walkman Dr.\nSuite #292\n",
+ "city": "Royal Oak",
+ "state": "MI",
+ "postal": 48046
+ }
+ },
+ "ship-to": {
+ "given": "Chris",
+ "family": "Dumars",
+ "address": {
+ "lines": "458 Walkman Dr.\nSuite #292\n",
+ "city": "Royal Oak",
+ "state": "MI",
+ "postal": 48046
+ }
+ },
+ "product": [
+ {
+ "sku": "BL394D",
+ "quantity": 4,
+ "description": "Basketball",
+ "price": 450
+ },
+ {
+ "sku": "BL4438H",
+ "quantity": 1,
+ "description": "Super Hoop",
+ "price": 2392
+ }
+ ],
+ "tax": 251.42,
+ "total": 4443.52,
+ "comments": "Late afternoon is best. Backup contact is Nancy Billsmer @ 338-4338."
+ }
+ ]
+ },
+ {
+ "id": "UKK6/00",
+ "name": "Syntax character edge cases",
+ "yaml": "- :\n",
+ "error": false
+ },
+ {
+ "id": "UKK6/01",
+ "name": "Syntax character edge cases",
+ "yaml": "::\n",
+ "error": false,
+ "json": [
+ {
+ ":": null
+ }
+ ]
+ },
+ {
+ "id": "UKK6/02",
+ "name": "Syntax character edge cases",
+ "yaml": "!\n",
+ "error": false
+ },
+ {
+ "id": "UT92",
+ "name": "Spec Example 9.4. Explicit Documents",
+ "yaml": "---\n{ matches\n% : 20 }\n...\n---\n# Empty\n...\n",
+ "error": false,
+ "json": [
+ {
+ "matches %": 20
+ },
+ null
+ ]
+ },
+ {
+ "id": "UV7Q",
+ "name": "Legal tab after indentation",
+ "yaml": "x:\n - x\n \tx\n",
+ "error": false,
+ "json": [
+ {
+ "x": [
+ "x x"
+ ]
+ }
+ ]
+ },
+ {
+ "id": "V55R",
+ "name": "Aliases in Block Sequence",
+ "yaml": "- &a a\n- &b b\n- *a\n- *b\n",
+ "error": false,
+ "json": [
+ [
+ "a",
+ "b",
+ "a",
+ "b"
+ ]
+ ]
+ },
+ {
+ "id": "V9D5",
+ "name": "Spec Example 8.19. Compact Block Mappings",
+ "yaml": "- sun: yellow\n- ? earth: blue\n : moon: white\n",
+ "error": false
+ },
+ {
+ "id": "VJP3/00",
+ "name": "Flow collections over many lines",
+ "yaml": "k: {\nk\n:\nv\n}\n",
+ "error": true
+ },
+ {
+ "id": "VJP3/01",
+ "name": "Flow collections over many lines",
+ "yaml": "k: {\n k\n :\n v\n }\n",
+ "error": false,
+ "json": [
+ {
+ "k": {
+ "k": "v"
+ }
+ }
+ ]
+ },
+ {
+ "id": "W42U",
+ "name": "Spec Example 8.15. Block Sequence Entry Types",
+ "yaml": "- # Empty\n- |\n block node\n- - one # Compact\n - two # sequence\n- one: two # Compact mapping\n",
+ "error": false,
+ "json": [
+ [
+ null,
+ "block node\n",
+ [
+ "one",
+ "two"
+ ],
+ {
+ "one": "two"
+ }
+ ]
+ ]
+ },
+ {
+ "id": "W4TN",
+ "name": "Spec Example 9.5. Directives Documents",
+ "yaml": "%YAML 1.2\n--- |\n%!PS-Adobe-2.0\n...\n%YAML 1.2\n---\n# Empty\n...\n",
+ "error": false,
+ "json": [
+ "%!PS-Adobe-2.0\n",
+ null
+ ]
+ },
+ {
+ "id": "W5VH",
+ "name": "Allowed characters in alias",
+ "yaml": "a: &:@*!$\": scalar a\nb: *:@*!$\":\n",
+ "error": false,
+ "json": [
+ {
+ "a": "scalar a",
+ "b": "scalar a"
+ }
+ ]
+ },
+ {
+ "id": "W9L4",
+ "name": "Literal block scalar with more spaces in first line",
+ "yaml": "---\nblock scalar: |\n \n more spaces at the beginning\n are invalid\n",
+ "error": true
+ },
+ {
+ "id": "WZ62",
+ "name": "Spec Example 7.2. Empty Content",
+ "yaml": "{\n foo : !!str,\n !!str : bar,\n}\n",
+ "error": false,
+ "json": [
+ {
+ "foo": "",
+ "": "bar"
+ }
+ ]
+ },
+ {
+ "id": "X38W",
+ "name": "Aliases in Flow Objects",
+ "yaml": "{ &a [a, &b b]: *b, *a : [c, *b, d]}\n",
+ "error": false
+ },
+ {
+ "id": "X4QW",
+ "name": "Comment without whitespace after block scalar indicator",
+ "yaml": "block: ># comment\n scalar\n",
+ "error": true
+ },
+ {
+ "id": "X8DW",
+ "name": "Explicit key and value seperated by comment",
+ "yaml": "---\n? key\n# comment\n: value\n",
+ "error": false,
+ "json": [
+ {
+ "key": "value"
+ }
+ ]
+ },
+ {
+ "id": "XLQ9",
+ "name": "Multiline scalar that looks like a YAML directive",
+ "yaml": "---\nscalar\n%YAML 1.2\n",
+ "error": false,
+ "json": [
+ "scalar %YAML 1.2"
+ ]
+ },
+ {
+ "id": "XV9V",
+ "name": "Spec Example 6.5. Empty Lines [1.3]",
+ "yaml": "Folding:\n \"Empty line\n\n as a line feed\"\nChomping: |\n Clipped empty lines\n \n\n",
+ "error": false,
+ "json": [
+ {
+ "Folding": "Empty line\nas a line feed",
+ "Chomping": "Clipped empty lines\n"
+ }
+ ]
+ },
+ {
+ "id": "XW4D",
+ "name": "Various Trailing Comments",
+ "yaml": "a: \"double\n quotes\" # lala\nb: plain\n value # lala\nc : #lala\n d\n? # lala\n - seq1\n: # lala\n - #lala\n seq2\ne:\n &node # lala\n - x: y\nblock: > # lala\n abcde\n",
+ "error": false
+ },
+ {
+ "id": "Y2GN",
+ "name": "Anchor with colon in the middle",
+ "yaml": "---\nkey: &an:chor value\n",
+ "error": false,
+ "json": [
+ {
+ "key": "value"
+ }
+ ]
+ },
+ {
+ "id": "Y79Y/000",
+ "name": "Tabs in various contexts",
+ "yaml": "foo: |\n\t\nbar: 1\n",
+ "error": true
+ },
+ {
+ "id": "Y79Y/001",
+ "name": "Tabs in various contexts",
+ "yaml": "foo: |\n \t\nbar: 1\n",
+ "error": false,
+ "json": [
+ {
+ "foo": "\t\n",
+ "bar": 1
+ }
+ ]
+ },
+ {
+ "id": "Y79Y/002",
+ "name": "Tabs in various contexts",
+ "yaml": "- [\n\t\n foo\n ]\n",
+ "error": false,
+ "json": [
+ [
+ [
+ "foo"
+ ]
+ ]
+ ]
+ },
+ {
+ "id": "Y79Y/003",
+ "name": "Tabs in various contexts",
+ "yaml": "- [\n\tfoo,\n foo\n ]\n",
+ "error": true
+ },
+ {
+ "id": "Y79Y/004",
+ "name": "Tabs in various contexts",
+ "yaml": "-\t-\n",
+ "error": true
+ },
+ {
+ "id": "Y79Y/005",
+ "name": "Tabs in various contexts",
+ "yaml": "- \t-\n",
+ "error": true
+ },
+ {
+ "id": "Y79Y/006",
+ "name": "Tabs in various contexts",
+ "yaml": "?\t-\n",
+ "error": true
+ },
+ {
+ "id": "Y79Y/007",
+ "name": "Tabs in various contexts",
+ "yaml": "? -\n:\t-\n",
+ "error": true
+ },
+ {
+ "id": "Y79Y/008",
+ "name": "Tabs in various contexts",
+ "yaml": "?\tkey:\n",
+ "error": true
+ },
+ {
+ "id": "Y79Y/009",
+ "name": "Tabs in various contexts",
+ "yaml": "? key:\n:\tkey:\n",
+ "error": true
+ },
+ {
+ "id": "Y79Y/010",
+ "name": "Tabs in various contexts",
+ "yaml": "-\t-1\n",
+ "error": false,
+ "json": [
+ [
+ -1
+ ]
+ ]
+ },
+ {
+ "id": "YD5X",
+ "name": "Spec Example 2.5. Sequence of Sequences",
+ "yaml": "- [name , hr, avg ]\n- [Mark McGwire, 65, 0.278]\n- [Sammy Sosa , 63, 0.288]\n",
+ "error": false,
+ "json": [
+ [
+ [
+ "name",
+ "hr",
+ "avg"
+ ],
+ [
+ "Mark McGwire",
+ 65,
+ 0.278
+ ],
+ [
+ "Sammy Sosa",
+ 63,
+ 0.288
+ ]
+ ]
+ ]
+ },
+ {
+ "id": "YJV2",
+ "name": "Dash in flow sequence",
+ "yaml": "[-]\n",
+ "error": true
+ },
+ {
+ "id": "Z67P",
+ "name": "Spec Example 8.21. Block Scalar Nodes [1.3]",
+ "yaml": "literal: |2\n value\nfolded: !foo >1\n value\n",
+ "error": false,
+ "json": [
+ {
+ "literal": "value\n",
+ "folded": "value\n"
+ }
+ ]
+ },
+ {
+ "id": "Z9M4",
+ "name": "Spec Example 6.22. Global Tag Prefix",
+ "yaml": "%TAG !e! tag:example.com,2000:app/\n---\n- !e!foo \"bar\"\n",
+ "error": false,
+ "json": [
+ [
+ "bar"
+ ]
+ ]
+ },
+ {
+ "id": "ZCZ6",
+ "name": "Invalid mapping in plain single line value",
+ "yaml": "a: b: c: d\n",
+ "error": true
+ },
+ {
+ "id": "ZF4X",
+ "name": "Spec Example 2.6. Mapping of Mappings",
+ "yaml": "Mark McGwire: {hr: 65, avg: 0.278}\nSammy Sosa: {\n hr: 63,\n avg: 0.288\n }\n",
+ "error": false,
+ "json": [
+ {
+ "Mark McGwire": {
+ "hr": 65,
+ "avg": 0.278
+ },
+ "Sammy Sosa": {
+ "hr": 63,
+ "avg": 0.288
+ }
+ }
+ ]
+ },
+ {
+ "id": "ZH7C",
+ "name": "Anchors in Mapping",
+ "yaml": "&a a: b\nc: &d d\n",
+ "error": false,
+ "json": [
+ {
+ "a": "b",
+ "c": "d"
+ }
+ ]
+ },
+ {
+ "id": "ZK9H",
+ "name": "Nested top level flow mapping",
+ "yaml": "{ key: [[[\n value\n ]]]\n}\n",
+ "error": false,
+ "json": [
+ {
+ "key": [
+ [
+ [
+ "value"
+ ]
+ ]
+ ]
+ }
+ ]
+ },
+ {
+ "id": "ZL4Z",
+ "name": "Invalid nested mapping",
+ "yaml": "---\na: 'b': c\n",
+ "error": true
+ },
+ {
+ "id": "ZVH3",
+ "name": "Wrong indented sequence item",
+ "yaml": "- key: value\n - item1\n",
+ "error": true
+ },
+ {
+ "id": "ZWK4",
+ "name": "Key with anchor after missing explicit mapping value",
+ "yaml": "---\na: 1\n? b\n&anchor c: 3\n",
+ "error": false,
+ "json": [
+ {
+ "a": 1,
+ "b": null,
+ "c": 3
+ }
+ ]
+ },
+ {
+ "id": "ZXT5",
+ "name": "Implicit key followed by newline and adjacent value",
+ "yaml": "[ \"key\"\n :value ]\n",
+ "error": true
+ }
+]
\ No newline at end of file
diff --git a/test/parallel/test-module-builtin-experimental.js b/test/parallel/test-module-builtin-experimental.js
index 444c254b4ffd..46f17bcf1f39 100644
--- a/test/parallel/test-module-builtin-experimental.js
+++ b/test/parallel/test-module-builtin-experimental.js
@@ -9,6 +9,7 @@ const experimentalBuiltins = [
['bench/reporters', '--experimental-bench', true],
['dtls', '--experimental-dtls', common.hasDtls],
['quic', '--experimental-quic', common.hasQuic],
+ ['yaml', '--experimental-yaml', true],
].filter(([, , available]) => available);
for (const [id, flag] of experimentalBuiltins) {
diff --git a/test/parallel/test-process-get-builtin.mjs b/test/parallel/test-process-get-builtin.mjs
index a4feabf5b93c..a180d79f0e82 100644
--- a/test/parallel/test-process-get-builtin.mjs
+++ b/test/parallel/test-process-get-builtin.mjs
@@ -40,6 +40,8 @@ publicBuiltins.delete('node:dtls');
publicBuiltins.delete('node:quic');
// Remove this once node:vfs graduates from unflagged.
publicBuiltins.delete('node:vfs');
+// Remove this once node:yaml graduates from unflagged.
+publicBuiltins.delete('node:yaml');
if (!hasInspector) {
publicBuiltins.delete('inspector');
diff --git a/test/parallel/test-require-resolve.js b/test/parallel/test-require-resolve.js
index 984afa78a1a9..794f062a7692 100644
--- a/test/parallel/test-require-resolve.js
+++ b/test/parallel/test-require-resolve.js
@@ -62,6 +62,8 @@ require(fixtures.path('resolve-paths', 'default', 'verify-paths.js'));
builtinModules.forEach((mod) => {
// TODO(@jasnell): Remove once node:quic is no longer flagged
if (mod === 'node:quic') return;
+ // Remove once node:yaml is no longer flagged
+ if (mod === 'node:yaml') return;
if (mod === 'node:sqlite' && !common.hasSQLite) return;
assert.strictEqual(require.resolve.paths(mod), null);
if (!mod.startsWith('node:')) {
diff --git a/test/parallel/test-yaml-test-suite.js b/test/parallel/test-yaml-test-suite.js
new file mode 100644
index 000000000000..cccc0cdb6d6a
--- /dev/null
+++ b/test/parallel/test-yaml-test-suite.js
@@ -0,0 +1,57 @@
+// Flags: --expose-internals
+'use strict';
+
+// Runs the official YAML test suite (https://github.com/yaml/yaml-test-suite,
+// data branch, commit 6ad3d2c62885d82fc349026c136ef560838fdf3d) against the
+// parser. test/fixtures/yaml-test-suite.json holds, for every test, the input
+// and either the expected documents as JSON or a flag that the input is an
+// error.
+
+require('../common');
+const assert = require('node:assert');
+const fixtures = require('../common/fixtures');
+const { parseDocuments } = require('internal/yaml/parser');
+
+// Tests the parser does not pass yet. A test that starts to pass must be
+// removed from this list.
+const knownFailures = new Set([
+ // Duplicate keys, two empty keys and an alias of the first key: the parser
+ // rejects them, which the suite does not check.
+ '2JQS',
+ 'X38W',
+]);
+
+const tests = JSON.parse(fixtures.readSync('yaml-test-suite.json', 'utf8'));
+assert.ok(tests.length > 300);
+
+const failures = [];
+let passed = 0;
+for (const test of tests) {
+ let failure;
+ try {
+ const documents = parseDocuments(test.yaml);
+ if (test.error) {
+ failure = `expected an error, parsed ${JSON.stringify(documents)}`;
+ } else if (test.json !== undefined) {
+ assert.deepStrictEqual(documents, test.json);
+ }
+ } catch (error) {
+ if (!test.error) {
+ failure = error.message;
+ } else if (error.code !== 'ERR_INVALID_YAML') {
+ failure = `unexpected ${error.name}: ${error.message}`;
+ }
+ }
+ if (failure === undefined) {
+ if (knownFailures.has(test.id)) {
+ failures.push(`${test.id} (${test.name}) passes and must leave knownFailures`);
+ } else {
+ passed++;
+ }
+ } else if (!knownFailures.has(test.id)) {
+ failures.push(`${test.id} (${test.name}): ${failure}`);
+ }
+}
+
+assert.deepStrictEqual(failures, [], `${failures.length} failures:\n${failures.join('\n')}`);
+assert.ok(passed > 0);
diff --git a/test/parallel/test-yaml.js b/test/parallel/test-yaml.js
new file mode 100644
index 000000000000..fbbea0f7d005
--- /dev/null
+++ b/test/parallel/test-yaml.js
@@ -0,0 +1,413 @@
+// Flags: --experimental-yaml
+'use strict';
+
+const common = require('../common');
+const assert = require('node:assert');
+const { parse } = require('node:yaml');
+
+common.expectWarning('ExperimentalWarning', 'YAML is an experimental feature and might change at any time');
+
+// Scalars of the core schema.
+assert.deepStrictEqual(parse(`
+nothing: ~
+also: null
+empty:
+yes: true
+no: FALSE
+int: -42
+hex: 0x1F
+octal: 0o17
+float: 1.5e3
+inf: -.inf
+str: yes
+version: 1.2.3
+quoted: "0x10"
+`), {
+ nothing: null,
+ also: null,
+ empty: null,
+ yes: true,
+ no: false,
+ int: -42,
+ hex: 31,
+ octal: 15,
+ float: 1500,
+ inf: -Infinity,
+ str: 'yes',
+ version: '1.2.3',
+ quoted: '0x10',
+});
+assert.ok(Number.isNaN(parse('.NaN')));
+
+// Collections.
+assert.deepStrictEqual(parse(`
+- a
+- - nested
+ - seq
+- key: value
+ other: 2
+-
+ late: true
+- [1, 2, {x: y}]
+`), [
+ 'a',
+ ['nested', 'seq'],
+ { key: 'value', other: 2 },
+ { late: true },
+ [1, 2, { x: 'y' }],
+]);
+
+assert.deepStrictEqual(parse(`
+map:
+ seq:
+ - compact
+ - entries
+ flow: { a: 1, "b":2, c, d: }
+? explicit key
+: explicit value
+? only key
+`), {
+ 'map': { seq: ['compact', 'entries'], flow: { a: 1, b: 2, c: null, d: null } },
+ 'explicit key': 'explicit value',
+ 'only key': null,
+});
+
+assert.deepStrictEqual(parse('[a: 1, b, "c": d]'),
+ [{ a: 1 }, 'b', { c: 'd' }]);
+assert.deepStrictEqual(parse('{ 1: one, true: yes, ~: nothing }'),
+ { 1: 'one', true: 'yes', null: 'nothing' });
+// Collection keys become their JSON text.
+assert.deepStrictEqual(parse('? [a, b]\n: c\n{x: 1}: d'),
+ { '["a","b"]': 'c', '{"x":1}': 'd' });
+
+// Scalar styles.
+assert.deepStrictEqual(parse(`
+plain: a multi
+ line plain
+
+ scalar
+single: 'it''s
+ folded'
+double: "tab\\there\\
+ joined \\u00e9 \\x41"
+literal: |
+ line one
+ indented
+
+ line three
+folded: >-
+ folded
+ text
+
+ kept
+keep: |+
+ text
+
+strip: |-
+ text
+indicator: |2
+ two extra
+`), {
+ plain: 'a multi line plain\nscalar',
+ single: "it's folded",
+ double: 'tab\therejoined \u00e9 A',
+ literal: 'line one\n indented\n\nline three\n',
+ folded: 'folded text\nkept',
+ keep: 'text\n\n',
+ strip: 'text',
+ indicator: ' two extra\n',
+});
+
+// Anchors, aliases and tags.
+{
+ const value = parse(`
+base: &base
+ name: base
+copy: *base
+list: &list
+ - *base
+ - *list
+str: !!str 123
+int: !!int "42"
+float: !!float 1
+tagged: !custom 123
+`);
+ assert.strictEqual(value.copy, value.base);
+ assert.strictEqual(value.list[0], value.base);
+ assert.strictEqual(value.list[1], value.list);
+ assert.strictEqual(value.str, '123');
+ assert.strictEqual(value.int, 42);
+ assert.deepStrictEqual(parse('[!!int -7, !!int 0x1f, !!int 0o17, !!float 2, !!float .inf]'),
+ [-7, 31, 15, 2, Infinity]);
+ assert.strictEqual(value.float, 1);
+ assert.strictEqual(value.tagged, '123');
+}
+
+// Documents.
+assert.strictEqual(parse(''), null);
+assert.strictEqual(parse('# only a comment\n'), null);
+assert.strictEqual(parse('---\n'), null);
+assert.strictEqual(parse('--- text\n...\n'), 'text');
+assert.deepStrictEqual(parse('%YAML 1.2\n---\na: 1\n...\n---\n- 2\n'),
+ [{ a: 1 }, [2]]);
+assert.strictEqual(parse('%TAG !e! tag:example.com,2000:\n--- !e!thing\nx\n'), 'x');
+assert.deepStrictEqual(parse('...\n---\n...\nvalue\n...\n...\n- 1'), [null, 'value', [1]]);
+assert.strictEqual(parse('!!binary abc'), 'abc');
+assert.strictEqual(parse('\uFEFFbom'), 'bom');
+assert.deepStrictEqual(parse('\uFEFF a: 1\n b: 2'), { a: 1, b: 2 });
+
+// Binary input is decoded as UTF-8.
+assert.deepStrictEqual(parse(Buffer.from('caf\u00e9: 1')), { 'caf\u00e9': 1 });
+assert.deepStrictEqual(parse(new TextEncoder().encode('[1]').buffer), [1]);
+{
+ const bytes = new TextEncoder().encode('xx[2]yy');
+ assert.deepStrictEqual(parse(new Uint8Array(bytes.buffer, 2, 3)), [2]);
+ assert.deepStrictEqual(parse(new DataView(bytes.buffer, 2, 3)), [2]);
+ const shared = new SharedArrayBuffer(3);
+ new Uint8Array(shared).set(bytes.subarray(2, 5));
+ assert.deepStrictEqual(parse(shared), [2]);
+ assert.deepStrictEqual(parse(new Uint8Array(shared)), [2]);
+}
+
+// Prototype keys are own properties, as with JSON.parse().
+{
+ const value = parse('__proto__: 1\nconstructor: 2\n');
+ assert.strictEqual(Object.getPrototypeOf(value), Object.prototype);
+ assert.deepStrictEqual(Object.keys(value), ['__proto__', 'constructor']);
+ assert.strictEqual(Object.getOwnPropertyDescriptor(value, '__proto__').value, 1);
+}
+
+// Errors.
+for (const input of [1, null, undefined, {}, [], Symbol('x')]) {
+ assert.throws(() => parse(input), { code: 'ERR_INVALID_ARG_TYPE' });
+}
+const invalid = [
+ ['a: 1\n b: 2', /Implicit mapping keys must be on a single line \(line 1, column 4\)/],
+ ['a: [1]\n b: 2', /Bad indentation \(line 2, column 3\)/],
+ ['a: 1\na: 2', /Duplicate mapping key "a" \(line 2, column 1\)/],
+ ['[1, 2', /Unterminated flow collection/],
+ ['"open', /Unterminated double-quoted scalar \(line 1, column 1\)/],
+ ['"\\q"', /Unknown escape sequence "\\q"/],
+ ['*missing', /Unknown anchor "missing"/],
+ ['a\n b: 1', /Implicit mapping keys must be on a single line/],
+ ['!!int abc', /"abc" is not an integer/],
+ ['!!int 1.0', /"1\.0" is not an integer/],
+ ['!!int 1e2', /"1e2" is not an integer/],
+ ['!!int 0x', /"0x" is not an integer/],
+ ['!!float abc', /"abc" is not a float/],
+ ['!!seq {}', /cannot be applied to a mapping/],
+ ['%YAML 1.2\na: 1', /Directives must be followed by "---"/],
+ ['%YAML 2.0\n---\na', /Unsupported YAML version "2\.0"/],
+ ['a: |\n x\n b', /Bad indentation/],
+ ['a:\n\tb: 1', /Tabs cannot be used for indentation/],
+ ['key: "a"#c', /Comments must be separated/],
+ ['... trailing', /Unexpected content after "\.\.\."/],
+ ['--- a: 1', /A block mapping is not allowed here/],
+ ['key: - a', /A block sequence is not allowed here/],
+ ['a: b: c', /A block mapping is not allowed here/],
+ ['-\t- a', /Tabs cannot be used for indentation/],
+ ['key: "a\nb"', /Bad indentation in a quoted scalar/],
+ ['key: [a,\nb]', /Bad indentation in a flow collection/],
+];
+for (const [input, message] of invalid) {
+ assert.throws(() => parse(input), {
+ code: 'ERR_INVALID_YAML',
+ name: 'SyntaxError',
+ message,
+ }, input);
+}
+
+// Line endings, leading spaces and the special floats.
+assert.deepStrictEqual(parse('a: 1\r\nb: |\r\n x\r\n\r\n y\rc: 3\r'),
+ { a: 1, b: 'x\n\ny\n', c: 3 });
+assert.deepStrictEqual(parse(' a: 1\n b: 2'), { a: 1, b: 2 });
+assert.ok(Number.isNaN(parse('.nan')));
+assert.deepStrictEqual(parse('[.inf, +.INF, -.Inf, -0, +3, 1e3, 1E+3, 1.5e-1]'),
+ [Infinity, Infinity, -Infinity, 0, 3, 1000, 1000, 0.15]);
+// Things that look like numbers but are strings in the core schema.
+assert.deepStrictEqual(parse('[0xZZ, 0o9, 1e, 1e+, 1.2.3, +, ., ~x, 0x, 1_000, .e1]'),
+ ['0xZZ', '0o9', '1e', '1e+', '1.2.3', '+', '.', '~x', '0x', '1_000', '.e1']);
+
+// Every escape sequence of double-quoted scalars.
+assert.strictEqual(
+ parse('"\\0\\a\\b\\t\\\t\\n\\v\\f\\r\\e\\ \\"\\/\\\\\\N\\_\\L\\P\\x41\\u00e9\\U0001F600"'),
+ '\0\x07\b\t\t\n\v\f\r\x1B "/\\\x85\xA0\u2028\u2029A\u00e9\u{1F600}');
+
+// Tags.
+assert.deepStrictEqual(parse('%TAG !e! tag:example.com,2000:\n--- [!e!a%41 x, ! y, !!null ~, !!bool True]'),
+ ['x', 'y', null, true]);
+{
+ const value = parse('&a\n[1, 2]\n');
+ assert.deepStrictEqual(value, [1, 2]);
+ assert.deepStrictEqual(parse('- &a\n [1]\n- *a')[1], [1]);
+}
+assert.deepStrictEqual(parse('x: 1\n[a, b]: 2\n{c: 3}: 4'),
+ { 'x': 1, '["a","b"]': 2, '{"c":3}': 4 });
+assert.deepStrictEqual(parse('[ ? a, ? b : c, ? : d ]'), [{ a: null }, { b: 'c' }, { null: 'd' }]);
+assert.deepStrictEqual(parse('? a\n? b\n:\n? \n'), { a: null, b: null, null: null });
+assert.strictEqual(parse('|+\n\n\n'), '\n\n');
+assert.strictEqual(parse('- >+\n\n\n')[0], '\n\n');
+assert.strictEqual(parse('|-\n\n'), '');
+assert.strictEqual(parse('a: |\n text\n ').a, 'text\n');
+assert.strictEqual(parse('a: |+\n text\n ').a, 'text\n\n');
+assert.strictEqual(parse('a: |+\n\n').a, '\n');
+assert.deepStrictEqual(parse('a: |+\n\nb: 1'), { a: '\n', b: 1 });
+assert.deepStrictEqual(parse('{:}'), { null: null });
+assert.deepStrictEqual(parse('[ ? ]'), [{ null: null }]);
+// Properties at the end of the input are an empty scalar.
+assert.strictEqual(parse('!!str'), '');
+assert.strictEqual(parse('&a #c'), null);
+assert.deepStrictEqual(parse('a: !!str'), { a: '' });
+{
+ // An alias may refer to the collection that holds it, with the anchor on
+ // the same line or on the line before.
+ const value = parse('...\n--- &a [*a, {b: *a}]');
+ assert.strictEqual(value[0], value);
+ assert.strictEqual(value[1].b, value);
+ const seq = parse('&a\n[*a]');
+ assert.strictEqual(seq[0], seq);
+ const map = parse('&a\n{k: *a}');
+ assert.strictEqual(map.k, map);
+ const block = parse('&a\n- *a\n- &b\n k: *b');
+ assert.strictEqual(block[0], block);
+ assert.strictEqual(block[1].k, block[1]);
+ const nested = parse('key: &a\n [*a]\nother: !!seq &b\n - *b');
+ assert.strictEqual(nested.key[0], nested.key);
+ assert.strictEqual(nested.other[0], nested.other);
+}
+
+// Raw control characters are rejected, their escaped forms are not.
+assert.strictEqual(parse('"\\0\\a\\x7f\\x85\\x9f"'), '\0\x07\x7f\x85\x9f');
+for (const c of ['\0', '\x07', '\x0b', '\x1f', '\x7f', '\x80', '\x9f', '\ufffe']) {
+ const expected = { code: 'ERR_INVALID_YAML', message: /is not allowed in YAML/ };
+ assert.throws(() => parse(`"a${c}b"`), expected);
+ assert.throws(() => parse(`'a${c}b'`), expected);
+ assert.throws(() => parse(`a${c}b`), expected);
+ assert.throws(() => parse(`|\n a${c}b`), expected);
+ assert.throws(() => parse(`# ${c}`), expected);
+}
+assert.strictEqual(parse('"\u0085\u00a0\u2028\ud83d\ude00"'), '\u0085\u00a0\u2028\ud83d\ude00');
+assert.strictEqual(parse('"😀"'), '😀');
+for (const lone of ['\ud83d', '\ude00', '\ude00\ud83d', '\ud83d\ud83d']) {
+ assert.throws(() => parse(`"${lone}"`), { code: 'ERR_INVALID_YAML', message: /U\+d(83d|e00) is not allowed/ });
+}
+
+// An escaped line break joins the lines; empty lines in between are kept.
+assert.strictEqual(parse('"a\\\n b"'), 'ab');
+assert.strictEqual(parse('"a\\\n\n\n b"'), 'a\n\nb');
+assert.strictEqual(parse('key: "a \\\n \\ b"').key, 'a b');
+assert.throws(() => parse('key: "a\\\nb"'), { code: 'ERR_INVALID_YAML', message: /Bad indentation in a quoted scalar/ });
+assert.throws(() => parse('"a\\\n---\nb"'), { code: 'ERR_INVALID_YAML', message: /Unexpected document marker inside a quoted scalar/ });
+assert.throws(() => parse('"a\\\n'), { code: 'ERR_INVALID_YAML', message: /Unterminated quoted scalar/ });
+
+// A collection key is bounded, so aliases cannot blow it up.
+{
+ let yaml = 'a0: &a0 [x, x, x, x, x, x, x, x, x, x]\n';
+ for (let i = 1; i < 12; i++) {
+ const prev = `*a${i - 1}`;
+ yaml += `a${i}: &a${i} [${Array(10).fill(prev).join(', ')}]\n`;
+ }
+ assert.throws(() => parse(`${yaml}? *a11\n: 1\n`), {
+ code: 'ERR_INVALID_YAML',
+ message: /A collection key must not exceed 1024 characters \(line 13, column 1\)/,
+ });
+ assert.strictEqual(parse(yaml).a11.length, 10);
+}
+assert.deepStrictEqual(parse('- ? a'), [{ a: null }]);
+
+const moreInvalid = [
+ ['key: "a\n b', /Unterminated double-quoted scalar \(line 1, column 6\)/],
+ ["'open", /Unterminated single-quoted scalar/],
+ ['"\\x4"', /Invalid hexadecimal escape sequence/],
+ ['"\\U00110000"', /Invalid code point in escape sequence/],
+ ['"\\UD800DC00"', /Invalid code point in escape sequence/],
+ ['"\\', /Unknown escape sequence/],
+ ['"a\\\n', /Unterminated quoted scalar/],
+ ['|--\n a', /Repeated chomping indicator/],
+ ['|22\n a', /Repeated indentation indicator/],
+ ['|0\n a', /Indentation indicator must be between 1 and 9/],
+ ['| x\n a', /Unexpected characters after block scalar header/],
+ ['|#c\n a', /Comments must be separated/],
+ ['& a', /Anchor name must not be empty/],
+ ['&a &b c', /A node can have only one anchor/],
+ ['!!str !!int 1', /A node can have only one tag/],
+ ['&a\n&b c', /A node can have only one anchor/],
+ ['!!str\n!!int 1', /A node can have only one tag/],
+ ['&a *b', /An alias cannot have properties/],
+ ['- &b b\n- &a\n *b', /An alias cannot have properties/],
+ ['[&a *b]', /An alias cannot have properties/],
+ ['&a *b: 1', /An alias cannot have properties/],
+ ['!e!x a', /Undeclared tag handle "!e!"/],
+ ['!!str%4 a', /Invalid escape in tag/],
+ ['! a', /Verbatim tag must not be empty/],
+ ['!a', /Unexpected characters after tag/],
+ ['!!str!x a', /Invalid character in tag/],
+ ['!! a', /Tag suffix must not be empty/],
+ ['!!bool yes', /"yes" is not a boolean/],
+ ['!!null x', /"x" is not null/],
+ ['!!map 1', /cannot be applied to a scalar/],
+ ['!!str [a]', /cannot be applied to a sequence/],
+ ['!!int {a: 1}', /cannot be applied to a mapping/],
+ ['[ | ]', /Block scalars are not allowed in flow collections/],
+ ['[ a, ]b', /Unexpected content after the document root/],
+ ['[ "a" b ]', /Expected "," or "\]" in flow sequence/],
+ ['[ , ]', /Unexpected character in flow sequence/],
+ ['{ , }', /Unexpected character in flow mapping/],
+ ['{ *a:1 }', /Unknown anchor/],
+ ['- &a x\n- { *a :b }', /Expected whitespace after ":" in flow mapping/],
+ ['&a ? b', /A block mapping is not allowed here/],
+ ['{ a: "1" b }', /Expected "," or "}" in flow mapping/],
+ ['{ a: 1\n', /Unterminated flow collection/],
+ ['[ a,\n--- ]', /Unexpected document marker inside a flow collection/],
+ ['"a\n---\n"', /Unexpected document marker inside a quoted scalar/],
+ ['? a\n- b', /Expected a mapping key/],
+ ['a: 1\n- b', /Unexpected sequence entry in a mapping/],
+ ['*a: 1', /Unknown anchor "a:"/],
+ ['- &a a\n- *a : 1\n *a x: 2', /Expected ":" after mapping key/],
+ ['a: 1\n"b" c: 2', /Expected ":" after mapping key/],
+ ['a: 1\n@b: 2', /Expected a mapping key/],
+ ['a: 1\n&x\nb: 2', /Expected a mapping key/],
+ ['%YAML 1.2#c\n---\na', /Invalid YAML version "1\.2#c"/],
+ ['%YAML 1.2\n%YAML 1.2\n---\na', /Repeated %YAML directive/],
+ ['%YAML\n---\na', /The %YAML directive takes one argument/],
+ ['%YAML x.2\n---\na', /Invalid YAML version "x\.2"/],
+ ['%YAML 1.x\n---\na', /Invalid YAML version "1\.x"/],
+ ['%TAG !e!\n---\na', /The %TAG directive takes two arguments/],
+ ['%TAG e! tag:x\n---\na', /Invalid tag handle "e!"/],
+ ['%TAG !e tag:x\n---\na', /Invalid tag handle "!e"/],
+ ['%TAG !e! tag:x\n%TAG !e! tag:y\n---\na', /Repeated tag handle "!e!"/],
+ ['%YAML 1.2', /Directives must be followed by "---"/],
+ ['%YAML 1.2\n...', /Directives must be followed by "---"/],
+ ['-\t- a', /Tabs cannot be used for indentation/],
+ ['- \t? a', /Tabs cannot be used for indentation/],
+ ['- \ta: 1', /Tabs cannot be used for indentation/],
+ ['- &a x\n-\t*a : v', /Tabs cannot be used for indentation/],
+ ['- &a x\n- k: *a : v', /A block mapping is not allowed here/],
+ ['a: : b', /A block mapping is not allowed here/],
+ ['-\t: b', /Tabs cannot be used for indentation/],
+ ['x: 1\ry: "a\r b', /Unterminated double-quoted scalar \(line 2, column 4\)/],
+ ['a: 1\n| x', /Expected a mapping key/],
+ ['!!int 0x1g', /"0x1g" is not an integer/],
+ ['!!int 0o8', /"0o8" is not an integer/],
+ ['!!int +', /"\+" is not an integer/],
+ ['[ a: }', /Expected "," or "\]" in flow sequence/],
+ ['{ a: ] }', /Expected "," or "}" in flow mapping/],
+ ['"a":b', /Unexpected content after the document root/],
+ ['[a]:b', /Unexpected content after the document root/],
+ ['a: 1\n"b":2', /Expected ":" after mapping key/],
+ ['a: 1\n&b', /Expected a mapping key/],
+ ['a:\n b\n\tc: 1', /Tabs cannot be used for indentation|Bad indentation/],
+];
+for (const [input, message] of moreInvalid) {
+ assert.throws(() => parse(input), {
+ code: 'ERR_INVALID_YAML',
+ name: 'SyntaxError',
+ message,
+ }, input);
+}
+
+// Deep nesting is bounded.
+assert.throws(() => parse('['.repeat(1001) + ']'.repeat(1001)),
+ { code: 'ERR_INVALID_YAML', message: /Nesting deeper than 1000 levels/ });
+assert.ok(Array.isArray(parse('['.repeat(1000) + ']'.repeat(1000))));