| OLD | NEW |
| (Empty) |
| 1 // Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file | |
| 2 // for details. All rights reserved. Use of this source code is governed by a | |
| 3 // BSD-style license that can be found in the LICENSE file. | |
| 4 | |
| 5 library yaml.loader; | |
| 6 | |
| 7 import 'package:charcode/ascii.dart'; | |
| 8 import 'package:source_span/source_span.dart'; | |
| 9 | |
| 10 import 'equality.dart'; | |
| 11 import 'event.dart'; | |
| 12 import 'parser.dart'; | |
| 13 import 'yaml_document.dart'; | |
| 14 import 'yaml_exception.dart'; | |
| 15 import 'yaml_node.dart'; | |
| 16 | |
| 17 /// A loader that reads [Event]s emitted by a [Parser] and emits | |
| 18 /// [YamlDocument]s. | |
| 19 /// | |
| 20 /// This is based on the libyaml loader, available at | |
| 21 /// https://github.com/yaml/libyaml/blob/master/src/loader.c. The license for | |
| 22 /// that is available in ../../libyaml-license.txt. | |
| 23 class Loader { | |
| 24 /// The underlying [Parser] that generates [Event]s. | |
| 25 final Parser _parser; | |
| 26 | |
| 27 /// Aliases by the alias name. | |
| 28 final _aliases = new Map<String, YamlNode>(); | |
| 29 | |
| 30 /// The span of the entire stream emitted so far. | |
| 31 FileSpan get span => _span; | |
| 32 FileSpan _span; | |
| 33 | |
| 34 /// Creates a loader that loads [source]. | |
| 35 /// | |
| 36 /// [sourceUrl] can be a String or a [Uri]. | |
| 37 Loader(String source, {sourceUrl}) | |
| 38 : _parser = new Parser(source, sourceUrl: sourceUrl) { | |
| 39 var event = _parser.parse(); | |
| 40 _span = event.span; | |
| 41 assert(event.type == EventType.STREAM_START); | |
| 42 } | |
| 43 | |
| 44 /// Loads the next document from the stream. | |
| 45 /// | |
| 46 /// If there are no more documents, returns `null`. | |
| 47 YamlDocument load() { | |
| 48 if (_parser.isDone) return null; | |
| 49 | |
| 50 var event = _parser.parse(); | |
| 51 if (event.type == EventType.STREAM_END) { | |
| 52 _span = _span.expand(event.span); | |
| 53 return null; | |
| 54 } | |
| 55 | |
| 56 var document = _loadDocument(event); | |
| 57 _span = _span.expand(document.span); | |
| 58 _aliases.clear(); | |
| 59 return document; | |
| 60 } | |
| 61 | |
| 62 /// Composes a document object. | |
| 63 YamlDocument _loadDocument(DocumentStartEvent firstEvent) { | |
| 64 var contents = _loadNode(_parser.parse()); | |
| 65 | |
| 66 var lastEvent = _parser.parse(); | |
| 67 assert(lastEvent.type == EventType.DOCUMENT_END); | |
| 68 | |
| 69 return new YamlDocument.internal( | |
| 70 contents, | |
| 71 firstEvent.span.expand(lastEvent.span), | |
| 72 firstEvent.versionDirective, | |
| 73 firstEvent.tagDirectives, | |
| 74 startImplicit: firstEvent.isImplicit, | |
| 75 endImplicit: lastEvent.isImplicit); | |
| 76 } | |
| 77 | |
| 78 /// Composes a node. | |
| 79 YamlNode _loadNode(Event firstEvent) { | |
| 80 switch (firstEvent.type) { | |
| 81 case EventType.ALIAS: return _loadAlias(firstEvent); | |
| 82 case EventType.SCALAR: return _loadScalar(firstEvent); | |
| 83 case EventType.SEQUENCE_START: return _loadSequence(firstEvent); | |
| 84 case EventType.MAPPING_START: return _loadMapping(firstEvent); | |
| 85 default: throw "Unreachable"; | |
| 86 } | |
| 87 } | |
| 88 | |
| 89 /// Registers an anchor. | |
| 90 void _registerAnchor(String anchor, YamlNode node) { | |
| 91 if (anchor == null) return; | |
| 92 | |
| 93 // libyaml throws an error for duplicate anchors, but example 7.1 makes it | |
| 94 // clear that they should be overridden: | |
| 95 // http://yaml.org/spec/1.2/spec.html#id2786448. | |
| 96 | |
| 97 _aliases[anchor] = node; | |
| 98 } | |
| 99 | |
| 100 /// Composes a node corresponding to an alias. | |
| 101 YamlNode _loadAlias(AliasEvent event) { | |
| 102 var alias = _aliases[event.name]; | |
| 103 if (alias != null) return alias; | |
| 104 | |
| 105 throw new YamlException("Undefined alias.", event.span); | |
| 106 } | |
| 107 | |
| 108 /// Composes a scalar node. | |
| 109 YamlNode _loadScalar(ScalarEvent scalar) { | |
| 110 var node; | |
| 111 if (scalar.tag == "!") { | |
| 112 node = new YamlScalar.internal(scalar.value, scalar); | |
| 113 } else if (scalar.tag != null) { | |
| 114 node = _parseByTag(scalar); | |
| 115 } else { | |
| 116 node = _parseScalar(scalar); | |
| 117 } | |
| 118 | |
| 119 _registerAnchor(scalar.anchor, node); | |
| 120 return node; | |
| 121 } | |
| 122 | |
| 123 /// Composes a sequence node. | |
| 124 YamlNode _loadSequence(SequenceStartEvent firstEvent) { | |
| 125 if (firstEvent.tag != "!" && firstEvent.tag != null && | |
| 126 firstEvent.tag != "tag:yaml.org,2002:seq") { | |
| 127 throw new YamlException("Invalid tag for sequence.", firstEvent.span); | |
| 128 } | |
| 129 | |
| 130 var children = []; | |
| 131 var node = new YamlList.internal( | |
| 132 children, firstEvent.span, firstEvent.style); | |
| 133 _registerAnchor(firstEvent.anchor, node); | |
| 134 | |
| 135 var event = _parser.parse(); | |
| 136 while (event.type != EventType.SEQUENCE_END) { | |
| 137 children.add(_loadNode(event)); | |
| 138 event = _parser.parse(); | |
| 139 } | |
| 140 | |
| 141 setSpan(node, firstEvent.span.expand(event.span)); | |
| 142 return node; | |
| 143 } | |
| 144 | |
| 145 /// Composes a mapping node. | |
| 146 YamlNode _loadMapping(MappingStartEvent firstEvent) { | |
| 147 if (firstEvent.tag != "!" && firstEvent.tag != null && | |
| 148 firstEvent.tag != "tag:yaml.org,2002:map") { | |
| 149 throw new YamlException("Invalid tag for mapping.", firstEvent.span); | |
| 150 } | |
| 151 | |
| 152 var children = deepEqualsMap(); | |
| 153 var node = new YamlMap.internal( | |
| 154 children, firstEvent.span, firstEvent.style); | |
| 155 _registerAnchor(firstEvent.anchor, node); | |
| 156 | |
| 157 var event = _parser.parse(); | |
| 158 while (event.type != EventType.MAPPING_END) { | |
| 159 var key = _loadNode(event); | |
| 160 var value = _loadNode(_parser.parse()); | |
| 161 children[key] = value; | |
| 162 event = _parser.parse(); | |
| 163 } | |
| 164 | |
| 165 setSpan(node, firstEvent.span.expand(event.span)); | |
| 166 return node; | |
| 167 } | |
| 168 | |
| 169 /// Parses a scalar according to its tag name. | |
| 170 YamlScalar _parseByTag(ScalarEvent scalar) { | |
| 171 switch (scalar.tag) { | |
| 172 case "tag:yaml.org,2002:null": | |
| 173 var result = _parseNull(scalar); | |
| 174 if (result != null) return result; | |
| 175 throw new YamlException("Invalid null scalar.", scalar.span); | |
| 176 case "tag:yaml.org,2002:bool": | |
| 177 var result = _parseBool(scalar); | |
| 178 if (result != null) return result; | |
| 179 throw new YamlException("Invalid bool scalar.", scalar.span); | |
| 180 case "tag:yaml.org,2002:int": | |
| 181 var result = _parseNumber(scalar, allowFloat: false); | |
| 182 if (result != null) return result; | |
| 183 throw new YamlException("Invalid int scalar.", scalar.span); | |
| 184 case "tag:yaml.org,2002:float": | |
| 185 var result = _parseNumber(scalar, allowInt: false); | |
| 186 if (result != null) return result; | |
| 187 throw new YamlException("Invalid float scalar.", scalar.span); | |
| 188 case "tag:yaml.org,2002:str": | |
| 189 return new YamlScalar.internal(scalar.value, scalar); | |
| 190 default: | |
| 191 throw new YamlException('Undefined tag: ${scalar.tag}.', scalar.span); | |
| 192 } | |
| 193 } | |
| 194 | |
| 195 /// Parses [scalar], which may be one of several types. | |
| 196 YamlScalar _parseScalar(ScalarEvent scalar) => | |
| 197 _tryParseScalar(scalar) ?? new YamlScalar.internal(scalar.value, scalar); | |
| 198 | |
| 199 /// Tries to parse [scalar]. | |
| 200 /// | |
| 201 /// If parsing fails, this returns `null`, indicating that the scalar should | |
| 202 /// be parsed as a string. | |
| 203 YamlScalar _tryParseScalar(ScalarEvent scalar) { | |
| 204 // Quickly check for the empty string, which means null. | |
| 205 var length = scalar.value.length; | |
| 206 if (length == 0) return new YamlScalar.internal(null, scalar); | |
| 207 | |
| 208 // Dispatch on the first character. | |
| 209 var firstChar = scalar.value.codeUnitAt(0); | |
| 210 switch (firstChar) { | |
| 211 case $dot: | |
| 212 case $plus: | |
| 213 case $minus: | |
| 214 return _parseNumber(scalar); | |
| 215 case $n: | |
| 216 case $N: | |
| 217 return length == 4 ? _parseNull(scalar) : null; | |
| 218 case $t: | |
| 219 case $T: | |
| 220 return length == 4 ? _parseBool(scalar) : null; | |
| 221 case $f: | |
| 222 case $F: | |
| 223 return length == 5 ? _parseBool(scalar) : null; | |
| 224 case $tilde: | |
| 225 return length == 1 ? new YamlScalar.internal(null, scalar) : null; | |
| 226 default: | |
| 227 if (firstChar >= $0 && firstChar <= $9) return _parseNumber(scalar); | |
| 228 return null; | |
| 229 } | |
| 230 } | |
| 231 | |
| 232 /// Parse a null scalar. | |
| 233 /// | |
| 234 /// Returns a Dart `null` if parsing fails. | |
| 235 YamlScalar _parseNull(ScalarEvent scalar) { | |
| 236 switch (scalar.value) { | |
| 237 case "": | |
| 238 case "null": | |
| 239 case "Null": | |
| 240 case "NULL": | |
| 241 case "~": | |
| 242 return new YamlScalar.internal(null, scalar); | |
| 243 default: | |
| 244 return null; | |
| 245 } | |
| 246 } | |
| 247 | |
| 248 /// Parse a boolean scalar. | |
| 249 /// | |
| 250 /// Returns `null` if parsing fails. | |
| 251 YamlScalar _parseBool(ScalarEvent scalar) { | |
| 252 switch (scalar.value) { | |
| 253 case "true": | |
| 254 case "True": | |
| 255 case "TRUE": | |
| 256 return new YamlScalar.internal(true, scalar); | |
| 257 case "false": | |
| 258 case "False": | |
| 259 case "FALSE": | |
| 260 return new YamlScalar.internal(false, scalar); | |
| 261 default: | |
| 262 return null; | |
| 263 } | |
| 264 } | |
| 265 | |
| 266 /// Parses a numeric scalar. | |
| 267 /// | |
| 268 /// Returns `null` if parsing fails. | |
| 269 YamlNode _parseNumber(ScalarEvent scalar, {bool allowInt: true, | |
| 270 bool allowFloat: true}) { | |
| 271 var value = _parseNumberValue(scalar.value, | |
| 272 allowInt: allowInt, allowFloat: allowFloat); | |
| 273 return value == null ? null : new YamlScalar.internal(value, scalar); | |
| 274 } | |
| 275 | |
| 276 /// Parses the value of a number. | |
| 277 /// | |
| 278 /// Returns the number if it's parsed successfully, or `null` if it's not. | |
| 279 num _parseNumberValue(String contents, {bool allowInt: true, | |
| 280 bool allowFloat: true}) { | |
| 281 assert(allowInt || allowFloat); | |
| 282 | |
| 283 var firstChar = contents.codeUnitAt(0); | |
| 284 var length = contents.length; | |
| 285 | |
| 286 // Quick check for single digit integers. | |
| 287 if (allowInt && length == 1) { | |
| 288 var value = firstChar - $0; | |
| 289 return value >= 0 && value <= 9 ? value : null; | |
| 290 } | |
| 291 | |
| 292 var secondChar = contents.codeUnitAt(1); | |
| 293 | |
| 294 // Hexadecimal or octal integers. | |
| 295 if (allowInt && firstChar == $0) { | |
| 296 // int.parse supports 0x natively. | |
| 297 if (secondChar == $x) return int.parse(contents, onError: (_) => null); | |
| 298 | |
| 299 if (secondChar == $o) { | |
| 300 var afterRadix = contents.substring(2); | |
| 301 return int.parse(afterRadix, radix: 8, onError: (_) => null); | |
| 302 } | |
| 303 } | |
| 304 | |
| 305 // Int or float starting with a digit or a +/- sign. | |
| 306 if ((firstChar >= $0 && firstChar <= $9) || | |
| 307 ((firstChar == $plus || firstChar == $minus) && | |
| 308 secondChar >= $0 && secondChar <= $9)) { | |
| 309 // Try to parse an int or, failing that, a double. | |
| 310 var result = null; | |
| 311 if (allowInt) { | |
| 312 // Pass "radix: 10" explicitly to ensure that "-0x10", which is valid | |
| 313 // Dart but invalid YAML, doesn't get parsed. | |
| 314 result = int.parse(contents, radix: 10, onError: (_) => null); | |
| 315 } | |
| 316 | |
| 317 if (allowFloat) result ??= double.parse(contents, (_) => null); | |
| 318 return result; | |
| 319 } | |
| 320 | |
| 321 if (!allowFloat) return null; | |
| 322 | |
| 323 // Now the only possibility is to parse a float starting with a dot or a | |
| 324 // sign and a dot, or the signed/unsigned infinity values and not-a-numbers. | |
| 325 if ((firstChar == $dot && secondChar >= $0 && secondChar <= $9) || | |
| 326 (firstChar == $minus || firstChar == $plus) && secondChar == $dot) { | |
| 327 // Starting with a . and a number or a sign followed by a dot. | |
| 328 if (length == 5) { | |
| 329 switch (contents) { | |
| 330 case "+.inf": | |
| 331 case "+.Inf": | |
| 332 case "+.INF": | |
| 333 return double.INFINITY; | |
| 334 case "-.inf": | |
| 335 case "-.Inf": | |
| 336 case "-.INF": | |
| 337 return -double.INFINITY; | |
| 338 } | |
| 339 } | |
| 340 | |
| 341 return double.parse(contents, (_) => null); | |
| 342 } | |
| 343 | |
| 344 if (length == 4 && firstChar == $dot) { | |
| 345 switch (contents) { | |
| 346 case ".inf": | |
| 347 case ".Inf": | |
| 348 case ".INF": | |
| 349 return double.INFINITY; | |
| 350 case ".nan": | |
| 351 case ".NaN": | |
| 352 case ".NAN": | |
| 353 return double.NAN; | |
| 354 } | |
| 355 } | |
| 356 | |
| 357 return null; | |
| 358 } | |
| 359 } | |
| OLD | NEW |