Restructure YAML package suitable for pub lish After this I can use tools/publish_pkg.py upload script to fix dartbug.com/4126 Review URL: https://codereview.chromium.org//11622011 git-svn-id: https://dart.googlecode.com/svn/branches/bleeding_edge/dart/pkg/yaml@17685 260f80e4-7a28-3924-810f-c04153c831b5
diff --git a/pkgs/yaml/README.md b/pkgs/yaml/README.md new file mode 100644 index 0000000..ad7678f --- /dev/null +++ b/pkgs/yaml/README.md
@@ -0,0 +1,24 @@ +A parser for [YAML](http://www.yaml.org/). + +Use `loadYaml` to load a single document, or `loadYamlStream` to load a +stream of documents. For example: + + import 'package:yaml/yaml.dart'; + main() { + var doc = loadYaml("YAML: YAML Ain't Markup Language"); + print(doc['YAML']); + } + +This library currently doesn't support dumping to YAML. You should use +`stringify` from `dart:json` instead: + + import 'dart:json' as json; + import 'package:yaml/yaml.dart'; + main() { + var doc = loadYaml("YAML: YAML Ain't Markup Language"); + print(json.stringify(doc)); + } + +The source code for this package is at <http://code.google.com/p/dart>. +Please file issues at <http://dartbug.com>. Other questions or comments can be +directed to the Dart mailing list at <mailto:misc@dartlang.org>.
diff --git a/pkgs/yaml/lib/composer.dart b/pkgs/yaml/lib/composer.dart new file mode 100644 index 0000000..e9c1879 --- /dev/null +++ b/pkgs/yaml/lib/composer.dart
@@ -0,0 +1,173 @@ +// Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +part of yaml; + +/// Takes a parsed YAML document (what the spec calls the "serialization tree") +/// and resolves aliases, resolves tags, and parses scalars to produce the +/// "representation graph". +class _Composer extends _Visitor { + /// The root node of the serialization tree. + _Node root; + + /// Map from anchor names to the most recent representation graph node with + /// that anchor. + Map<String, _Node> anchors; + + /// The next id to use for the represenation graph's anchors. The spec doesn't + /// use anchors in the representation graph, but we do so that the constructor + /// can ensure that the same node in the representation graph produces the + /// same native object. + int idCounter; + + _Composer(this.root) : this.anchors = <String, _Node>{}, this.idCounter = 0; + + /// Runs the Composer to produce a representation graph. + _Node compose() => root.visit(this); + + /// Returns the anchor to which an alias node refers. + _Node visitAlias(_AliasNode alias) { + if (!anchors.containsKey(alias.anchor)) { + throw new YamlException("no anchor for alias ${alias.anchor}"); + } + return anchors[alias.anchor]; + } + + /// Parses a scalar node according to its tag, or auto-detects the type if no + /// tag exists. Currently this only supports the YAML core type schema. + _Node visitScalar(_ScalarNode scalar) { + if (scalar.tag.name == "!") { + return setAnchor(scalar, parseString(scalar.content)); + } else if (scalar.tag.name == "?") { + for (var fn in [parseNull, parseBool, parseInt, parseFloat]) { + var result = fn(scalar.content); + if (result != null) return result; + } + return setAnchor(scalar, parseString(scalar.content)); + } + + // TODO(nweiz): support the full YAML type repository + var tagParsers = { + 'null': parseNull, 'bool': parseBool, 'int': parseInt, + 'float': parseFloat, 'str': parseString + }; + + for (var key in tagParsers.keys) { + if (scalar.tag.name != _Tag.yaml(key)) continue; + var result = tagParsers[key](scalar.content); + if (result != null) return setAnchor(scalar, result); + throw new YamlException('invalid literal for $key: "${scalar.content}"'); + } + + throw new YamlException('undefined tag: "${scalar.tag.name}"'); + } + + /// Assigns a tag to the sequence and recursively composes its contents. + _Node visitSequence(_SequenceNode seq) { + var tagName = seq.tag.name; + if (tagName != "!" && tagName != "?" && tagName != _Tag.yaml("seq")) { + throw new YamlException("invalid tag for sequence: ${tagName}"); + } + + var result = setAnchor(seq, new _SequenceNode(_Tag.yaml("seq"), null)); + result.content = super.visitSequence(seq); + return result; + } + + /// Assigns a tag to the mapping and recursively composes its contents. + _Node visitMapping(_MappingNode map) { + var tagName = map.tag.name; + if (tagName != "!" && tagName != "?" && tagName != _Tag.yaml("map")) { + throw new YamlException("invalid tag for mapping: ${tagName}"); + } + + var result = setAnchor(map, new _MappingNode(_Tag.yaml("map"), null)); + result.content = super.visitMapping(map); + return result; + } + + /// If the serialization tree node [anchored] has an anchor, records that + /// that anchor is pointing to the representation graph node [result]. + _Node setAnchor(_Node anchored, _Node result) { + if (anchored.anchor == null) return result; + result.anchor = '${idCounter++}'; + anchors[anchored.anchor] = result; + return result; + } + + /// Parses a null scalar. + _ScalarNode parseNull(String content) { + if (!new RegExp("^(null|Null|NULL|~|)\$").hasMatch(content)) return null; + return new _ScalarNode(_Tag.yaml("null"), value: null); + } + + /// Parses a boolean scalar. + _ScalarNode parseBool(String content) { + var match = new RegExp("^(?:(true|True|TRUE)|(false|False|FALSE))\$"). + firstMatch(content); + if (match == null) return null; + return new _ScalarNode(_Tag.yaml("bool"), value: match.group(1) != null); + } + + /// Parses an integer scalar. + _ScalarNode parseInt(String content) { + var match = new RegExp("^[-+]?[0-9]+\$").firstMatch(content); + if (match != null) { + return new _ScalarNode(_Tag.yaml("int"), + value: int.parse(match.group(0))); + } + + match = new RegExp("^0o([0-7]+)\$").firstMatch(content); + if (match != null) { + // TODO(nweiz): clean this up when Dart can parse an octal string + var n = 0; + for (var c in match.group(1).charCodes) { + n *= 8; + n += c - 48; + } + return new _ScalarNode(_Tag.yaml("int"), value: n); + } + + match = new RegExp("^0x[0-9a-fA-F]+\$").firstMatch(content); + if (match != null) { + return new _ScalarNode(_Tag.yaml("int"), + value: int.parse(match.group(0))); + } + + return null; + } + + /// Parses a floating-point scalar. + _ScalarNode parseFloat(String content) { + var match = new RegExp( + "^[-+]?(\.[0-9]+|[0-9]+(\.[0-9]*)?)([eE][-+]?[0-9]+)?\$"). + firstMatch(content); + if (match != null) { + // YAML allows floats of the form "0.", but Dart does not. Fix up those + // floats by removing the trailing dot. + var matchStr = match.group(0).replaceAll(new RegExp(r"\.$"), ""); + return new _ScalarNode(_Tag.yaml("float"), + value: double.parse(matchStr)); + } + + match = new RegExp("^([+-]?)\.(inf|Inf|INF)\$").firstMatch(content); + if (match != null) { + var infinityStr = match.group(1) == "-" ? "-Infinity" : "Infinity"; + return new _ScalarNode(_Tag.yaml("float"), + value: double.parse(infinityStr)); + } + + match = new RegExp("^\.(nan|NaN|NAN)\$").firstMatch(content); + if (match != null) { + return new _ScalarNode(_Tag.yaml("float"), + value: double.parse("NaN")); + } + + return null; + } + + /// Parses a string scalar. + _ScalarNode parseString(String content) => + new _ScalarNode(_Tag.yaml("str"), value: content); +}
diff --git a/pkgs/yaml/lib/constructor.dart b/pkgs/yaml/lib/constructor.dart new file mode 100644 index 0000000..73a62a8 --- /dev/null +++ b/pkgs/yaml/lib/constructor.dart
@@ -0,0 +1,56 @@ +// Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +part of yaml; + +/// Takes a parsed and composed YAML document (what the spec calls the +/// "representation graph") and creates native Dart objects that represent that +/// document. +class _Constructor extends _Visitor { + /// The root node of the representation graph. + _Node root; + + /// Map from anchor names to the most recent Dart node with that anchor. + Map<String, dynamic> anchors; + + _Constructor(this.root) : this.anchors = {}; + + /// Runs the Constructor to produce a Dart object. + construct() => root.visit(this); + + /// Returns the value of a scalar. + visitScalar(_ScalarNode scalar) => scalar.value; + + /// Converts a sequence into a List of Dart objects. + visitSequence(_SequenceNode seq) { + var anchor = getAnchor(seq); + if (anchor != null) return anchor; + var dartSeq = setAnchor(seq, []); + dartSeq.addAll(super.visitSequence(seq)); + return dartSeq; + } + + /// Converts a mapping into a Map of Dart objects. + visitMapping(_MappingNode map) { + var anchor = getAnchor(map); + if (anchor != null) return anchor; + var dartMap = setAnchor(map, new YamlMap()); + super.visitMapping(map).forEach((k, v) { dartMap[k] = v; }); + return dartMap; + } + + /// Returns the Dart object that already represents [anchored], if such a + /// thing exists. + getAnchor(_Node anchored) { + if (anchored.anchor == null) return null; + if (anchors.containsKey(anchored.anchor)) return anchors[anchored.anchor]; + } + + /// Records that [value] is the Dart object representing [anchored]. + setAnchor(_Node anchored, value) { + if (anchored.anchor == null) return value; + anchors[anchored.anchor] = value; + return value; + } +}
diff --git a/pkgs/yaml/lib/deep_equals.dart b/pkgs/yaml/lib/deep_equals.dart new file mode 100644 index 0000000..bce4aad --- /dev/null +++ b/pkgs/yaml/lib/deep_equals.dart
@@ -0,0 +1,72 @@ +// Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +library deep_equals; + +/// Returns whether two objects are structurally equivalent. This considers NaN +/// values to be equivalent. It also handles self-referential structures. +bool deepEquals(obj1, obj2, [List parents1, List parents2]) { + if (identical(obj1, obj2)) return true; + if (parents1 == null) { + parents1 = []; + parents2 = []; + } + + // parents1 and parents2 are guaranteed to be the same size. + for (var i = 0; i < parents1.length; i++) { + var loop1 = identical(obj1, parents1[i]); + var loop2 = identical(obj2, parents2[i]); + // If both structures loop in the same place, they're equal at that point in + // the structure. If one loops and the other doesn't, they're not equal. + if (loop1 && loop2) return true; + if (loop1 || loop2) return false; + } + + parents1.add(obj1); + parents2.add(obj2); + try { + if (obj1 is List && obj2 is List) { + return _listEquals(obj1, obj2, parents1, parents2); + } else if (obj1 is Map && obj2 is Map) { + return _mapEquals(obj1, obj2, parents1, parents2); + } else if (obj1 is double && obj2 is double) { + return _doubleEquals(obj1, obj2); + } else { + return obj1 == obj2; + } + } finally { + parents1.removeLast(); + parents2.removeLast(); + } +} + +/// Returns whether [list1] and [list2] are structurally equal. +bool _listEquals(List list1, List list2, List parents1, List parents2) { + if (list1.length != list2.length) return false; + + for (var i = 0; i < list1.length; i++) { + if (!deepEquals(list1[i], list2[i], parents1, parents2)) return false; + } + + return true; +} + +/// Returns whether [map1] and [map2] are structurally equal. +bool _mapEquals(Map map1, Map map2, List parents1, List parents2) { + if (map1.length != map2.length) return false; + + for (var key in map1.keys) { + if (!map2.containsKey(key)) return false; + if (!deepEquals(map1[key], map2[key], parents1, parents2)) return false; + } + + return true; +} + +/// Returns whether two doubles are equivalent. This differs from `d1 == d2` in +/// that it considers NaN to be equal to itself. +bool _doubleEquals(double d1, double d2) { + if (d1.isNaN && d2.isNaN) return true; + return d1 == d2; +}
diff --git a/pkgs/yaml/lib/model.dart b/pkgs/yaml/lib/model.dart new file mode 100644 index 0000000..8c111af --- /dev/null +++ b/pkgs/yaml/lib/model.dart
@@ -0,0 +1,234 @@ +// Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +part of yaml; + +// This file contains the node classes for the internal representations of YAML +// documents. These nodes are used for both the serialization tree and the +// representation graph. + +/// A tag that indicates the type of a YAML node. +class _Tag { + // TODO(nweiz): it would better match the semantics of the spec if there were + // a singleton instance of this class for each tag. + + static const SCALAR_KIND = 0; + static const SEQUENCE_KIND = 1; + static const MAPPING_KIND = 2; + + static const String YAML_URI_PREFIX = 'tag:yaml.org,2002:'; + + /// The name of the tag, either a URI or a local tag beginning with "!". + final String name; + + /// The kind of the tag: SCALAR_KIND, SEQUENCE_KIND, or MAPPING_KIND. + final int kind; + + _Tag(this.name, this.kind); + + _Tag.scalar(String name) : this(name, SCALAR_KIND); + _Tag.sequence(String name) : this(name, SEQUENCE_KIND); + _Tag.mapping(String name) : this(name, MAPPING_KIND); + + /// Returns the standard YAML tag URI for [type]. + static String yaml(String type) => "tag:yaml.org,2002:$type"; + + /// Two tags are equal if their URIs are equal. + operator ==(other) { + if (other is! _Tag) return false; + return name == other.name; + } + + String toString() { + if (name.startsWith(YAML_URI_PREFIX)) { + return '!!${name.substring(YAML_URI_PREFIX.length)}'; + } else { + return '!<$name>'; + } + } + + int get hashCode => name.hashCode; +} + +/// The abstract class for YAML nodes. +abstract class _Node { + /// Every YAML node has a tag that describes its type. + _Tag tag; + + /// Any YAML node can have an anchor associated with it. + String anchor; + + _Node(this.tag, [this.anchor]); + + bool operator ==(other) { + if (other is! _Node) return false; + return tag == other.tag; + } + + int get hashCode => _hashCode([tag, anchor]); + + visit(_Visitor v); +} + +/// A sequence node represents an ordered list of nodes. +class _SequenceNode extends _Node { + /// The nodes in the sequence. + List<_Node> content; + + _SequenceNode(String tagName, this.content) + : super(new _Tag.sequence(tagName)); + + /// Two sequences are equal if their tags and contents are equal. + bool operator ==(other) { + // Should be super != other; bug 2554 + if (!(super == other) || other is! _SequenceNode) return false; + if (content.length != other.content.length) return false; + for (var i = 0; i < content.length; i++) { + if (content[i] != other.content[i]) return false; + } + return true; + } + + String toString() => + '$tag [${Strings.join(content.mappedBy((e) => '$e'), ', ')}]'; + + int get hashCode => super.hashCode ^ _hashCode(content); + + visit(_Visitor v) => v.visitSequence(this); +} + +/// An alias node is a reference to an anchor. +class _AliasNode extends _Node { + _AliasNode(String anchor) : super(new _Tag.scalar(_Tag.yaml("str")), anchor); + + visit(_Visitor v) => v.visitAlias(this); +} + +/// A scalar node represents all YAML nodes that have a single value. +class _ScalarNode extends _Node { + /// The string value of the scalar node, if it was created by the parser. + final String _content; + + /// The Dart value of the scalar node, if it was created by the composer. + final value; + + /// Creates a new Scalar node. + /// + /// Exactly one of [content] and [value] should be specified. Content should + /// be specified for a newly-parsed scalar that hasn't yet been composed. + /// Value should be specified for a composed scalar, although `null` is a + /// valid value. + _ScalarNode(String tagName, {String content, this.value}) + : _content = content, + super(new _Tag.scalar(tagName)); + + /// Two scalars are equal if their string representations are equal. + bool operator ==(other) { + // Should be super != other; bug 2554 + if (!(super == other) || other is! _ScalarNode) return false; + return content == other.content; + } + + /// Returns the string representation of the scalar. After composition, this + /// is equal to the canonical serialization of the value of the scalar. + String get content => _content != null ? _content : canonicalContent; + + /// Returns the canonical serialization of the value of the scalar. If the + /// value isn't given, the result of this will be "null". + String get canonicalContent { + if (value == null || value is bool || value is int) return '$value'; + + if (value is num) { + // 20 is the maximum value for this argument, which we use since YAML + // doesn't specify a maximum. + return value.toStringAsExponential(20). + replaceFirst(new RegExp("0+e"), "e"); + } + + if (value is String) { + // TODO(nweiz): This could be faster if we used a RegExp to check for + // special characters and short-circuited if they didn't exist. + + var escapedValue = value.charCodes.mappedBy((c) { + switch (c) { + case _Parser.TAB: return "\\t"; + case _Parser.LF: return "\\n"; + case _Parser.CR: return "\\r"; + case _Parser.DOUBLE_QUOTE: return '\\"'; + case _Parser.NULL: return "\\0"; + case _Parser.BELL: return "\\a"; + case _Parser.BACKSPACE: return "\\b"; + case _Parser.VERTICAL_TAB: return "\\v"; + case _Parser.FORM_FEED: return "\\f"; + case _Parser.ESCAPE: return "\\e"; + case _Parser.BACKSLASH: return "\\\\"; + case _Parser.NEL: return "\\N"; + case _Parser.NBSP: return "\\_"; + case _Parser.LINE_SEPARATOR: return "\\L"; + case _Parser.PARAGRAPH_SEPARATOR: return "\\P"; + default: + if (c < 0x20 || (c >= 0x7f && c < 0x100)) { + return "\\x${zeroPad(c.toRadixString(16).toUpperCase(), 2)}"; + } else if (c >= 0x100 && c < 0x10000) { + return "\\u${zeroPad(c.toRadixString(16).toUpperCase(), 4)}"; + } else if (c >= 0x10000) { + return "\\u${zeroPad(c.toRadixString(16).toUpperCase(), 8)}"; + } else { + return new String.fromCharCodes([c]); + } + } + }); + return '"${Strings.join(escapedValue, '')}"'; + } + + throw new YamlException("unknown scalar value: $value"); + } + + String toString() => '$tag "$content"'; + + /// Left-pads [str] with zeros so that it's at least [length] characters + /// long. + String zeroPad(String str, int length) { + assert(length >= str.length); + var prefix = []; + prefix.insertRange(0, length - str.length, '0'); + return '${Strings.join(prefix, '')}$str'; + } + + int get hashCode => super.hashCode ^ content.hashCode; + + visit(_Visitor v) => v.visitScalar(this); +} + +/// A mapping node represents an unordered map of nodes to nodes. +class _MappingNode extends _Node { + /// The node map. + Map<_Node, _Node> content; + + _MappingNode(String tagName, this.content) + : super(new _Tag.mapping(tagName)); + + /// Two mappings are equal if their tags and contents are equal. + bool operator ==(other) { + // Should be super != other; bug 2554 + if (!(super == other) || other is! _MappingNode) return false; + if (content.length != other.content.length) return false; + for (var key in content.keys) { + if (!other.content.containsKey(key)) return false; + if (content[key] != other.content[key]) return false; + } + return true; + } + + String toString() { + var strContent = content.keys + .mappedBy((k) => '${k}: ${content[k]}') + .join(', '); + return '$tag {$strContent}'; + } + + int get hashCode => super.hashCode ^ _hashCode(content); + + visit(_Visitor v) => v.visitMapping(this); +}
diff --git a/pkgs/yaml/lib/parser.dart b/pkgs/yaml/lib/parser.dart new file mode 100644 index 0000000..f2f255e --- /dev/null +++ b/pkgs/yaml/lib/parser.dart
@@ -0,0 +1,1939 @@ +// Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +part of yaml; + +/// Translates a string of characters into a YAML serialization tree. +/// +/// This parser is designed to closely follow the spec. All productions in the +/// spec are numbered, and the corresponding methods in the parser have the same +/// numbers. This is certainly not the most efficient way of parsing YAML, but +/// it is the easiest to write and read in the context of the spec. +/// +/// Methods corresponding to productions are also named as in the spec, +/// translating the name of the method (although not the annotation characters) +/// into camel-case for dart style.. For example, the spec has a production +/// named `nb-ns-plain-in-line`, and the method implementing it is named +/// `nb_ns_plainInLine`. The exception to that rule is methods that just +/// recognize character classes; these are named `is*`. +class _Parser { + static const TAB = 0x9; + static const LF = 0xA; + static const CR = 0xD; + static const SP = 0x20; + static const TILDE = 0x7E; + static const NEL = 0x85; + static const PLUS = 0x2B; + static const HYPHEN = 0x2D; + static const QUESTION_MARK = 0x3F; + static const COLON = 0x3A; + static const COMMA = 0x2C; + static const LEFT_BRACKET = 0x5B; + static const RIGHT_BRACKET = 0x5D; + static const LEFT_BRACE = 0x7B; + static const RIGHT_BRACE = 0x7D; + static const HASH = 0x23; + static const AMPERSAND = 0x26; + static const ASTERISK = 0x2A; + static const EXCLAMATION = 0x21; + static const VERTICAL_BAR = 0x7C; + static const GREATER_THAN = 0x3E; + static const SINGLE_QUOTE = 0x27; + static const DOUBLE_QUOTE = 0x22; + static const PERCENT = 0x25; + static const AT = 0x40; + static const GRAVE_ACCENT = 0x60; + + static const NULL = 0x0; + static const BELL = 0x7; + static const BACKSPACE = 0x8; + static const VERTICAL_TAB = 0xB; + static const FORM_FEED = 0xC; + static const ESCAPE = 0x1B; + static const SLASH = 0x2F; + static const BACKSLASH = 0x5C; + static const UNDERSCORE = 0x5F; + static const NBSP = 0xA0; + static const LINE_SEPARATOR = 0x2028; + static const PARAGRAPH_SEPARATOR = 0x2029; + + static const NUMBER_0 = 0x30; + static const NUMBER_9 = 0x39; + + static const LETTER_A = 0x61; + static const LETTER_B = 0x62; + static const LETTER_E = 0x65; + static const LETTER_F = 0x66; + static const LETTER_N = 0x6E; + static const LETTER_R = 0x72; + static const LETTER_T = 0x74; + static const LETTER_U = 0x75; + static const LETTER_V = 0x76; + static const LETTER_X = 0x78; + + static const LETTER_CAP_A = 0x41; + static const LETTER_CAP_F = 0x46; + static const LETTER_CAP_L = 0x4C; + static const LETTER_CAP_N = 0x4E; + static const LETTER_CAP_P = 0x50; + static const LETTER_CAP_U = 0x55; + static const LETTER_CAP_X = 0x58; + + static const C_SEQUENCE_ENTRY = 4; + static const C_MAPPING_KEY = 5; + static const C_MAPPING_VALUE = 6; + static const C_COLLECT_ENTRY = 7; + static const C_SEQUENCE_START = 8; + static const C_SEQUENCE_END = 9; + static const C_MAPPING_START = 10; + static const C_MAPPING_END = 11; + static const C_COMMENT = 12; + static const C_ANCHOR = 13; + static const C_ALIAS = 14; + static const C_TAG = 15; + static const C_LITERAL = 16; + static const C_FOLDED = 17; + static const C_SINGLE_QUOTE = 18; + static const C_DOUBLE_QUOTE = 19; + static const C_DIRECTIVE = 20; + static const C_RESERVED = 21; + + static const BLOCK_OUT = 0; + static const BLOCK_IN = 1; + static const FLOW_OUT = 2; + static const FLOW_IN = 3; + static const BLOCK_KEY = 4; + static const FLOW_KEY = 5; + + static const CHOMPING_STRIP = 0; + static const CHOMPING_KEEP = 1; + static const CHOMPING_CLIP = 2; + + /// The source string being parsed. + final String s; + + /// The current position in the source string. + int pos = 0; + + /// The length of the string being parsed. + final int len; + + /// The current (0-based) line in the source string. + int line = 0; + + /// The current (0-based) column in the source string. + int column = 0; + + /// Whether we're parsing a bare document (that is, one that doesn't begin + /// with `---`). Bare documents don't allow `%` immediately following + /// newlines. + bool inBareDocument = false; + + /// The line number of the farthest position that has been parsed successfully + /// before backtracking. Used for error reporting. + int farthestLine = 0; + + /// The column number of the farthest position that has been parsed + /// successfully before backtracking. Used for error reporting. + int farthestColumn = 0; + + /// The farthest position in the source string that has been parsed + /// successfully before backtracking. Used for error reporting. + int farthestPos = 0; + + /// The name of the context of the farthest position that has been parsed + /// successfully before backtracking. Used for error reporting. + String farthestContext = "document"; + + /// A stack of the names of parse contexts. Used for error reporting. + List<String> contextStack; + + /// Annotations attached to ranges of the source string that add extra + /// information to any errors that occur in the annotated range. + _RangeMap<String> errorAnnotations; + + /// The buffer containing the string currently being captured. + StringBuffer capturedString; + + /// The beginning of the current section of the captured string. + int captureStart; + + /// Whether the current string capture is being overridden. + bool capturingAs = false; + + _Parser(String s) + : this.s = s, + len = s.length, + contextStack = <String>["document"], + errorAnnotations = new _RangeMap(); + + /// Return the character at the current position, then move that position + /// forward one character. Also updates the current line and column numbers. + int next() { + if (pos == len) return -1; + var char = s.charCodeAt(pos++); + if (isBreak(char)) { + line++; + column = 0; + } else { + column++; + } + + if (farthestLine < line) { + farthestLine = line; + farthestColumn = column; + farthestContext = contextStack.last; + } else if (farthestLine == line && farthestColumn < column) { + farthestColumn = column; + farthestContext = contextStack.last; + } + farthestPos = pos; + + return char; + } + + /// Returns the character at the current position, or the character [i] + /// characters after the current position. + /// + /// Returns -1 if this would return a character after the end or before the + /// beginning of the input string. + int peek([int i = 0]) { + var peekPos = pos + i; + return (peekPos >= len || peekPos < 0) ? -1 : s.charCodeAt(peekPos); + } + + /// The truthiness operator. Returns `false` if [obj] is `null` or `false`, + /// `true` otherwise. + bool truth(obj) => obj != null && obj != false; + + /// Consumes the current character if it matches [matcher]. Returns the result + /// of [matcher]. + bool consume(bool matcher(int)) { + if (matcher(peek())) { + next(); + return true; + } + return false; + } + + /// Consumes the current character if it equals [char]. + bool consumeChar(int char) => consume((c) => c == char); + + /// Calls [consumer] until it returns a falsey value. Returns a list of all + /// truthy return values of [consumer], or null if it didn't consume anything. + /// + /// Conceptually, repeats a production one or more times. + List oneOrMore(consumer()) { + var first = consumer(); + if (!truth(first)) return null; + var out = [first]; + while (true) { + var el = consumer(); + if (!truth(el)) return out; + out.add(el); + } + return null; // Unreachable. + } + + /// Calls [consumer] until it returns a falsey value. Returns a list of all + /// truthy return values of [consumer], or the empty list if it didn't consume + /// anything. + /// + /// Conceptually, repeats a production any number of times. + List zeroOrMore(consumer()) { + var out = []; + var oldPos = pos; + while (true) { + var el = consumer(); + if (!truth(el) || oldPos == pos) return out; + oldPos = pos; + out.add(el); + } + return null; // Unreachable. + } + + /// Just calls [consumer] and returns its result. Used to make it explicit + /// that a production is intended to be optional. + zeroOrOne(consumer()) => consumer(); + + /// Calls each function in [consumers] until one returns a truthy value, then + /// returns that. + or(List<Function> consumers) { + for (var c in consumers) { + var res = c(); + if (truth(res)) return res; + } + return null; + } + + /// Calls [consumer] and returns its result, but rolls back the parser state + /// if [consumer] returns a falsey value. + transaction(consumer()) { + var oldPos = pos; + var oldLine = line; + var oldColumn = column; + var oldCaptureStart = captureStart; + String capturedSoFar = capturedString == null ? null : + capturedString.toString(); + var res = consumer(); + if (truth(res)) return res; + + pos = oldPos; + line = oldLine; + column = oldColumn; + captureStart = oldCaptureStart; + capturedString = capturedSoFar == null ? null : + new StringBuffer(capturedSoFar); + return res; + } + + /// Consumes [n] characters matching [matcher], or none if there isn't a + /// complete match. The first argument to [matcher] is the character code, the + /// second is the index (from 0 to [n] - 1). + /// + /// Returns whether or not the characters were consumed. + bool nAtOnce(int n, bool matcher(int c, int i)) => transaction(() { + for (int i = 0; i < n; i++) { + if (!consume((c) => matcher(c, i))) return false; + } + return true; + }); + + /// Consumes the exact characters in [str], or nothing. + /// + /// Returns whether or not the string was consumed. + bool rawString(String str) => + nAtOnce(str.length, (c, i) => str.charCodeAt(i) == c); + + /// Consumes and returns a string of characters matching [matcher], or null if + /// there are no such characters. + String stringOf(bool matcher(int)) => + captureString(() => oneOrMore(() => consume(matcher))); + + /// Calls [consumer] and returns the string that was consumed while doing so, + /// or null if [consumer] returned a falsey value. Automatically wraps + /// [consumer] in `transaction`. + String captureString(consumer()) { + // captureString calls may not be nested + assert(capturedString == null); + + captureStart = pos; + capturedString = new StringBuffer(); + var res = transaction(consumer); + if (!truth(res)) { + captureStart = null; + capturedString = null; + return null; + } + + flushCapture(); + var result = capturedString.toString(); + captureStart = null; + capturedString = null; + return result; + } + + captureAs(String replacement, consumer()) => + captureAndTransform(consumer, (_) => replacement); + + captureAndTransform(consumer(), String transformation(String captured)) { + if (capturedString == null) return consumer(); + if (capturingAs) return consumer(); + + flushCapture(); + capturingAs = true; + var res = consumer(); + capturingAs = false; + if (!truth(res)) return res; + + capturedString.add(transformation(s.substring(captureStart, pos))); + captureStart = pos; + return res; + } + + void flushCapture() { + capturedString.add(s.substring(captureStart, pos)); + captureStart = pos; + } + + /// Adds a tag and an anchor to [node], if they're defined. + _Node addProps(_Node node, _Pair<_Tag, String> props) { + if (props == null || node == null) return node; + if (truth(props.first)) node.tag = props.first; + if (truth(props.last)) node.anchor = props.last; + return node; + } + + /// Creates a MappingNode from [pairs]. + _MappingNode map(List<_Pair<_Node, _Node>> pairs) { + var content = new Map<_Node, _Node>(); + pairs.forEach((pair) => content[pair.first] = pair.last); + return new _MappingNode("?", content); + } + + /// Runs [fn] in a context named [name]. Used for error reporting. + context(String name, fn()) { + try { + contextStack.add(name); + return fn(); + } finally { + var popped = contextStack.removeLast(); + assert(popped == name); + } + } + + /// Adds [message] as extra information to any errors that occur between the + /// current position and the position of the cursor after running [fn]. The + /// cursor is reset after [fn] is run. + annotateError(String message, fn()) { + var start = pos; + var end; + transaction(() { + fn(); + end = pos; + return false; + }); + errorAnnotations[new _Range(start, end)] = message; + } + + /// Throws an error with additional context information. + error(String message) { + // Line and column should be one-based. + throw new SyntaxError(line + 1, column + 1, + "$message (in $farthestContext)"); + } + + /// If [result] is falsey, throws an error saying that [expected] was + /// expected. + expect(result, String expected) { + if (truth(result)) return result; + error("expected $expected"); + } + + /// Throws an error saying that the parse failed. Uses [farthestLine], + /// [farthestColumn], and [farthestContext] to provide additional information. + parseFailed() { + var message = "invalid YAML in $farthestContext"; + var extraError = errorAnnotations[farthestPos]; + if (extraError != null) message = "$message ($extraError)"; + throw new SyntaxError(farthestLine + 1, farthestColumn + 1, message); + } + + /// Returns the number of spaces after the current position. + int countIndentation() { + var i = 0; + while (peek(i) == SP) i++; + return i; + } + + /// Returns the indentation for a block scalar. + int blockScalarAdditionalIndentation(_BlockHeader header, int indent) { + if (!header.autoDetectIndent) return header.additionalIndent; + + var maxSpaces = 0; + var maxSpacesLine = 0; + var spaces = 0; + transaction(() { + do { + spaces = captureString(() => zeroOrMore(() => consumeChar(SP))).length; + if (spaces > maxSpaces) { + maxSpaces = spaces; + maxSpacesLine = line; + } + } while (b_break()); + return false; + }); + + // If the next non-empty line isn't indented further than the start of the + // block scalar, that means the scalar is going to be empty. Returning any + // value > 0 will cause the parser not to consume any text. + if (spaces <= indent) return 1; + + // It's an error for a leading empty line to be indented more than the first + // non-empty line. + if (maxSpaces > spaces) { + throw new SyntaxError(maxSpacesLine + 1, maxSpaces, + "Leading empty lines may not be indented more than the first " + "non-empty line."); + } + + return spaces - indent; + } + + /// Returns whether the current position is at the beginning of a line. + bool get atStartOfLine => column == 0; + + /// Returns whether the current position is at the end of the input. + bool get atEndOfFile => pos == len; + + /// Given an indicator character, returns the type of that indicator (or null + /// if the indicator isn't found. + int indicatorType(int char) { + switch (char) { + case HYPHEN: return C_SEQUENCE_ENTRY; + case QUESTION_MARK: return C_MAPPING_KEY; + case COLON: return C_MAPPING_VALUE; + case COMMA: return C_COLLECT_ENTRY; + case LEFT_BRACKET: return C_SEQUENCE_START; + case RIGHT_BRACKET: return C_SEQUENCE_END; + case LEFT_BRACE: return C_MAPPING_START; + case RIGHT_BRACE: return C_MAPPING_END; + case HASH: return C_COMMENT; + case AMPERSAND: return C_ANCHOR; + case ASTERISK: return C_ALIAS; + case EXCLAMATION: return C_TAG; + case VERTICAL_BAR: return C_LITERAL; + case GREATER_THAN: return C_FOLDED; + case SINGLE_QUOTE: return C_SINGLE_QUOTE; + case DOUBLE_QUOTE: return C_DOUBLE_QUOTE; + case PERCENT: return C_DIRECTIVE; + case AT: + case GRAVE_ACCENT: + return C_RESERVED; + default: return null; + } + } + + // 1 + bool isPrintable(int char) { + return char == TAB || + char == LF || + char == CR || + (char >= SP && char <= TILDE) || + char == NEL || + (char >= 0xA0 && char <= 0xD7FF) || + (char >= 0xE000 && char <= 0xFFFD) || + (char >= 0x10000 && char <= 0x10FFFF); + } + + // 2 + bool isJson(int char) => char == TAB || (char >= SP && char <= 0x10FFFF); + + // 22 + bool c_indicator(int type) => consume((c) => indicatorType(c) == type); + + // 23 + bool isFlowIndicator(int char) { + var indicator = indicatorType(char); + return indicator == C_COLLECT_ENTRY || + indicator == C_SEQUENCE_START || + indicator == C_SEQUENCE_END || + indicator == C_MAPPING_START || + indicator == C_MAPPING_END; + } + + // 26 + bool isBreak(int char) => char == LF || char == CR; + + // 27 + bool isNonBreak(int char) => isPrintable(char) && !isBreak(char); + + // 28 + bool b_break() { + if (consumeChar(CR)) { + zeroOrOne(() => consumeChar(LF)); + return true; + } + return consumeChar(LF); + } + + // 29 + bool b_asLineFeed() => captureAs("\n", () => b_break()); + + // 30 + bool b_nonContent() => captureAs("", () => b_break()); + + // 33 + bool isSpace(int char) => char == SP || char == TAB; + + // 34 + bool isNonSpace(int char) => isNonBreak(char) && !isSpace(char); + + // 35 + bool isDecDigit(int char) => char >= NUMBER_0 && char <= NUMBER_9; + + // 36 + bool isHexDigit(int char) { + return isDecDigit(char) || + (char >= LETTER_A && char <= LETTER_F) || + (char >= LETTER_CAP_A && char <= LETTER_CAP_F); + } + + // 41 + bool c_escape() => captureAs("", () => consumeChar(BACKSLASH)); + + // 42 + bool ns_escNull() => captureAs("\x00", () => consumeChar(NUMBER_0)); + + // 43 + bool ns_escBell() => captureAs("\x07", () => consumeChar(LETTER_A)); + + // 44 + bool ns_escBackspace() => captureAs("\b", () => consumeChar(LETTER_B)); + + // 45 + bool ns_escHorizontalTab() => captureAs("\t", () { + return consume((c) => c == LETTER_T || c == TAB); + }); + + // 46 + bool ns_escLineFeed() => captureAs("\n", () => consumeChar(LETTER_N)); + + // 47 + bool ns_escVerticalTab() => captureAs("\v", () => consumeChar(LETTER_V)); + + // 48 + bool ns_escFormFeed() => captureAs("\f", () => consumeChar(LETTER_F)); + + // 49 + bool ns_escCarriageReturn() => captureAs("\r", () => consumeChar(LETTER_R)); + + // 50 + bool ns_escEscape() => captureAs("\x1B", () => consumeChar(LETTER_E)); + + // 51 + bool ns_escSpace() => consumeChar(SP); + + // 52 + bool ns_escDoubleQuote() => consumeChar(DOUBLE_QUOTE); + + // 53 + bool ns_escSlash() => consumeChar(SLASH); + + // 54 + bool ns_escBackslash() => consumeChar(BACKSLASH); + + // 55 + bool ns_escNextLine() => captureAs("\x85", () => consumeChar(LETTER_CAP_N)); + + // 56 + bool ns_escNonBreakingSpace() => + captureAs("\xA0", () => consumeChar(UNDERSCORE)); + + // 57 + bool ns_escLineSeparator() => + captureAs("\u2028", () => consumeChar(LETTER_CAP_L)); + + // 58 + bool ns_escParagraphSeparator() => + captureAs("\u2029", () => consumeChar(LETTER_CAP_P)); + + // 59 + bool ns_esc8Bit() => ns_escNBit(LETTER_X, 2); + + // 60 + bool ns_esc16Bit() => ns_escNBit(LETTER_U, 4); + + // 61 + bool ns_esc32Bit() => ns_escNBit(LETTER_CAP_U, 8); + + // Helper method for 59 - 61 + bool ns_escNBit(int char, int digits) { + if (!captureAs('', () => consumeChar(char))) return false; + var captured = captureAndTransform( + () => nAtOnce(digits, (c, _) => isHexDigit(c)), + (hex) => new String.fromCharCodes([int.parse("0x$hex")])); + return expect(captured, "$digits hexidecimal digits"); + } + + // 62 + bool c_ns_escChar() => context('escape sequence', () => transaction(() { + if (!truth(c_escape())) return false; + return truth(or([ + ns_escNull, ns_escBell, ns_escBackspace, ns_escHorizontalTab, + ns_escLineFeed, ns_escVerticalTab, ns_escFormFeed, ns_escCarriageReturn, + ns_escEscape, ns_escSpace, ns_escDoubleQuote, ns_escSlash, + ns_escBackslash, ns_escNextLine, ns_escNonBreakingSpace, + ns_escLineSeparator, ns_escParagraphSeparator, ns_esc8Bit, ns_esc16Bit, + ns_esc32Bit + ])); + })); + + // 63 + bool s_indent(int indent) { + var result = nAtOnce(indent, (c, i) => c == SP); + if (peek() == TAB) { + annotateError("tab characters are not allowed as indentation in YAML", + () => zeroOrMore(() => consume(isSpace))); + } + return result; + } + + // 64 + bool s_indentLessThan(int indent) { + for (int i = 0; i < indent - 1; i++) { + if (!consumeChar(SP)) { + if (peek() == TAB) { + annotateError("tab characters are not allowed as indentation in YAML", + () { + for (; i < indent - 1; i++) { + if (!consume(isSpace)) break; + } + }); + } + break; + } + } + return true; + } + + // 65 + bool s_indentLessThanOrEqualTo(int indent) => s_indentLessThan(indent + 1); + + // 66 + bool s_separateInLine() => transaction(() { + return captureAs('', () => + truth(oneOrMore(() => consume(isSpace))) || atStartOfLine); + }); + + // 67 + bool s_linePrefix(int indent, int ctx) => captureAs("", () { + switch (ctx) { + case BLOCK_OUT: + case BLOCK_IN: + return s_blockLinePrefix(indent); + case FLOW_OUT: + case FLOW_IN: + return s_flowLinePrefix(indent); + } + }); + + // 68 + bool s_blockLinePrefix(int indent) => s_indent(indent); + + // 69 + bool s_flowLinePrefix(int indent) => captureAs('', () { + if (!truth(s_indent(indent))) return false; + zeroOrOne(s_separateInLine); + return true; + }); + + // 70 + bool l_empty(int indent, int ctx) => transaction(() { + var start = or([ + () => s_linePrefix(indent, ctx), + () => s_indentLessThan(indent) + ]); + if (!truth(start)) return false; + return b_asLineFeed(); + }); + + // 71 + bool b_asSpace() => captureAs(" ", () => consume(isBreak)); + + // 72 + bool b_l_trimmed(int indent, int ctx) => transaction(() { + if (!truth(b_nonContent())) return false; + return truth(oneOrMore(() => captureAs("\n", () => l_empty(indent, ctx)))); + }); + + // 73 + bool b_l_folded(int indent, int ctx) => + or([() => b_l_trimmed(indent, ctx), b_asSpace]); + + // 74 + bool s_flowFolded(int indent) => transaction(() { + zeroOrOne(s_separateInLine); + if (!truth(b_l_folded(indent, FLOW_IN))) return false; + return s_flowLinePrefix(indent); + }); + + // 75 + bool c_nb_commentText() { + if (!truth(c_indicator(C_COMMENT))) return false; + zeroOrMore(() => consume(isNonBreak)); + return true; + } + + // 76 + bool b_comment() => atEndOfFile || b_nonContent(); + + // 77 + bool s_b_comment() { + if (truth(s_separateInLine())) { + zeroOrOne(c_nb_commentText); + } + return b_comment(); + } + + // 78 + bool l_comment() => transaction(() { + if (!truth(s_separateInLine())) return false; + zeroOrOne(c_nb_commentText); + return b_comment(); + }); + + // 79 + bool s_l_comments() { + if (!truth(s_b_comment()) && !atStartOfLine) return false; + zeroOrMore(l_comment); + return true; + } + + // 80 + bool s_separate(int indent, int ctx) { + switch (ctx) { + case BLOCK_OUT: + case BLOCK_IN: + case FLOW_OUT: + case FLOW_IN: + return s_separateLines(indent); + case BLOCK_KEY: + case FLOW_KEY: + return s_separateInLine(); + default: throw 'invalid context "$ctx"'; + } + } + + // 81 + bool s_separateLines(int indent) { + return transaction(() => s_l_comments() && s_flowLinePrefix(indent)) || + s_separateInLine(); + } + + // 82 + bool l_directive() => false; // TODO(nweiz): implement + + // 96 + _Pair<_Tag, String> c_ns_properties(int indent, int ctx) { + var tag, anchor; + tag = c_ns_tagProperty(); + if (truth(tag)) { + anchor = transaction(() { + if (!truth(s_separate(indent, ctx))) return null; + return c_ns_anchorProperty(); + }); + return new _Pair<_Tag, String>(tag, anchor); + } + + anchor = c_ns_anchorProperty(); + if (truth(anchor)) { + tag = transaction(() { + if (!truth(s_separate(indent, ctx))) return null; + return c_ns_tagProperty(); + }); + return new _Pair<_Tag, String>(tag, anchor); + } + + return null; + } + + // 97 + _Tag c_ns_tagProperty() => null; // TODO(nweiz): implement + + // 101 + String c_ns_anchorProperty() => null; // TODO(nweiz): implement + + // 102 + bool isAnchorChar(int char) => isNonSpace(char) && !isFlowIndicator(char); + + // 103 + String ns_anchorName() => + captureString(() => oneOrMore(() => consume(isAnchorChar))); + + // 104 + _Node c_ns_aliasNode() { + if (!truth(c_indicator(C_ALIAS))) return null; + var name = expect(ns_anchorName(), 'anchor name'); + return new _AliasNode(name); + } + + // 105 + _ScalarNode e_scalar() => new _ScalarNode("?", content: ""); + + // 106 + _ScalarNode e_node() => e_scalar(); + + // 107 + bool nb_doubleChar() => or([ + c_ns_escChar, + () => consume((c) => isJson(c) && c != BACKSLASH && c != DOUBLE_QUOTE) + ]); + + // 108 + bool ns_doubleChar() => !isSpace(peek()) && truth(nb_doubleChar()); + + // 109 + _Node c_doubleQuoted(int indent, int ctx) => context('string', () { + return transaction(() { + if (!truth(c_indicator(C_DOUBLE_QUOTE))) return null; + var contents = nb_doubleText(indent, ctx); + if (!truth(c_indicator(C_DOUBLE_QUOTE))) return null; + return new _ScalarNode("!", content: contents); + }); + }); + + // 110 + String nb_doubleText(int indent, int ctx) => captureString(() { + switch (ctx) { + case FLOW_OUT: + case FLOW_IN: + nb_doubleMultiLine(indent); + break; + case BLOCK_KEY: + case FLOW_KEY: + nb_doubleOneLine(); + break; + } + return true; + }); + + // 111 + void nb_doubleOneLine() { + zeroOrMore(nb_doubleChar); + } + + // 112 + bool s_doubleEscaped(int indent) => transaction(() { + zeroOrMore(() => consume(isSpace)); + if (!captureAs("", () => consumeChar(BACKSLASH))) return false; + if (!truth(b_nonContent())) return false; + zeroOrMore(() => captureAs("\n", () => l_empty(indent, FLOW_IN))); + return s_flowLinePrefix(indent); + }); + + // 113 + bool s_doubleBreak(int indent) => or([ + () => s_doubleEscaped(indent), + () => s_flowFolded(indent) + ]); + + // 114 + void nb_ns_doubleInLine() { + zeroOrMore(() => transaction(() { + zeroOrMore(() => consume(isSpace)); + return ns_doubleChar(); + })); + } + + // 115 + bool s_doubleNextLine(int indent) { + if (!truth(s_doubleBreak(indent))) return false; + zeroOrOne(() { + if (!truth(ns_doubleChar())) return; + nb_ns_doubleInLine(); + or([ + () => s_doubleNextLine(indent), + () => zeroOrMore(() => consume(isSpace)) + ]); + }); + return true; + } + + // 116 + void nb_doubleMultiLine(int indent) { + nb_ns_doubleInLine(); + or([ + () => s_doubleNextLine(indent), + () => zeroOrMore(() => consume(isSpace)) + ]); + } + + // 117 + bool c_quotedQuote() => captureAs("'", () => rawString("''")); + + // 118 + bool nb_singleChar() => or([ + c_quotedQuote, + () => consume((c) => isJson(c) && c != SINGLE_QUOTE) + ]); + + // 119 + bool ns_singleChar() => !isSpace(peek()) && truth(nb_singleChar()); + + // 120 + _Node c_singleQuoted(int indent, int ctx) => context('string', () { + return transaction(() { + if (!truth(c_indicator(C_SINGLE_QUOTE))) return null; + var contents = nb_singleText(indent, ctx); + if (!truth(c_indicator(C_SINGLE_QUOTE))) return null; + return new _ScalarNode("!", content: contents); + }); + }); + + // 121 + String nb_singleText(int indent, int ctx) => captureString(() { + switch (ctx) { + case FLOW_OUT: + case FLOW_IN: + nb_singleMultiLine(indent); + break; + case BLOCK_KEY: + case FLOW_KEY: + nb_singleOneLine(indent); + break; + } + return true; + }); + + // 122 + void nb_singleOneLine(int indent) { + zeroOrMore(nb_singleChar); + } + + // 123 + void nb_ns_singleInLine() { + zeroOrMore(() => transaction(() { + zeroOrMore(() => consume(isSpace)); + return ns_singleChar(); + })); + } + + // 124 + bool s_singleNextLine(int indent) { + if (!truth(s_flowFolded(indent))) return false; + zeroOrOne(() { + if (!truth(ns_singleChar())) return; + nb_ns_singleInLine(); + or([ + () => s_singleNextLine(indent), + () => zeroOrMore(() => consume(isSpace)) + ]); + }); + return true; + } + + // 125 + void nb_singleMultiLine(int indent) { + nb_ns_singleInLine(); + or([ + () => s_singleNextLine(indent), + () => zeroOrMore(() => consume(isSpace)) + ]); + } + + // 126 + bool ns_plainFirst(int ctx) { + var char = peek(); + var indicator = indicatorType(char); + if (indicator == C_RESERVED) { + error("reserved indicators can't start a plain scalar"); + } + var match = (isNonSpace(char) && indicator == null) || + ((indicator == C_MAPPING_KEY || + indicator == C_MAPPING_VALUE || + indicator == C_SEQUENCE_ENTRY) && + isPlainSafe(ctx, peek(1))); + + if (match) next(); + return match; + } + + // 127 + bool isPlainSafe(int ctx, int char) { + switch (ctx) { + case FLOW_OUT: + case BLOCK_KEY: + // 128 + return isNonSpace(char); + case FLOW_IN: + case FLOW_KEY: + // 129 + return isNonSpace(char) && !isFlowIndicator(char); + default: throw 'invalid context "$ctx"'; + } + } + + // 130 + bool ns_plainChar(int ctx) { + var char = peek(); + var indicator = indicatorType(char); + var safeChar = isPlainSafe(ctx, char) && indicator != C_MAPPING_VALUE && + indicator != C_COMMENT; + var nonCommentHash = isNonSpace(peek(-1)) && indicator == C_COMMENT; + var nonMappingColon = indicator == C_MAPPING_VALUE && + isPlainSafe(ctx, peek(1)); + var match = safeChar || nonCommentHash || nonMappingColon; + + if (match) next(); + return match; + } + + // 131 + String ns_plain(int indent, int ctx) => context('plain scalar', () { + return captureString(() { + switch (ctx) { + case FLOW_OUT: + case FLOW_IN: + return ns_plainMultiLine(indent, ctx); + case BLOCK_KEY: + case FLOW_KEY: + return ns_plainOneLine(ctx); + default: throw 'invalid context "$ctx"'; + } + }); + }); + + // 132 + void nb_ns_plainInLine(int ctx) { + zeroOrMore(() => transaction(() { + zeroOrMore(() => consume(isSpace)); + return ns_plainChar(ctx); + })); + } + + // 133 + bool ns_plainOneLine(int ctx) { + if (truth(c_forbidden())) return false; + if (!truth(ns_plainFirst(ctx))) return false; + nb_ns_plainInLine(ctx); + return true; + } + + // 134 + bool s_ns_plainNextLine(int indent, int ctx) => transaction(() { + if (!truth(s_flowFolded(indent))) return false; + if (truth(c_forbidden())) return false; + if (!truth(ns_plainChar(ctx))) return false; + nb_ns_plainInLine(ctx); + return true; + }); + + // 135 + bool ns_plainMultiLine(int indent, int ctx) { + if (!truth(ns_plainOneLine(ctx))) return false; + zeroOrMore(() => s_ns_plainNextLine(indent, ctx)); + return true; + } + + // 136 + int inFlow(int ctx) { + switch (ctx) { + case FLOW_OUT: + case FLOW_IN: + return FLOW_IN; + case BLOCK_KEY: + case FLOW_KEY: + return FLOW_KEY; + } + } + + // 137 + _SequenceNode c_flowSequence(int indent, int ctx) => transaction(() { + if (!truth(c_indicator(C_SEQUENCE_START))) return null; + zeroOrOne(() => s_separate(indent, ctx)); + var content = zeroOrOne(() => ns_s_flowSeqEntries(indent, inFlow(ctx))); + if (!truth(c_indicator(C_SEQUENCE_END))) return null; + return new _SequenceNode("?", new List<_Node>.from(content)); + }); + + // 138 + Collection<_Node> ns_s_flowSeqEntries(int indent, int ctx) { + var first = ns_flowSeqEntry(indent, ctx); + if (!truth(first)) return new Queue<_Node>(); + zeroOrOne(() => s_separate(indent, ctx)); + + var rest; + if (truth(c_indicator(C_COLLECT_ENTRY))) { + zeroOrOne(() => s_separate(indent, ctx)); + rest = zeroOrOne(() => ns_s_flowSeqEntries(indent, ctx)); + } + + if (rest == null) rest = new Queue<_Node>(); + rest.addFirst(first); + + return rest; + } + + // 139 + _Node ns_flowSeqEntry(int indent, int ctx) => or([ + () => ns_flowPair(indent, ctx), + () => ns_flowNode(indent, ctx) + ]); + + // 140 + _Node c_flowMapping(int indent, int ctx) { + if (!truth(c_indicator(C_MAPPING_START))) return null; + zeroOrOne(() => s_separate(indent, ctx)); + var content = zeroOrOne(() => ns_s_flowMapEntries(indent, inFlow(ctx))); + if (!truth(c_indicator(C_MAPPING_END))) return null; + return new _MappingNode("?", content); + } + + // 141 + YamlMap ns_s_flowMapEntries(int indent, int ctx) { + var first = ns_flowMapEntry(indent, ctx); + if (!truth(first)) return new YamlMap(); + zeroOrOne(() => s_separate(indent, ctx)); + + var rest; + if (truth(c_indicator(C_COLLECT_ENTRY))) { + zeroOrOne(() => s_separate(indent, ctx)); + rest = ns_s_flowMapEntries(indent, ctx); + } + + if (rest == null) rest = new YamlMap(); + + // TODO(nweiz): Duplicate keys should be an error. This includes keys with + // different representations but the same value (e.g. 10 vs 0xa). To make + // this user-friendly we'll probably also want to associate nodes with a + // source range. + if (!rest.containsKey(first.first)) rest[first.first] = first.last; + + return rest; + } + + // 142 + _Pair<_Node, _Node> ns_flowMapEntry(int indent, int ctx) => or([ + () => transaction(() { + if (!truth(c_indicator(C_MAPPING_KEY))) return false; + if (!truth(s_separate(indent, ctx))) return false; + return ns_flowMapExplicitEntry(indent, ctx); + }), + () => ns_flowMapImplicitEntry(indent, ctx) + ]); + + // 143 + _Pair<_Node, _Node> ns_flowMapExplicitEntry(int indent, int ctx) => or([ + () => ns_flowMapImplicitEntry(indent, ctx), + () => new _Pair<_Node, _Node>(e_node(), e_node()) + ]); + + // 144 + _Pair<_Node, _Node> ns_flowMapImplicitEntry(int indent, int ctx) => or([ + () => ns_flowMapYamlKeyEntry(indent, ctx), + () => c_ns_flowMapEmptyKeyEntry(indent, ctx), + () => c_ns_flowMapJsonKeyEntry(indent, ctx) + ]); + + // 145 + _Pair<_Node, _Node> ns_flowMapYamlKeyEntry(int indent, int ctx) { + var key = ns_flowYamlNode(indent, ctx); + if (!truth(key)) return null; + var value = or([ + () => transaction(() { + zeroOrOne(() => s_separate(indent, ctx)); + return c_ns_flowMapSeparateValue(indent, ctx); + }), + e_node + ]); + return new _Pair<_Node, _Node>(key, value); + } + + // 146 + _Pair<_Node, _Node> c_ns_flowMapEmptyKeyEntry(int indent, int ctx) { + var value = c_ns_flowMapSeparateValue(indent, ctx); + if (!truth(value)) return null; + return new _Pair<_Node, _Node>(e_node(), value); + } + + // 147 + _Node c_ns_flowMapSeparateValue(int indent, int ctx) => transaction(() { + if (!truth(c_indicator(C_MAPPING_VALUE))) return null; + if (isPlainSafe(ctx, peek())) return null; + + return or([ + () => transaction(() { + if (!s_separate(indent, ctx)) return null; + return ns_flowNode(indent, ctx); + }), + e_node + ]); + }); + + // 148 + _Pair<_Node, _Node> c_ns_flowMapJsonKeyEntry(int indent, int ctx) { + var key = c_flowJsonNode(indent, ctx); + if (!truth(key)) return null; + var value = or([ + () => transaction(() { + zeroOrOne(() => s_separate(indent, ctx)); + return c_ns_flowMapAdjacentValue(indent, ctx); + }), + e_node + ]); + return new _Pair<_Node, _Node>(key, value); + } + + // 149 + _Node c_ns_flowMapAdjacentValue(int indent, int ctx) { + if (!truth(c_indicator(C_MAPPING_VALUE))) return null; + return or([ + () => transaction(() { + zeroOrOne(() => s_separate(indent, ctx)); + return ns_flowNode(indent, ctx); + }), + e_node + ]); + } + + // 150 + _Node ns_flowPair(int indent, int ctx) { + var pair = or([ + () => transaction(() { + if (!truth(c_indicator(C_MAPPING_KEY))) return null; + if (!truth(s_separate(indent, ctx))) return null; + return ns_flowMapExplicitEntry(indent, ctx); + }), + () => ns_flowPairEntry(indent, ctx) + ]); + if (!truth(pair)) return null; + + return map([pair]); + } + + // 151 + _Pair<_Node, _Node> ns_flowPairEntry(int indent, int ctx) => or([ + () => ns_flowPairYamlKeyEntry(indent, ctx), + () => c_ns_flowMapEmptyKeyEntry(indent, ctx), + () => c_ns_flowPairJsonKeyEntry(indent, ctx) + ]); + + // 152 + _Pair<_Node, _Node> ns_flowPairYamlKeyEntry(int indent, int ctx) => + transaction(() { + var key = ns_s_implicitYamlKey(FLOW_KEY); + if (!truth(key)) return null; + var value = c_ns_flowMapSeparateValue(indent, ctx); + if (!truth(value)) return null; + return new _Pair<_Node, _Node>(key, value); + }); + + // 153 + _Pair<_Node, _Node> c_ns_flowPairJsonKeyEntry(int indent, int ctx) => + transaction(() { + var key = c_s_implicitJsonKey(FLOW_KEY); + if (!truth(key)) return null; + var value = c_ns_flowMapAdjacentValue(indent, ctx); + if (!truth(value)) return null; + return new _Pair<_Node, _Node>(key, value); + }); + + // 154 + _Node ns_s_implicitYamlKey(int ctx) => transaction(() { + // TODO(nweiz): this is supposed to be limited to 1024 characters. + + // The indentation parameter is "null" since it's unused in this path + var node = ns_flowYamlNode(null, ctx); + if (!truth(node)) return null; + zeroOrOne(s_separateInLine); + return node; + }); + + // 155 + _Node c_s_implicitJsonKey(int ctx) => transaction(() { + // TODO(nweiz): this is supposed to be limited to 1024 characters. + + // The indentation parameter is "null" since it's unused in this path + var node = c_flowJsonNode(null, ctx); + if (!truth(node)) return null; + zeroOrOne(s_separateInLine); + return node; + }); + + // 156 + _Node ns_flowYamlContent(int indent, int ctx) { + var str = ns_plain(indent, ctx); + if (!truth(str)) return null; + return new _ScalarNode("?", content: str); + } + + // 157 + _Node c_flowJsonContent(int indent, int ctx) => or([ + () => c_flowSequence(indent, ctx), + () => c_flowMapping(indent, ctx), + () => c_singleQuoted(indent, ctx), + () => c_doubleQuoted(indent, ctx) + ]); + + // 158 + _Node ns_flowContent(int indent, int ctx) => or([ + () => ns_flowYamlContent(indent, ctx), + () => c_flowJsonContent(indent, ctx) + ]); + + // 159 + _Node ns_flowYamlNode(int indent, int ctx) => or([ + c_ns_aliasNode, + () => ns_flowYamlContent(indent, ctx), + () { + var props = c_ns_properties(indent, ctx); + if (!truth(props)) return null; + var node = or([ + () => transaction(() { + if (!truth(s_separate(indent, ctx))) return null; + return ns_flowYamlContent(indent, ctx); + }), + e_scalar + ]); + return addProps(node, props); + } + ]); + + // 160 + _Node c_flowJsonNode(int indent, int ctx) => transaction(() { + var props; + zeroOrOne(() => transaction(() { + props = c_ns_properties(indent, ctx); + if (!truth(props)) return null; + return s_separate(indent, ctx); + })); + + return addProps(c_flowJsonContent(indent, ctx), props); + }); + + // 161 + _Node ns_flowNode(int indent, int ctx) => or([ + c_ns_aliasNode, + () => ns_flowContent(indent, ctx), + () => transaction(() { + var props = c_ns_properties(indent, ctx); + if (!truth(props)) return null; + var node = or([ + () => transaction(() => s_separate(indent, ctx) ? + ns_flowContent(indent, ctx) : null), + e_scalar]); + return addProps(node, props); + }) + ]); + + // 162 + _BlockHeader c_b_blockHeader() => transaction(() { + var indentation = c_indentationIndicator(); + var chomping = c_chompingIndicator(); + if (!truth(indentation)) indentation = c_indentationIndicator(); + if (!truth(s_b_comment())) return null; + + return new _BlockHeader(indentation, chomping); + }); + + // 163 + int c_indentationIndicator() { + if (!isDecDigit(peek())) return null; + return next() - NUMBER_0; + } + + // 164 + int c_chompingIndicator() { + switch (peek()) { + case HYPHEN: + next(); + return CHOMPING_STRIP; + case PLUS: + next(); + return CHOMPING_KEEP; + default: + return CHOMPING_CLIP; + } + } + + // 165 + bool b_chompedLast(int chomping) { + if (atEndOfFile) return true; + switch (chomping) { + case CHOMPING_STRIP: + return b_nonContent(); + case CHOMPING_CLIP: + case CHOMPING_KEEP: + return b_asLineFeed(); + } + } + + // 166 + void l_chompedEmpty(int indent, int chomping) { + switch (chomping) { + case CHOMPING_STRIP: + case CHOMPING_CLIP: + l_stripEmpty(indent); + break; + case CHOMPING_KEEP: + l_keepEmpty(indent); + break; + } + } + + // 167 + void l_stripEmpty(int indent) { + captureAs('', () { + zeroOrMore(() => transaction(() { + if (!truth(s_indentLessThanOrEqualTo(indent))) return false; + return b_nonContent(); + })); + zeroOrOne(() => l_trailComments(indent)); + return true; + }); + } + + // 168 + void l_keepEmpty(int indent) { + zeroOrMore(() => captureAs('\n', () => l_empty(indent, BLOCK_IN))); + zeroOrOne(() => captureAs('', () => l_trailComments(indent))); + } + + // 169 + bool l_trailComments(int indent) => transaction(() { + if (!truth(s_indentLessThanOrEqualTo(indent))) return false; + if (!truth(c_nb_commentText())) return false; + if (!truth(b_comment())) return false; + zeroOrMore(l_comment); + return true; + }); + + // 170 + _Node c_l_literal(int indent) => transaction(() { + if (!truth(c_indicator(C_LITERAL))) return null; + var header = c_b_blockHeader(); + if (!truth(header)) return null; + + var additionalIndent = blockScalarAdditionalIndentation(header, indent); + var content = l_literalContent(indent + additionalIndent, header.chomping); + if (!truth(content)) return null; + + return new _ScalarNode("!", content: content); + }); + + // 171 + bool l_nb_literalText(int indent) => transaction(() { + zeroOrMore(() => captureAs("\n", () => l_empty(indent, BLOCK_IN))); + if (!truth(captureAs("", () => s_indent(indent)))) return false; + return truth(oneOrMore(() => consume(isNonBreak))); + }); + + // 172 + bool b_nb_literalNext(int indent) => transaction(() { + if (!truth(b_asLineFeed())) return false; + return l_nb_literalText(indent); + }); + + // 173 + String l_literalContent(int indent, int chomping) => captureString(() { + transaction(() { + if (!truth(l_nb_literalText(indent))) return false; + zeroOrMore(() => b_nb_literalNext(indent)); + return b_chompedLast(chomping); + }); + l_chompedEmpty(indent, chomping); + return true; + }); + + // 174 + _Node c_l_folded(int indent) => transaction(() { + if (!truth(c_indicator(C_FOLDED))) return null; + var header = c_b_blockHeader(); + if (!truth(header)) return null; + + var additionalIndent = blockScalarAdditionalIndentation(header, indent); + var content = l_foldedContent(indent + additionalIndent, header.chomping); + if (!truth(content)) return null; + + return new _ScalarNode("!", content: content); + }); + + // 175 + bool s_nb_foldedText(int indent) => transaction(() { + if (!truth(captureAs('', () => s_indent(indent)))) return false; + if (!truth(consume(isNonSpace))) return false; + zeroOrMore(() => consume(isNonBreak)); + return true; + }); + + // 176 + bool l_nb_foldedLines(int indent) { + if (!truth(s_nb_foldedText(indent))) return false; + zeroOrMore(() => transaction(() { + if (!truth(b_l_folded(indent, BLOCK_IN))) return false; + return s_nb_foldedText(indent); + })); + return true; + } + + // 177 + bool s_nb_spacedText(int indent) => transaction(() { + if (!truth(captureAs('', () => s_indent(indent)))) return false; + if (!truth(consume(isSpace))) return false; + zeroOrMore(() => consume(isNonBreak)); + return true; + }); + + // 178 + bool b_l_spaced(int indent) { + if (!truth(b_asLineFeed())) return false; + zeroOrMore(() => captureAs("\n", () => l_empty(indent, BLOCK_IN))); + return true; + } + + // 179 + bool l_nb_spacedLines(int indent) { + if (!truth(s_nb_spacedText(indent))) return false; + zeroOrMore(() => transaction(() { + if (!truth(b_l_spaced(indent))) return false; + return s_nb_spacedText(indent); + })); + return true; + } + + // 180 + bool l_nb_sameLines(int indent) => transaction(() { + zeroOrMore(() => captureAs('\n', () => l_empty(indent, BLOCK_IN))); + return or([ + () => l_nb_foldedLines(indent), + () => l_nb_spacedLines(indent) + ]); + }); + + // 181 + bool l_nb_diffLines(int indent) { + if (!truth(l_nb_sameLines(indent))) return false; + zeroOrMore(() => transaction(() { + if (!truth(b_asLineFeed())) return false; + return l_nb_sameLines(indent); + })); + return true; + } + + // 182 + String l_foldedContent(int indent, int chomping) => captureString(() { + transaction(() { + if (!truth(l_nb_diffLines(indent))) return false; + return b_chompedLast(chomping); + }); + l_chompedEmpty(indent, chomping); + return true; + }); + + // 183 + _SequenceNode l_blockSequence(int indent) => context('sequence', () { + var additionalIndent = countIndentation() - indent; + if (additionalIndent <= 0) return null; + + var content = oneOrMore(() => transaction(() { + if (!truth(s_indent(indent + additionalIndent))) return null; + return c_l_blockSeqEntry(indent + additionalIndent); + })); + if (!truth(content)) return null; + + return new _SequenceNode("?", content); + }); + + // 184 + _Node c_l_blockSeqEntry(int indent) => transaction(() { + if (!truth(c_indicator(C_SEQUENCE_ENTRY))) return null; + if (isNonSpace(peek())) return null; + + return s_l_blockIndented(indent, BLOCK_IN); + }); + + // 185 + _Node s_l_blockIndented(int indent, int ctx) { + var additionalIndent = countIndentation(); + return or([ + () => transaction(() { + if (!truth(s_indent(additionalIndent))) return null; + return or([ + () => ns_l_compactSequence(indent + 1 + additionalIndent), + () => ns_l_compactMapping(indent + 1 + additionalIndent)]); + }), + () => s_l_blockNode(indent, ctx), + () => s_l_comments() ? e_node() : null]); + } + + // 186 + _Node ns_l_compactSequence(int indent) => context('sequence', () { + var first = c_l_blockSeqEntry(indent); + if (!truth(first)) return null; + + var content = zeroOrMore(() => transaction(() { + if (!truth(s_indent(indent))) return null; + return c_l_blockSeqEntry(indent); + })); + content.insertRange(0, 1, first); + + return new _SequenceNode("?", content); + }); + + // 187 + _Node l_blockMapping(int indent) => context('mapping', () { + var additionalIndent = countIndentation() - indent; + if (additionalIndent <= 0) return null; + + var pairs = oneOrMore(() => transaction(() { + if (!truth(s_indent(indent + additionalIndent))) return null; + return ns_l_blockMapEntry(indent + additionalIndent); + })); + if (!truth(pairs)) return null; + + return map(pairs); + }); + + // 188 + _Pair<_Node, _Node> ns_l_blockMapEntry(int indent) => or([ + () => c_l_blockMapExplicitEntry(indent), + () => ns_l_blockMapImplicitEntry(indent) + ]); + + // 189 + _Pair<_Node, _Node> c_l_blockMapExplicitEntry(int indent) { + var key = c_l_blockMapExplicitKey(indent); + if (!truth(key)) return null; + + var value = or([ + () => l_blockMapExplicitValue(indent), + e_node + ]); + + return new _Pair<_Node, _Node>(key, value); + } + + // 190 + _Node c_l_blockMapExplicitKey(int indent) => transaction(() { + if (!truth(c_indicator(C_MAPPING_KEY))) return null; + return s_l_blockIndented(indent, BLOCK_OUT); + }); + + // 191 + _Node l_blockMapExplicitValue(int indent) => transaction(() { + if (!truth(s_indent(indent))) return null; + if (!truth(c_indicator(C_MAPPING_VALUE))) return null; + return s_l_blockIndented(indent, BLOCK_OUT); + }); + + // 192 + _Pair<_Node, _Node> ns_l_blockMapImplicitEntry(int indent) => transaction(() { + var key = or([ns_s_blockMapImplicitKey, e_node]); + var value = c_l_blockMapImplicitValue(indent); + return truth(value) ? new _Pair<_Node, _Node>(key, value) : null; + }); + + // 193 + _Node ns_s_blockMapImplicitKey() => context('mapping key', () => or([ + () => c_s_implicitJsonKey(BLOCK_KEY), + () => ns_s_implicitYamlKey(BLOCK_KEY) + ])); + + // 194 + _Node c_l_blockMapImplicitValue(int indent) => context('mapping value', () => + transaction(() { + if (!truth(c_indicator(C_MAPPING_VALUE))) return null; + return or([ + () => s_l_blockNode(indent, BLOCK_OUT), + () => s_l_comments() ? e_node() : null + ]); + })); + + // 195 + _Node ns_l_compactMapping(int indent) => context('mapping', () { + var first = ns_l_blockMapEntry(indent); + if (!truth(first)) return null; + + var pairs = zeroOrMore(() => transaction(() { + if (!truth(s_indent(indent))) return null; + return ns_l_blockMapEntry(indent); + })); + pairs.insertRange(0, 1, first); + + return map(pairs); + }); + + // 196 + _Node s_l_blockNode(int indent, int ctx) => or([ + () => s_l_blockInBlock(indent, ctx), + () => s_l_flowInBlock(indent) + ]); + + // 197 + _Node s_l_flowInBlock(int indent) => transaction(() { + if (!truth(s_separate(indent + 1, FLOW_OUT))) return null; + var node = ns_flowNode(indent + 1, FLOW_OUT); + if (!truth(node)) return null; + if (!truth(s_l_comments())) return null; + return node; + }); + + // 198 + _Node s_l_blockInBlock(int indent, int ctx) => or([ + () => s_l_blockScalar(indent, ctx), + () => s_l_blockCollection(indent, ctx) + ]); + + // 199 + _Node s_l_blockScalar(int indent, int ctx) => transaction(() { + if (!truth(s_separate(indent + 1, ctx))) return null; + var props = transaction(() { + var innerProps = c_ns_properties(indent + 1, ctx); + if (!truth(innerProps)) return null; + if (!truth(s_separate(indent + 1, ctx))) return null; + return innerProps; + }); + + var node = or([() => c_l_literal(indent), () => c_l_folded(indent)]); + if (!truth(node)) return null; + return addProps(node, props); + }); + + // 200 + _Node s_l_blockCollection(int indent, int ctx) => transaction(() { + var props = transaction(() { + if (!truth(s_separate(indent + 1, ctx))) return null; + return c_ns_properties(indent + 1, ctx); + }); + + if (!truth(s_l_comments())) return null; + return or([ + () => l_blockSequence(seqSpaces(indent, ctx)), + () => l_blockMapping(indent)]); + }); + + // 201 + int seqSpaces(int indent, int ctx) => ctx == BLOCK_OUT ? indent - 1 : indent; + + // 202 + void l_documentPrefix() { + zeroOrMore(l_comment); + } + + // 203 + bool c_directivesEnd() => rawString("---"); + + // 204 + bool c_documentEnd() => rawString("..."); + + // 205 + bool l_documentSuffix() => transaction(() { + if (!truth(c_documentEnd())) return false; + return s_l_comments(); + }); + + // 206 + bool c_forbidden() { + if (!inBareDocument || !atStartOfLine) return false; + var forbidden = false; + transaction(() { + if (!truth(or([c_directivesEnd, c_documentEnd]))) return; + var char = peek(); + forbidden = isBreak(char) || isSpace(char) || atEndOfFile; + return; + }); + return forbidden; + } + + // 207 + _Node l_bareDocument() { + try { + inBareDocument = true; + return s_l_blockNode(-1, BLOCK_IN); + } finally { + inBareDocument = false; + } + } + + // 208 + _Node l_explicitDocument() { + if (!truth(c_directivesEnd())) return null; + var doc = l_bareDocument(); + if (truth(doc)) return doc; + + doc = e_node(); + s_l_comments(); + return doc; + } + + // 209 + _Node l_directiveDocument() { + if (!truth(oneOrMore(l_directive))) return null; + var doc = l_explicitDocument(); + if (doc != null) return doc; + parseFailed(); + return null; // Unreachable. + } + + // 210 + _Node l_anyDocument() => + or([l_directiveDocument, l_explicitDocument, l_bareDocument]); + + // 211 + List<_Node> l_yamlStream() { + var docs = []; + zeroOrMore(l_documentPrefix); + var first = zeroOrOne(l_anyDocument); + if (!truth(first)) first = e_node(); + docs.add(first); + + zeroOrMore(() { + var doc; + if (truth(oneOrMore(l_documentSuffix))) { + zeroOrMore(l_documentPrefix); + doc = zeroOrOne(l_anyDocument); + } else { + zeroOrMore(l_documentPrefix); + doc = zeroOrOne(l_explicitDocument); + } + if (truth(doc)) docs.add(doc); + return doc; + }); + + if (!atEndOfFile) parseFailed(); + return docs; + } +} + +class SyntaxError extends YamlException { + final int line; + final int column; + + SyntaxError(this.line, this.column, String msg) : super(msg); + + String toString() => "Syntax error on line $line, column $column: $msg"; +} + +/// A pair of values. +class _Pair<E, F> { + E first; + F last; + + _Pair(this.first, this.last); + + String toString() => '($first, $last)'; +} + +/// The information in the header for a block scalar. +class _BlockHeader { + final int additionalIndent; + final int chomping; + + _BlockHeader(this.additionalIndent, this.chomping); + + bool get autoDetectIndent => additionalIndent == null; +} + +/// A range of characters in the YAML document, from [start] to [end] +/// (inclusive). +class _Range { + /// The first character in the range. + final int start; + + /// The last character in the range. + final int end; + + _Range(this.start, this.end); + + /// Returns whether or not [pos] lies within this range. + bool contains(int pos) => pos >= start && pos <= end; +} + +/// A map that associates [E] values with [_Range]s. It's efficient to create +/// new associations, but finding the value associated with a position is more +/// expensive. +class _RangeMap<E> { + /// The ranges and their associated elements. + final List<_Pair<_Range, E>> contents; + + _RangeMap() : this.contents = <_Pair<_Range, E>>[]; + + /// Returns the value associated with the range in which [pos] lies, or null + /// if there is no such range. If there's more than one such range, the most + /// recently set one is used. + E operator[](int pos) { + // Iterate backwards through contents so the more recent range takes + // precedence. TODO(nweiz): clean this up when issue 2804 is fixed. + for (var i = contents.length - 1; i >= 0; i--) { + var pair = contents[i]; + if (pair.first.contains(pos)) return pair.last; + } + return null; + } + + /// Associates [value] with [range]. + operator[]=(_Range range, E value) => + contents.add(new _Pair<_Range, E>(range, value)); +}
diff --git a/pkgs/yaml/lib/visitor.dart b/pkgs/yaml/lib/visitor.dart new file mode 100644 index 0000000..b5c14c9 --- /dev/null +++ b/pkgs/yaml/lib/visitor.dart
@@ -0,0 +1,27 @@ +// Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +part of yaml; + +/// The visitor pattern for YAML documents. +class _Visitor { + /// Returns [alias]. + visitAlias(_AliasNode alias) => alias; + + /// Returns [scalar]. + visitScalar(_ScalarNode scalar) => scalar; + + /// Visits each node in [seq] and returns a list of the results. + visitSequence(_SequenceNode seq) + => seq.content.mappedBy((e) => e.visit(this)).toList(); + + /// Visits each key and value in [map] and returns a map of the results. + visitMapping(_MappingNode map) { + var out = new YamlMap(); + for (var key in map.content.keys) { + out[key.visit(this)] = map.content[key].visit(this); + } + return out; + } +}
diff --git a/pkgs/yaml/lib/yaml.dart b/pkgs/yaml/lib/yaml.dart new file mode 100644 index 0000000..ef0ac83 --- /dev/null +++ b/pkgs/yaml/lib/yaml.dart
@@ -0,0 +1,75 @@ +// Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +/// A parser for [YAML](http://www.yaml.org/). +/// +/// Use [loadYaml] to load a single document, or [loadYamlStream] to load a +/// stream of documents. For example: +/// +/// import 'package:yaml/yaml.dart'; +/// main() { +/// var doc = loadYaml("YAML: YAML Ain't Markup Language"); +/// print(doc['YAML']); +/// } +/// +/// This library currently doesn't support dumping to YAML. You should use +/// `stringify` from `dart:json` instead: +/// +/// import 'dart:json' as json; +/// import 'package:yaml/yaml.dart'; +/// main() { +/// var doc = loadYaml("YAML: YAML Ain't Markup Language"); +/// print(json.stringify(doc)); +/// } +library yaml; + +import 'dart:math' as Math; +import 'dart:collection' show Queue; + +import 'deep_equals.dart'; + +part 'yaml_map.dart'; +part 'model.dart'; +part 'parser.dart'; +part 'visitor.dart'; +part 'composer.dart'; +part 'constructor.dart'; + +/// Loads a single document from a YAML string. If the string contains more than +/// one document, this throws an error. +/// +/// The return value is mostly normal Dart objects. However, since YAML mappings +/// support some key types that the default Dart map implementation doesn't +/// (null, NaN, booleans, lists, and maps), all maps in the returned document +/// are YamlMaps. These have a few small behavioral differences from the default +/// Map implementation; for details, see the YamlMap class. +loadYaml(String yaml) { + var stream = loadYamlStream(yaml); + if (stream.length != 1) { + throw new YamlException("Expected 1 document, were ${stream.length}"); + } + return stream[0]; +} + +/// Loads a stream of documents from a YAML string. +/// +/// The return value is mostly normal Dart objects. However, since YAML mappings +/// support some key types that the default Dart map implementation doesn't +/// (null, NaN, booleans, lists, and maps), all maps in the returned document +/// are YamlMaps. These have a few small behavioral differences from the default +/// Map implementation; for details, see the YamlMap class. +List loadYamlStream(String yaml) { + return new _Parser(yaml).l_yamlStream().mappedBy((doc) => + new _Constructor(new _Composer(doc).compose()).construct()) + .toList(); +} + +/// An error thrown by the YAML processor. +class YamlException implements Exception { + String msg; + + YamlException(this.msg); + + String toString() => msg; +}
diff --git a/pkgs/yaml/lib/yaml_map.dart b/pkgs/yaml/lib/yaml_map.dart new file mode 100644 index 0000000..65dd119 --- /dev/null +++ b/pkgs/yaml/lib/yaml_map.dart
@@ -0,0 +1,110 @@ +// Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +part of yaml; + +/// This class wraps behaves almost identically to the normal Dart Map +/// implementation, with the following differences: +/// +/// * It allows null, NaN, boolean, list, and map keys. +/// * It defines `==` structurally. That is, `yamlMap1 == yamlMap2` if they +/// have the same contents. +/// * It has a compatible [hashCode] method. +class YamlMap implements Map { + Map _map; + + YamlMap() : _map = new Map(); + + YamlMap.from(Map map) : _map = new Map.from(map); + + YamlMap._wrap(this._map); + + bool containsValue(value) => _map.containsValue(value); + bool containsKey(key) => _map.containsKey(_wrapKey(key)); + operator [](key) => _map[_wrapKey(key)]; + operator []=(key, value) { _map[_wrapKey(key)] = value; } + putIfAbsent(key, ifAbsent()) => _map.putIfAbsent(_wrapKey(key), ifAbsent); + remove(key) => _map.remove(_wrapKey(key)); + void clear() => _map.clear(); + void forEach(void f(key, value)) => + _map.forEach((k, v) => f(_unwrapKey(k), v)); + Iterable get keys => _map.keys.mappedBy(_unwrapKey); + Iterable get values => _map.values; + int get length => _map.length; + bool get isEmpty => _map.isEmpty; + String toString() => _map.toString(); + + int get hashCode => _hashCode(_map); + + bool operator ==(other) { + if (other is! YamlMap) return false; + return deepEquals(this, other); + } + + /// Wraps an object for use as a key in the map. + _wrapKey(obj) { + if (obj != null && obj is! bool && obj is! List && + (obj is! double || !obj.isNan()) && + (obj is! Map || obj is YamlMap)) { + return obj; + } else if (obj is Map) { + return new YamlMap._wrap(obj); + } + return new _WrappedHashKey(obj); + } + + /// Unwraps an object that was used as a key in the map. + _unwrapKey(obj) => obj is _WrappedHashKey ? obj.value : obj; +} + +/// A class for wrapping normally-unhashable objects that are being used as keys +/// in a YamlMap. +class _WrappedHashKey { + var value; + + _WrappedHashKey(this.value); + + int get hashCode => _hashCode(value); + + String toString() => value.toString(); + + /// This is defined as both values being structurally equal. + bool operator ==(other) { + if (other is! _WrappedHashKey) return false; + return deepEquals(this.value, other.value); + } +} + +/// Returns the hash code for [obj]. This includes null, true, false, maps, and +/// lists. Also handles self-referential structures. +int _hashCode(obj, [List parents]) { + if (parents == null) { + parents = []; + } else if (parents.any((p) => identical(p, obj))) { + return -1; + } + + parents.add(obj); + try { + if (obj == null) return 0; + if (obj == true) return 1; + if (obj == false) return 2; + if (obj is Map) { + return _hashCode(obj.keys, parents) ^ + _hashCode(obj.values, parents); + } + if (obj is Iterable) { + // This is probably a really bad hash function, but presumably we'll get + // this in the standard library before it actually matters. + int hash = 0; + for (var e in obj) { + hash ^= _hashCode(e, parents); + } + return hash; + } + return obj.hashCode; + } finally { + parents.removeLast(); + } +}
diff --git a/pkgs/yaml/pubspec.yaml b/pkgs/yaml/pubspec.yaml new file mode 100644 index 0000000..7c944d2 --- /dev/null +++ b/pkgs/yaml/pubspec.yaml
@@ -0,0 +1,7 @@ +name: yaml +author: "Dart Team <misc@dartlang.org>" +homepage: http://www.dartlang.org +description: A parser for YAML. +dependencies: + unittest: + sdk: unittest
diff --git a/pkgs/yaml/test/yaml_test.dart b/pkgs/yaml/test/yaml_test.dart new file mode 100644 index 0000000..b939b67 --- /dev/null +++ b/pkgs/yaml/test/yaml_test.dart
@@ -0,0 +1,1879 @@ +// Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +library yaml_test; + +import 'package:unittest/unittest.dart'; +import 'package:yaml/yaml.dart'; +import 'package:yaml/deep_equals.dart'; +// TODO(jmesserly): we should not be reaching outside the YAML package +// The http package has a similar problem. +import '../../../tests/utils/test_utils.dart'; + +/// Constructs a new yaml.YamlMap, optionally from a normal Map. +Map yamlMap([Map from]) => + from == null ? new YamlMap() : new YamlMap.from(from); + +/// Asserts that a string containing a single YAML document produces a given +/// value when loaded. +expectYamlLoads(expected, String source) { + var actual = loadYaml(cleanUpLiteral(source)); + Expect.isTrue(deepEquals(expected, actual), + 'expectYamlLoads(expected: <$expected>, actual: <$actual>)'); +} + +/// Asserts that a string containing a stream of YAML documents produces a given +/// list of values when loaded. +expectYamlStreamLoads(List expected, String source) { + var actual = loadYamlStream(cleanUpLiteral(source)); + Expect.isTrue(deepEquals(expected, actual), + 'expectYamlStreamLoads(expected: <$expected>, actual: <$actual>)'); +} + +main() { + var infinity = double.parse("Infinity"); + var nan = double.parse("NaN"); + + group('YamlMap', () { + group('accepts as a key', () { + _expectKeyWorks(keyFn()) { + var map = yamlMap(); + map[keyFn()] = 5; + expect(map.containsKey(keyFn()), isTrue); + expect(map[keyFn()], 5); + } + + test('null', () => _expectKeyWorks(() => null)); + test('true', () => _expectKeyWorks(() => true)); + test('false', () => _expectKeyWorks(() => false)); + test('a list', () => _expectKeyWorks(() => [1, 2, 3])); + test('a map', () => _expectKeyWorks(() => {'foo': 'bar'})); + test('a YAML map', () => _expectKeyWorks(() => yamlMap({'foo': 'bar'}))); + }); + + test('works as a hash key', () { + var normalMap = new Map(); + normalMap[yamlMap({'foo': 'bar'})] = 'baz'; + expect(normalMap.containsKey(yamlMap({'foo': 'bar'})), isTrue); + expect(normalMap[yamlMap({'foo': 'bar'})], 'baz'); + }); + + test('treats YamlMap keys the same as normal maps', () { + var map = yamlMap(); + map[{'a': 'b'}] = 5; + expect(map[yamlMap({'a': 'b'})], 5); + }); + }); + + group('has a friendly error message for', () { + var tabError = predicate((e) => + e.toString().contains('tab characters are not allowed as indentation')); + + test('using a tab as indentation', () { + expect(() => loadYaml('foo:\n\tbar'), + throwsA(tabError)); + }); + + test('using a tab not as indentation', () { + expect(() => loadYaml(''' + "foo + \tbar" + error'''), + throwsA(isNot(tabError))); + }); + }); + + // The following tests are all taken directly from the YAML spec + // (http://www.yaml.org/spec/1.2/spec.html). Most of them are code examples + // that are directly included in the spec, but additional tests are derived + // from the prose. + + // A few examples from the spec are deliberately excluded, because they test + // features that this implementation doesn't intend to support (character + // encoding detection and user-defined tags). More tests are commented out, + // because they're intended to be supported but not yet implemented. + + // Chapter 2 is just a preview of various Yaml documents. It's probably not + // necessary to test its examples, but it would be nice to test everything in + // the spec. + group('2.1: Collections', () { + test('[Example 2.1]', () { + expectYamlLoads(["Mark McGwire", "Sammy Sosa", "Ken Griffey"], + """ + - Mark McGwire + - Sammy Sosa + - Ken Griffey"""); + }); + + test('[Example 2.2]', () { + expectYamlLoads({"hr": 65, "avg": 0.278, "rbi": 147}, + """ + hr: 65 # Home runs + avg: 0.278 # Batting average + rbi: 147 # Runs Batted In"""); + }); + + test('[Example 2.3]', () { + expectYamlLoads({ + "american": ["Boston Red Sox", "Detroit Tigers", "New York Yankees"], + "national": ["New York Mets", "Chicago Cubs", "Atlanta Braves"], + }, + """ + american: + - Boston Red Sox + - Detroit Tigers + - New York Yankees + national: + - New York Mets + - Chicago Cubs + - Atlanta Braves"""); + }); + + test('[Example 2.4]', () { + expectYamlLoads([ + {"name": "Mark McGwire", "hr": 65, "avg": 0.278}, + {"name": "Sammy Sosa", "hr": 63, "avg": 0.288}, + ], + """ + - + name: Mark McGwire + hr: 65 + avg: 0.278 + - + name: Sammy Sosa + hr: 63 + avg: 0.288"""); + }); + + test('[Example 2.5]', () { + expectYamlLoads([ + ["name", "hr", "avg"], + ["Mark McGwire", 65, 0.278], + ["Sammy Sosa", 63, 0.288] + ], + """ + - [name , hr, avg ] + - [Mark McGwire, 65, 0.278] + - [Sammy Sosa , 63, 0.288]"""); + }); + + test('[Example 2.6]', () { + expectYamlLoads({ + "Mark McGwire": {"hr": 65, "avg": 0.278}, + "Sammy Sosa": {"hr": 63, "avg": 0.288} + }, + """ + Mark McGwire: {hr: 65, avg: 0.278} + Sammy Sosa: { + hr: 63, + avg: 0.288 + }"""); + }); + }); + + group('2.2: Structures', () { + test('[Example 2.7]', () { + expectYamlStreamLoads([ + ["Mark McGwire", "Sammy Sosa", "Ken Griffey"], + ["Chicago Cubs", "St Louis Cardinals"] + ], + """ + # Ranking of 1998 home runs + --- + - Mark McGwire + - Sammy Sosa + - Ken Griffey + + # Team ranking + --- + - Chicago Cubs + - St Louis Cardinals"""); + }); + + test('[Example 2.8]', () { + expectYamlStreamLoads([ + {"time": "20:03:20", "player": "Sammy Sosa", "action": "strike (miss)"}, + {"time": "20:03:47", "player": "Sammy Sosa", "action": "grand slam"}, + ], + """ + --- + time: 20:03:20 + player: Sammy Sosa + action: strike (miss) + ... + --- + time: 20:03:47 + player: Sammy Sosa + action: grand slam + ..."""); + }); + + test('[Example 2.9]', () { + expectYamlLoads({ + "hr": ["Mark McGwire", "Sammy Sosa"], + "rbi": ["Sammy Sosa", "Ken Griffey"] + }, + """ + --- + hr: # 1998 hr ranking + - Mark McGwire + - Sammy Sosa + rbi: + # 1998 rbi ranking + - Sammy Sosa + - Ken Griffey"""); + }); + + // test('[Example 2.10]', () { + // expectYamlLoads({ + // "hr": ["Mark McGwire", "Sammy Sosa"], + // "rbi": ["Sammy Sosa", "Ken Griffey"] + // }, + // """ + // --- + // hr: + // - Mark McGwire + // # Following node labeled SS + // - &SS Sammy Sosa + // rbi: + // - *SS # Subsequent occurrence + // - Ken Griffey"""); + // }); + + test('[Example 2.11]', () { + var doc = yamlMap(); + doc[["Detroit Tigers", "Chicago cubs"]] = ["2001-07-23"]; + doc[["New York Yankees", "Atlanta Braves"]] = + ["2001-07-02", "2001-08-12", "2001-08-14"]; + expectYamlLoads(doc, + """ + ? - Detroit Tigers + - Chicago cubs + : + - 2001-07-23 + + ? [ New York Yankees, + Atlanta Braves ] + : [ 2001-07-02, 2001-08-12, + 2001-08-14 ]"""); + }); + + test('[Example 2.12]', () { + expectYamlLoads([ + {"item": "Super Hoop", "quantity": 1}, + {"item": "Basketball", "quantity": 4}, + {"item": "Big Shoes", "quantity": 1}, + ], + """ + --- + # Products purchased + - item : Super Hoop + quantity: 1 + - item : Basketball + quantity: 4 + - item : Big Shoes + quantity: 1"""); + }); + }); + + group('2.3: Scalars', () { + test('[Example 2.13]', () { + expectYamlLoads( + cleanUpLiteral( + """ + \\//||\\/|| + // || ||__"""), + """ + # ASCII Art + --- | + \\//||\\/|| + // || ||__"""); + }); + + test('[Example 2.14]', () { + expectYamlLoads("Mark McGwire's year was crippled by a knee injury.", + """ + --- > + Mark McGwire's + year was crippled + by a knee injury."""); + }); + + test('[Example 2.15]', () { + expectYamlLoads( + cleanUpLiteral( + """ + Sammy Sosa completed another fine season with great stats. + + 63 Home Runs + 0.288 Batting Average + + What a year!"""), + """ + > + Sammy Sosa completed another + fine season with great stats. + + 63 Home Runs + 0.288 Batting Average + + What a year!"""); + }); + + test('[Example 2.16]', () { + expectYamlLoads({ + "name": "Mark McGwire", + "accomplishment": "Mark set a major league home run record in 1998.\n", + "stats": "65 Home Runs\n0.278 Batting Average" + }, + """ + name: Mark McGwire + accomplishment: > + Mark set a major league + home run record in 1998. + stats: | + 65 Home Runs + 0.278 Batting Average"""); + }); + + test('[Example 2.17]', () { + expectYamlLoads({ + "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": "|\\-*-/|" + }, + """ + unicode: "Sosa did fine.\\u263A" + control: "\\b1998\\t1999\\t2000\\n" + hex esc: "\\x0d\\x0a is \\r\\n" + + single: '"Howdy!" he cried.' + quoted: ' # Not a ''comment''.' + tie-fighter: '|\\-*-/|'"""); + }); + + test('[Example 2.18]', () { + expectYamlLoads({ + "plain": "This unquoted scalar spans many lines.", + "quoted": "So does this quoted scalar.\n" + }, + ''' + plain: + This unquoted scalar + spans many lines. + + quoted: "So does this + quoted scalar.\\n"'''); + }); + }); + + group('2.4: Tags', () { + test('[Example 2.19]', () { + expectYamlLoads({ + "canonical": 12345, + "decimal": 12345, + "octal": 12, + "hexadecimal": 12 + }, + """ + canonical: 12345 + decimal: +12345 + octal: 0o14 + hexadecimal: 0xC"""); + }); + + test('[Example 2.20]', () { + expectYamlLoads({ + "canonical": 1230.15, + "exponential": 1230.15, + "fixed": 1230.15, + "negative infinity": -infinity, + "not a number": nan + }, + """ + canonical: 1.23015e+3 + exponential: 12.3015e+02 + fixed: 1230.15 + negative infinity: -.inf + not a number: .NaN"""); + }); + + test('[Example 2.21]', () { + var doc = yamlMap({ + "booleans": [true, false], + "string": "012345" + }); + doc[null] = null; + expectYamlLoads(doc, + """ + null: + booleans: [ true, false ] + string: '012345'"""); + }); + + // Examples 2.22 through 2.26 test custom tag URIs, which this + // implementation currently doesn't plan to support. + }); + + group('2.5 Full Length Example', () { + // Example 2.27 tests custom tag URIs, which this implementation currently + // doesn't plan to support. + + test('[Example 2.28]', () { + expectYamlStreamLoads([ + { + "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." + }, + { + "DateTime": "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"} + ] + } + ], + """ + --- + 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. + --- + DateTime: 2001-11-23 15:03:17 -5 + User: ed + Fatal: + Unknown variable "bar" + Stack: + - file: TopClass.py + line: 23 + code: | + x = MoreObject("345\\n") + - file: MoreClass.py + line: 58 + code: |- + foo = bar"""); + }); + }); + + // Chapter 3 just talks about the structure of loading and dumping Yaml. + // Chapter 4 explains conventions used in the spec. + + // Chapter 5: Characters + group('5.1: Character Set', () { + expectAllowsCharacter(int charCode) { + var char = new String.fromCharCodes([charCode]); + expectYamlLoads('The character "$char" is allowed', + 'The character "$char" is allowed'); + } + + expectAllowsQuotedCharacter(int charCode) { + var char = new String.fromCharCodes([charCode]); + expectYamlLoads("The character '$char' is allowed", + '"The character \'$char\' is allowed"'); + } + + expectDisallowsCharacter(int charCode) { + var char = new String.fromCharCodes([charCode]); + Expect.throws(() => loadYaml('The character "$char" is disallowed')); + } + + test("doesn't include C0 control characters", () { + expectDisallowsCharacter(0x0); + expectDisallowsCharacter(0x8); + expectDisallowsCharacter(0x1F); + }); + + test("includes TAB", () => expectAllowsCharacter(0x9)); + test("doesn't include DEL", () => expectDisallowsCharacter(0x7F)); + + test("doesn't include C1 control characters", () { + expectDisallowsCharacter(0x80); + expectDisallowsCharacter(0x8A); + expectDisallowsCharacter(0x9F); + }); + + test("includes NEL", () => expectAllowsCharacter(0x85)); + + group("within quoted strings", () { + test("includes DEL", () => expectAllowsQuotedCharacter(0x7F)); + test("includes C1 control characters", () { + expectAllowsQuotedCharacter(0x80); + expectAllowsQuotedCharacter(0x8A); + expectAllowsQuotedCharacter(0x9F); + }); + }); + }); + + // Skipping section 5.2 (Character Encodings), since at the moment the module + // assumes that the client code is providing it with a string of the proper + // encoding. + + group('5.3: Indicator Characters', () { + test('[Example 5.3]', () { + expectYamlLoads({ + 'sequence': ['one', 'two'], + 'mapping': {'sky': 'blue', 'sea': 'green'} + }, + """ + sequence: + - one + - two + mapping: + ? sky + : blue + sea : green"""); + }); + + test('[Example 5.4]', () { + expectYamlLoads({ + 'sequence': ['one', 'two'], + 'mapping': {'sky': 'blue', 'sea': 'green'} + }, + """ + sequence: [ one, two, ] + mapping: { sky: blue, sea: green }"""); + }); + + test('[Example 5.5]', () => expectYamlLoads(null, "# Comment only.")); + + // Skipping 5.6 because it uses an undefined tag. + + test('[Example 5.7]', () { + expectYamlLoads({ + 'literal': "some\ntext\n", + 'folded': "some text\n" + }, + """ + literal: | + some + text + folded: > + some + text + """); + }); + + test('[Example 5.8]', () { + expectYamlLoads({ + 'single': "text", + 'double': "text" + }, + """ + single: 'text' + double: "text" + """); + }); + + // test('[Example 5.9]', () { + // expectYamlLoads("text", + // """ + // %YAML 1.2 + // --- text"""); + // }); + + test('[Example 5.10]', () { + Expect.throws(() => loadYaml("commercial-at: @text")); + Expect.throws(() => loadYaml("commercial-at: `text")); + }); + }); + + group('5.4: Line Break Characters', () { + group('include', () { + test('\\n', () => expectYamlLoads([1, 2], indentLiteral("- 1\n- 2"))); + test('\\r', () => expectYamlLoads([1, 2], "- 1\r- 2")); + }); + + group('do not include', () { + test('form feed', () => Expect.throws(() => loadYaml("- 1\x0C- 2"))); + test('NEL', () => expectYamlLoads(["1\x85- 2"], "- 1\x85- 2")); + test('0x2028', () => expectYamlLoads(["1\u2028- 2"], "- 1\u2028- 2")); + test('0x2029', () => expectYamlLoads(["1\u2029- 2"], "- 1\u2029- 2")); + }); + + group('in a scalar context must be normalized', () { + test("from \\r to \\n", () => + expectYamlLoads(["foo\nbar"], indentLiteral('- |\n foo\r bar'))); + test("from \\r\\n to \\n", () => + expectYamlLoads(["foo\nbar"], indentLiteral('- |\n foo\r\n bar'))); + }); + + test('[Example 5.11]', () { + expectYamlLoads( + cleanUpLiteral(""" + Line break (no glyph) + Line break (glyphed)"""), + """ + | + Line break (no glyph) + Line break (glyphed)"""); + }); + }); + + group('5.5: White Space Characters', () { + test('[Example 5.12]', () { + expectYamlLoads({ + "quoted": "Quoted \t", + "block": 'void main() {\n\tprintf("Hello, world!\\n");\n}\n' + }, + """ + # Tabs and spaces + quoted: "Quoted \t" + block:\t| + void main() { + \tprintf("Hello, world!\\n"); + } + """); + }); + }); + + group('5.7: Escaped Characters', () { + test('[Example 5.13]', () { + expectYamlLoads( + "Fun with \x5C " + "\x22 \x07 \x08 \x1B \x0C " + "\x0A \x0D \x09 \x0B \x00 " + "\x20 \xA0 \x85 \u2028 \u2029 " + "A A A", + ''' + "Fun with \\\\ + \\" \\a \\b \\e \\f \\ + \\n \\r \\t \\v \\0 \\ + \\ \\_ \\N \\L \\P \\ + \\x41 \\u0041 \\U00000041"'''); + }); + + test('[Example 5.14]', () { + Expect.throws(() => loadYaml('Bad escape: "\\c"')); + Expect.throws(() => loadYaml('Bad escape: "\\xq-"')); + }); + }); + + // Chapter 6: Basic Structures + group('6.1: Indentation Spaces', () { + test('may not include TAB characters', () { + Expect.throws(() => loadYaml(cleanUpLiteral( + """ + - + \t- foo + \t- bar"""))); + }); + + test('must be the same for all sibling nodes', () { + Expect.throws(() => loadYaml(cleanUpLiteral( + """ + - + - foo + - bar"""))); + }); + + test('may be different for the children of sibling nodes', () { + expectYamlLoads([["foo"], ["bar"]], + """ + - + - foo + - + - bar"""); + }); + + test('[Example 6.1]', () { + expectYamlLoads({ + "Not indented": { + "By one space": "By four\n spaces\n", + "Flow style": [ + "By two", + "Also by two", + "Still by two" + ] + } + }, + """ + # Leading comment line spaces are + # neither content nor indentation. + + Not indented: + By one space: | + By four + spaces + Flow style: [ # Leading spaces + By two, # in flow style + Also by two, # are neither + \tStill by two # content nor + ] # indentation."""); + }); + + test('[Example 6.2]', () { + expectYamlLoads({'a': ['b', ['c', 'd']]}, + """ + ? a + : -\tb + - -\tc + - d"""); + }); + }); + + group('6.2: Separation Spaces', () { + test('[Example 6.3]', () { + expectYamlLoads([{'foo': 'bar'}, ['baz', 'baz']], + """ + - foo:\t bar + - - baz + -\tbaz"""); + }); + }); + + group('6.3: Line Prefixes', () { + test('[Example 6.4]', () { + expectYamlLoads({ + "plain": "text lines", + "quoted": "text lines", + "block": "text\n \tlines\n" + }, + """ + plain: text + lines + quoted: "text + \tlines" + block: | + text + \tlines + """); + }); + }); + + group('6.4: Empty Lines', () { + test('[Example 6.5]', () { + expectYamlLoads({ + "Folding": "Empty line\nas a line feed", + "Chomping": "Clipped empty lines\n", + }, + """ + Folding: + "Empty line + \t + as a line feed" + Chomping: | + Clipped empty lines + """); + }); + }); + + group('6.5: Line Folding', () { + test('[Example 6.6]', () { + expectYamlLoads("trimmed\n\n\nas space", + """ + >- + trimmed + + + + as + space + """); + }); + + test('[Example 6.7]', () { + expectYamlLoads("foo \n\n\t bar\n\nbaz\n", + """ + > + foo + + \t bar + + baz + """); + }); + + test('[Example 6.8]', () { + expectYamlLoads(" foo\nbar\nbaz ", + ''' + " + foo + + \t bar + + baz + "'''); + }); + }); + + group('6.6: Comments', () { + test('must be separated from other tokens by white space characters', () { + expectYamlLoads("foo#bar", "foo#bar"); + expectYamlLoads("foo:#bar", "foo:#bar"); + expectYamlLoads("-#bar", "-#bar"); + }); + + test('[Example 6.9]', () { + expectYamlLoads({'key': 'value'}, + """ + key: # Comment + value"""); + }); + + group('outside of scalar content', () { + test('may appear on a line of their own', () { + expectYamlLoads([1, 2], + """ + - 1 + # Comment + - 2"""); + }); + + test('are independent of indentation level', () { + expectYamlLoads([[1, 2]], + """ + - + - 1 + # Comment + - 2"""); + }); + + test('include lines containing only white space characters', () { + expectYamlLoads([1, 2], + """ + - 1 + \t + - 2"""); + }); + }); + + group('within scalar content', () { + test('may not appear on a line of their own', () { + expectYamlLoads(["foo\n# not comment\nbar\n"], + """ + - | + foo + # not comment + bar + """); + }); + + test("don't include lines containing only white space characters", () { + expectYamlLoads(["foo\n \t \nbar\n"], + """ + - | + foo + \t + bar + """); + }); + }); + + test('[Example 6.10]', () { + expectYamlLoads(null, + """ + # Comment + + """); + }); + + test('[Example 6.11]', () { + expectYamlLoads({'key': 'value'}, + """ + key: # Comment + # lines + value + """); + }); + + group('ending a block scalar header', () { + test('may not be followed by additional comment lines', () { + expectYamlLoads(["# not comment\nfoo\n"], + """ + - | # comment + # not comment + foo + """); + }); + }); + }); + + group('6.7: Separation Lines', () { + test('may not be used within implicit keys', () { + Expect.throws(() => loadYaml(cleanUpLiteral( + """ + [1, + 2]: 3"""))); + }); + + test('[Example 6.12]', () { + var doc = yamlMap(); + doc[{'first': 'Sammy', 'last': 'Sosa'}] = { + 'hr': 65, + 'avg': 0.278 + }; + expectYamlLoads(doc, + """ + { first: Sammy, last: Sosa }: + # Statistics: + hr: # Home runs + 65 + avg: # Average + 0.278"""); + }); + }); + + group('6.8: Directives', () { + // // TODO(nweiz): assert that this produces a warning + // test('[Example 6.13]', () { + // expectYamlLoads("foo", + // ''' + // %FOO bar baz # Should be ignored + // # with a warning. + // --- "foo"'''); + // }); + + // // TODO(nweiz): assert that this produces a warning + // test('[Example 6.14]', () { + // expectYamlLoads("foo", + // ''' + // %YAML 1.3 # Attempt parsing + // # with a warning + // --- + // "foo"'''); + // }); + + // test('[Example 6.15]', () { + // Expect.throws(() => loadYaml(cleanUpLiteral( + // """ + // %YAML 1.2 + // %YAML 1.1 + // foo"""))); + // }); + + // test('[Example 6.16]', () { + // expectYamlLoads("foo", + // ''' + // %TAG !yaml! tag:yaml.org,2002: + // --- + // !yaml!str "foo"'''); + // }); + + // test('[Example 6.17]', () { + // Expect.throws(() => loadYaml(cleanUpLiteral( + // """ + // %TAG ! !foo + // %TAG ! !foo + // bar"""))); + // }); + + // Examples 6.18 through 6.22 test custom tag URIs, which this + // implementation currently doesn't plan to support. + }); + + group('6.9: Node Properties', () { + // test('may be specified in any order', () { + // expectYamlLoads(["foo", "bar"], + // """ + // - !!str &a1 foo + // - &a2 !!str bar"""); + // }); + + // test('[Example 6.23]', () { + // expectYamlLoads({ + // "foo": "bar", + // "baz": "foo" + // }, + // ''' + // !!str &a1 "foo": + // !!str bar + // &a2 baz : *a1'''); + // }); + + // // Example 6.24 tests custom tag URIs, which this implementation currently + // // doesn't plan to support. + + // test('[Example 6.25]', () { + // Expect.throws(() => loadYaml("- !<!> foo")); + // Expect.throws(() => loadYaml("- !<\$:?> foo")); + // }); + + // // Examples 6.26 and 6.27 test custom tag URIs, which this implementation + // // currently doesn't plan to support. + + // test('[Example 6.28]', () { + // expectYamlLoads(["12", 12, "12"], + // ''' + // # Assuming conventional resolution: + // - "12" + // - 12 + // - ! 12'''); + // }); + + // test('[Example 6.29]', () { + // expectYamlLoads({ + // "First occurrence": "Value", + // "Second occurrence": "anchor" + // }, + // """ + // First occurrence: &anchor Value + // Second occurrence: *anchor"""); + // }); + }); + + // Chapter 7: Flow Styles + group('7.1: Alias Nodes', () { + // test("must not use an anchor that doesn't previously occur", () { + // Expect.throws(() => loadYaml(cleanUpLiteral( + // """ + // - *anchor + // - &anchor foo""")); + // }); + + // test("don't have to exist for a given anchor node", () { + // expectYamlLoads(["foo"], "- &anchor foo"); + // }); + + // group('must not specify', () { + // test('tag properties', () => Expect.throws(() => loadYaml(cleanUpLiteral( + // """ + // - &anchor foo + // - !str *anchor"""))); + + // test('anchor properties', () => Expect.throws( + // () => loadYaml(cleanUpLiteral( + // """ + // - &anchor foo + // - &anchor2 *anchor"""))); + + // test('content', () => Expect.throws(() => loadYaml(cleanUpLiteral( + // """ + // - &anchor foo + // - *anchor bar"""))); + // }); + + // test('must preserve structural equality', () { + // var doc = loadYaml(cleanUpLiteral( + // """ + // anchor: &anchor [a, b, c] + // alias: *anchor"""); + // var anchorList = doc['anchor']; + // var aliasList = doc['alias']; + // Expect.isTrue(anchorList === aliasList); + // anchorList.add('d'); + // Expect.listEquals(['a', 'b', 'c', 'd'], aliasList); + + // doc = loadYaml(cleanUpLiteral( + // """ + // ? &anchor [a, b, c] + // : ? *anchor + // : bar"""); + // anchorList = doc.keys[0]; + // aliasList = doc[['a', 'b', 'c']].keys[0]; + // Expect.isTrue(anchorList === aliasList); + // anchorList.add('d'); + // Expect.listEquals(['a', 'b', 'c', 'd'], aliasList); + // }); + + // test('[Example 7.1]', () { + // expectYamlLoads({ + // "First occurence": "Foo", + // "Second occurence": "Foo", + // "Override anchor": "Bar", + // "Reuse anchor": "Bar", + // }, + // """ + // First occurrence: &anchor Foo + // Second occurrence: *anchor + // Override anchor: &anchor Bar + // Reuse anchor: *anchor"""); + // }); + }); + + group('7.2: Empty Nodes', () { + // test('[Example 7.2]', () { + // expectYamlLoads({ + // "foo": "", + // "": "bar" + // }, + // """ + // { + // foo : !!str, + // !!str : bar, + // }"""); + // }); + + test('[Example 7.3]', () { + var doc = yamlMap({"foo": null}); + doc[null] = "bar"; + expectYamlLoads(doc, + """ + { + ? foo :, + : bar, + }"""); + }); + }); + + group('7.3: Flow Scalar Styles', () { + test('[Example 7.4]', () { + expectYamlLoads({ + "implicit block key": [{"implicit flow key": "value"}] + }, + ''' + "implicit block key" : [ + "implicit flow key" : value, + ]'''); + }); + + test('[Example 7.5]', () { + expectYamlLoads( + "folded to a space,\nto a line feed, or \t \tnon-content", + ''' + "folded + to a space,\t + + to a line feed, or \t\\ + \\ \tnon-content"'''); + }); + + test('[Example 7.6]', () { + expectYamlLoads(" 1st non-empty\n2nd non-empty 3rd non-empty ", + ''' + " 1st non-empty + + 2nd non-empty + \t3rd non-empty "'''); + }); + + test('[Example 7.7]', () { + expectYamlLoads("here's to \"quotes\"", "'here''s to \"quotes\"'"); + }); + + test('[Example 7.8]', () { + expectYamlLoads({ + "implicit block key": [{"implicit flow key": "value"}] + }, + """ + 'implicit block key' : [ + 'implicit flow key' : value, + ]"""); + }); + + test('[Example 7.9]', () { + expectYamlLoads(" 1st non-empty\n2nd non-empty 3rd non-empty ", + """ + ' 1st non-empty + + 2nd non-empty + \t3rd non-empty '"""); + }); + + test('[Example 7.10]', () { + expectYamlLoads([ + "::vector", ": - ()", "Up, up, and away!", -123, + "http://example.com/foo#bar", + [ + "::vector", ": - ()", "Up, up, and away!", -123, + "http://example.com/foo#bar" + ] + ], + ''' + # Outside flow collection: + - ::vector + - ": - ()" + - Up, up, and away! + - -123 + - http://example.com/foo#bar + # Inside flow collection: + - [ ::vector, + ": - ()", + "Up, up, and away!", + -123, + http://example.com/foo#bar ]'''); + }); + + test('[Example 7.11]', () { + expectYamlLoads({ + "implicit block key": [{"implicit flow key": "value"}] + }, + """ + implicit block key : [ + implicit flow key : value, + ]"""); + }); + + test('[Example 7.12]', () { + expectYamlLoads("1st non-empty\n2nd non-empty 3rd non-empty", + """ + 1st non-empty + + 2nd non-empty + \t3rd non-empty"""); + }); + }); + + group('7.4: Flow Collection Styles', () { + test('[Example 7.13]', () { + expectYamlLoads([ + ['one', 'two'], + ['three', 'four'] + ], + """ + - [ one, two, ] + - [three ,four]"""); + }); + + test('[Example 7.14]', () { + expectYamlLoads([ + "double quoted", "single quoted", "plain text", ["nested"], + {"single": "pair"} + ], + """ + [ + "double + quoted", 'single + quoted', + plain + text, [ nested ], + single: pair, + ]"""); + }); + + test('[Example 7.15]', () { + expectYamlLoads([ + {"one": "two", "three": "four"}, + {"five": "six", "seven": "eight"}, + ], + """ + - { one : two , three: four , } + - {five: six,seven : eight}"""); + }); + + test('[Example 7.16]', () { + var doc = yamlMap({ + "explicit": "entry", + "implicit": "entry" + }); + doc[null] = null; + expectYamlLoads(doc, + """ + { + ? explicit: entry, + implicit: entry, + ? + }"""); + }); + + test('[Example 7.17]', () { + var doc = yamlMap({ + "unquoted": "separate", + "http://foo.com": null, + "omitted value": null + }); + doc[null] = "omitted key"; + expectYamlLoads(doc, + ''' + { + unquoted : "separate", + http://foo.com, + omitted value:, + : omitted key, + }'''); + }); + + test('[Example 7.18]', () { + expectYamlLoads({ + "adjacent": "value", + "readable": "value", + "empty": null + }, + ''' + { + "adjacent":value, + "readable": value, + "empty": + }'''); + }); + + test('[Example 7.19]', () { + expectYamlLoads([{"foo": "bar"}], + """ + [ + foo: bar + ]"""); + }); + + test('[Example 7.20]', () { + expectYamlLoads([{"foo bar": "baz"}], + """ + [ + ? foo + bar : baz + ]"""); + }); + + test('[Example 7.21]', () { + var el1 = yamlMap(); + el1[null] = "empty key entry"; + + var el2 = yamlMap(); + el2[{"JSON": "like"}] = "adjacent"; + + expectYamlLoads([[{"YAML": "separate"}], [el1], [el2]], + """ + - [ YAML : separate ] + - [ : empty key entry ] + - [ {JSON: like}:adjacent ]"""); + }); + + test('[Example 7.22]', () { + Expect.throws(() => loadYaml(cleanUpLiteral( + """ + [ foo + bar: invalid ]"""))); + + // TODO(nweiz): enable this when we throw an error for long keys + // var dotList = []; + // dotList.insertRange(0, 1024, ' '); + // var dots = Strings.join(dotList, ''); + // Expect.throws(() => loadYaml('[ "foo...$dots...bar": invalid ]')); + }); + }); + + group('7.5: Flow Nodes', () { + test('[Example 7.23]', () { + expectYamlLoads([["a", "b"], {"a": "b"}, "a", "b", "c"], + """ + - [ a, b ] + - { a: b } + - "a" + - 'b' + - c"""); + }); + + // test('[Example 7.24]', () { + // expectYamlLoads(["a", "b", "c", "c", ""], + // """ + // - !!str "a" + // - 'b' + // - &anchor "c" + // - *anchor + // - !!str"""); + // }); + }); + + // Chapter 8: Block Styles + group('8.1: Block Scalar Styles', () { + test('[Example 8.1]', () { + expectYamlLoads(["literal\n", " folded\n", "keep\n\n", " strip"], + """ + - | # Empty header + literal + - >1 # Indentation indicator + folded + - |+ # Chomping indicator + keep + + - >1- # Both indicators + strip"""); + }); + + test('[Example 8.2]', () { + // Note: in the spec, the fourth element in this array is listed as + // "\t detected\n", not "\t\ndetected\n". However, I'm reasonably + // confident that "\t\ndetected\n" is correct when parsed according to the + // rest of the spec. + expectYamlLoads([ + "detected\n", + "\n\n# detected\n", + " explicit\n", + "\t\ndetected\n" + ], + """ + - | + detected + - > + + + # detected + - |1 + explicit + - > + \t + detected + """); + }); + + test('[Example 8.3]', () { + Expect.throws(() => loadYaml(cleanUpLiteral( + """ + - | + + text"""))); + + Expect.throws(() => loadYaml(cleanUpLiteral( + """ + - > + text + text"""))); + + Expect.throws(() => loadYaml(cleanUpLiteral( + """ + - |2 + text"""))); + }); + + test('[Example 8.4]', () { + expectYamlLoads({"strip": "text", "clip": "text\n", "keep": "text\n"}, + """ + strip: |- + text + clip: | + text + keep: |+ + text + """); + }); + + test('[Example 8.5]', () { + // This example in the spec only includes a single newline in the "keep" + // value, but as far as I can tell that's not how it's supposed to be + // parsed according to the rest of the spec. + expectYamlLoads({ + "strip": "# text", + "clip": "# text\n", + "keep": "# text\n\n" + }, + """ + # Strip + # Comments: + strip: |- + # text + + # Clip + # comments: + + clip: | + # text + + # Keep + # comments: + + keep: |+ + # text + + # Trail + # comments. + """); + }); + + test('[Example 8.6]', () { + expectYamlLoads({"strip": "", "clip": "", "keep": "\n"}, + """ + strip: >- + + clip: > + + keep: |+ + + """); + }); + + test('[Example 8.7]', () { + expectYamlLoads("literal\n\ttext\n", + """ + | + literal + \ttext + """); + }); + + test('[Example 8.8]', () { + expectYamlLoads("\n\nliteral\n \n\ntext\n", + """ + | + + + literal + + + text + + # Comment"""); + }); + + test('[Example 8.9]', () { + expectYamlLoads("folded text\n", + """ + > + folded + text + """); + }); + + test('[Example 8.10]', () { + expectYamlLoads( + cleanUpLiteral(""" + + folded line + next line + * bullet + + * list + * lines + + last line + """), + """ + > + + folded + line + + next + line + * bullet + + * list + * lines + + last + line + + # Comment"""); + }); + + // Examples 8.11 through 8.13 are duplicates of 8.10. + }); + + group('8.2: Block Collection Styles', () { + test('[Example 8.14]', () { + expectYamlLoads({"block sequence": ["one", {"two": "three"}]}, + """ + block sequence: + - one + - two : three"""); + }); + + test('[Example 8.15]', () { + expectYamlLoads([ + null, "block node\n", ["one", "two"], {"one": "two"} + ], + """ + - # Empty + - | + block node + - - one # Compact + - two # sequence + - one: two # Compact mapping"""); + }); + + test('[Example 8.16]', () { + expectYamlLoads({"block mapping": {"key": "value"}}, + """ + block mapping: + key: value"""); + }); + + test('[Example 8.17]', () { + expectYamlLoads({ + "explicit key": null, + "block key\n": ["one", "two"] + }, + """ + ? explicit key # Empty value + ? | + block key + : - one # Explicit compact + - two # block value"""); + }); + + test('[Example 8.18]', () { + var doc = yamlMap({ + 'plain key': 'in-line value', + "quoted key": ["entry"] + }); + doc[null] = null; + expectYamlLoads(doc, + ''' + plain key: in-line value + : # Both empty + "quoted key": + - entry'''); + }); + + test('[Example 8.19]', () { + var el = yamlMap(); + el[{'earth': 'blue'}] = {'moon': 'white'}; + expectYamlLoads([{'sun': 'yellow'}, el], + """ + - sun: yellow + - ? earth: blue + : moon: white"""); + }); + + // test('[Example 8.20]', () { + // expectYamlLoads(["flow in block", "Block scalar\n", {"foo": "bar"}], + // ''' + // - + // "flow in block" + // - > + // Block scalar + // - !!map # Block collection + // foo : bar'''); + // }); + + // test('[Example 8.21]', () { + // expectYamlLoads({"literal": "value", "folded": "value"}, + // """ + // literal: |2 + // value + // folded: + // !!str + // >1 + // value"""); + // }); + + // test('[Example 8.22]', () { + // expectYamlLoads({ + // "sequence": ["entry", ["nested"]], + // "mapping": {"foo": "bar"} + // }, + // """ + // sequence: !!seq + // - entry + // - !!seq + // - nested + // mapping: !!map + // foo: bar"""); + // }); + }); + + // Chapter 9: YAML Character Stream + group('9.1: Documents', () { + // Example 9.1 tests the use of a BOM, which this implementation currently + // doesn't plan to support. + + // test('[Example 9.2]', () { + // expectYamlLoads("Document", + // """ + // %YAML 1.2 + // --- + // Document + // ... # Suffix"""); + // }); + + test('[Example 9.3]', () { + // The spec example indicates that the comment after "%!PS-Adobe-2.0" + // should be stripped, which would imply that that line is not part of the + // literal defined by the "|". The rest of the spec is ambiguous on this + // point; the allowable indentation for non-indented literal content is + // not clearly explained. However, if both the "|" and the text were + // indented the same amount, the text would be part of the literal, which + // implies that the spec's parse of this document is incorrect. + expectYamlStreamLoads( + ["Bare document", "%!PS-Adobe-2.0 # Not the first line\n"], + """ + Bare + document + ... + # No document + ... + | + %!PS-Adobe-2.0 # Not the first line + """); + }); + + test('[Example 9.4]', () { + expectYamlStreamLoads([{"matches %": 20}, null], + """ + --- + { matches + % : 20 } + ... + --- + # Empty + ..."""); + }); + + // test('[Example 9.5]', () { + // expectYamlStreamLoads(["%!PS-Adobe-2.0\n", null], + // """ + // %YAML 1.2 + // --- | + // %!PS-Adobe-2.0 + // ... + // %YAML1.2 + // --- + // # Empty + // ..."""); + // }); + + // test('[Example 9.6]', () { + // expectYamlStreamLoads(["Document", null, {"matches %": 20}], + // """ + // Document + // --- + // # Empty + // ... + // %YAML 1.2 + // --- + // matches %: 20"""); + // }); + }); + + // Chapter 10: Recommended Schemas + group('10.1: Failsafe Schema', () { + // test('[Example 10.1]', () { + // expectYamlStreamLoads({ + // "Block style": { + // "Clark": "Evans", + // "Ingy": "döt Net", + // "Oren": "Ben-Kiki" + // }, + // "Flow style": { + // "Clark": "Evans", + // "Ingy": "döt Net", + // "Oren": "Ben-Kiki" + // } + // }, + // """ + // Block style: !!map + // Clark : Evans + // Ingy : döt Net + // Oren : Ben-Kiki + + // Flow style: !!map { Clark: Evans, Ingy: döt Net, Oren: Ben-Kiki }"""); + // }); + + // test('[Example 10.2]', () { + // expectYamlStreamLoads({ + // "Block style": ["Clark Evans", "Ingy döt Net", "Oren Ben-Kiki"], + // "Flow style": ["Clark Evans", "Ingy döt Net", "Oren Ben-Kiki"] + // }, + // """ + // Block style: !!seq + // - Clark Evans + // - Ingy döt Net + // - Oren Ben-Kiki + + // Flow style: !!seq [ Clark Evans, Ingy döt Net, Oren Ben-Kiki ]"""); + // }); + + // test('[Example 10.3]', () { + // expectYamlStreamLoads({ + // "Block style": "String: just a theory.", + // "Flow style": "String: just a theory." + // }, + // ''' + // Block style: !!str |- + // String: just a theory. + + // Flow style: !!str "String: just a theory."'''); + // }); + }); + + group('10.2: JSON Schema', () { + // test('[Example 10.4]', () { + // var doc = yamlMap({"key with null value": null}); + // doc[null] = "value for null key"; + // expectYamlStreamLoads(doc, + // """ + // !!null null: value for null key + // key with null value: !!null null"""); + // }); + + // test('[Example 10.5]', () { + // expectYamlStreamLoads({ + // "YAML is a superset of JSON": true, + // "Pluto is a planet": false + // }, + // """ + // YAML is a superset of JSON: !!bool true + // Pluto is a planet: !!bool false"""); + // }); + + // test('[Example 10.6]', () { + // expectYamlStreamLoads({ + // "negative": -12, + // "zero": 0, + // "positive": 34 + // }, + // """ + // negative: !!int -12 + // zero: !!int 0 + // positive: !!int 34"""); + // }); + + // test('[Example 10.7]', () { + // expectYamlStreamLoads({ + // "negative": -1, + // "zero": 0, + // "positive": 23000, + // "infinity": infinity, + // "not a number": nan + // }, + // """ + // negative: !!float -1 + // zero: !!float 0 + // positive: !!float 2.3e4 + // infinity: !!float .inf + // not a number: !!float .nan"""); + // }); + + // test('[Example 10.8]', () { + // expectYamlStreamLoads({ + // "A null": null, + // "Booleans": [true, false], + // "Integers": [0, -0, 3, -19], + // "Floats": [0, 0, 12000, -200000], + // // Despite being invalid in the JSON schema, these values are valid in + // // the core schema which this implementation supports. + // "Invalid": [ true, null, 7, 0x3A, 12.3] + // }, + // """ + // A null: null + // Booleans: [ true, false ] + // Integers: [ 0, -0, 3, -19 ] + // Floats: [ 0., -0.0, 12e03, -2E+05 ] + // Invalid: [ True, Null, 0o7, 0x3A, +12.3 ]"""); + // }); + }); + + group('10.3: Core Schema', () { + test('[Example 10.9]', () { + expectYamlLoads({ + "A null": null, + "Also a null": null, + "Not a null": "", + "Booleans": [true, true, false, false], + "Integers": [0, 7, 0x3A, -19], + "Floats": [0, 0, 0.5, 12000, -200000], + "Also floats": [infinity, -infinity, infinity, nan] + }, + ''' + A null: null + Also a null: # Empty + Not a null: "" + Booleans: [ true, True, false, FALSE ] + Integers: [ 0, 0o7, 0x3A, -19 ] + Floats: [ 0., -0.0, .5, +12e03, -2E+05 ] + Also floats: [ .inf, -.Inf, +.INF, .NAN ]'''); + }); + }); +}