| OLD | NEW |
| 1 // Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file | 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 | 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. | 3 // BSD-style license that can be found in the LICENSE file. |
| 4 | 4 |
| 5 library yaml.loader; | 5 library yaml.loader; |
| 6 | 6 |
| 7 import 'package:charcode/ascii.dart'; |
| 7 import 'package:source_span/source_span.dart'; | 8 import 'package:source_span/source_span.dart'; |
| 8 | 9 |
| 9 import 'equality.dart'; | 10 import 'equality.dart'; |
| 10 import 'event.dart'; | 11 import 'event.dart'; |
| 11 import 'parser.dart'; | 12 import 'parser.dart'; |
| 12 import 'yaml_document.dart'; | 13 import 'yaml_document.dart'; |
| 13 import 'yaml_exception.dart'; | 14 import 'yaml_exception.dart'; |
| 14 import 'yaml_node.dart'; | 15 import 'yaml_node.dart'; |
| 15 | 16 |
| 16 /// Matches YAML null. | |
| 17 final _nullRegExp = new RegExp(r"^(null|Null|NULL|~|)$"); | |
| 18 | |
| 19 /// Matches a YAML bool. | |
| 20 final _boolRegExp = new RegExp(r"^(?:(true|True|TRUE)|(false|False|FALSE))$"); | |
| 21 | |
| 22 /// Matches a YAML decimal integer like `+1234`. | |
| 23 final _decimalIntRegExp = new RegExp(r"^[-+]?[0-9]+$"); | |
| 24 | |
| 25 /// Matches a YAML octal integer like `0o123`. | |
| 26 final _octalIntRegExp = new RegExp(r"^0o([0-7]+)$"); | |
| 27 | |
| 28 /// Matches a YAML hexidecimal integer like `0x123abc`. | |
| 29 final _hexIntRegExp = new RegExp(r"^0x[0-9a-fA-F]+$"); | |
| 30 | |
| 31 /// Matches a YAML floating point number like `12.34+e56`. | |
| 32 final _floatRegExp = new RegExp( | |
| 33 r"^[-+]?(\.[0-9]+|[0-9]+(\.[0-9]*)?)([eE][-+]?[0-9]+)?$"); | |
| 34 | |
| 35 /// Matches YAML infinity. | |
| 36 final _infinityRegExp = new RegExp(r"^([+-]?)\.(inf|Inf|INF)$"); | |
| 37 | |
| 38 /// Matches YAML NaN. | |
| 39 final _nanRegExp = new RegExp(r"^\.(nan|NaN|NAN)$"); | |
| 40 | |
| 41 /// A loader that reads [Event]s emitted by a [Parser] and emits | 17 /// A loader that reads [Event]s emitted by a [Parser] and emits |
| 42 /// [YamlDocument]s. | 18 /// [YamlDocument]s. |
| 43 /// | 19 /// |
| 44 /// This is based on the libyaml loader, available at | 20 /// This is based on the libyaml loader, available at |
| 45 /// https://github.com/yaml/libyaml/blob/master/src/loader.c. The license for | 21 /// https://github.com/yaml/libyaml/blob/master/src/loader.c. The license for |
| 46 /// that is available in ../../libyaml-license.txt. | 22 /// that is available in ../../libyaml-license.txt. |
| 47 class Loader { | 23 class Loader { |
| 48 /// The underlying [Parser] that generates [Event]s. | 24 /// The underlying [Parser] that generates [Event]s. |
| 49 final Parser _parser; | 25 final Parser _parser; |
| 50 | 26 |
| (...skipping 75 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 126 var alias = _aliases[event.name]; | 102 var alias = _aliases[event.name]; |
| 127 if (alias != null) return alias; | 103 if (alias != null) return alias; |
| 128 | 104 |
| 129 throw new YamlException("Undefined alias.", event.span); | 105 throw new YamlException("Undefined alias.", event.span); |
| 130 } | 106 } |
| 131 | 107 |
| 132 /// Composes a scalar node. | 108 /// Composes a scalar node. |
| 133 YamlNode _loadScalar(ScalarEvent scalar) { | 109 YamlNode _loadScalar(ScalarEvent scalar) { |
| 134 var node; | 110 var node; |
| 135 if (scalar.tag == "!") { | 111 if (scalar.tag == "!") { |
| 136 node = _parseString(scalar); | 112 node = new YamlScalar.internal(scalar.value, scalar); |
| 137 } else if (scalar.tag != null) { | 113 } else if (scalar.tag != null) { |
| 138 node = _parseByTag(scalar); | 114 node = _parseByTag(scalar); |
| 139 } else { | 115 } else { |
| 140 node = _parseNull(scalar); | 116 node = _parseScalar(scalar); |
| 141 if (node == null) node = _parseBool(scalar); | |
| 142 if (node == null) node = _parseInt(scalar); | |
| 143 if (node == null) node = _parseFloat(scalar); | |
| 144 if (node == null) node = _parseString(scalar); | |
| 145 } | 117 } |
| 146 | 118 |
| 147 _registerAnchor(scalar.anchor, node); | 119 _registerAnchor(scalar.anchor, node); |
| 148 return node; | 120 return node; |
| 149 } | 121 } |
| 150 | 122 |
| 151 /// Composes a sequence node. | 123 /// Composes a sequence node. |
| 152 YamlNode _loadSequence(SequenceStartEvent firstEvent) { | 124 YamlNode _loadSequence(SequenceStartEvent firstEvent) { |
| 153 if (firstEvent.tag != "!" && firstEvent.tag != null && | 125 if (firstEvent.tag != "!" && firstEvent.tag != null && |
| 154 firstEvent.tag != "tag:yaml.org,2002:seq") { | 126 firstEvent.tag != "tag:yaml.org,2002:seq") { |
| (...skipping 35 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 190 event = _parser.parse(); | 162 event = _parser.parse(); |
| 191 } | 163 } |
| 192 | 164 |
| 193 setSpan(node, firstEvent.span.expand(event.span)); | 165 setSpan(node, firstEvent.span.expand(event.span)); |
| 194 return node; | 166 return node; |
| 195 } | 167 } |
| 196 | 168 |
| 197 /// Parses a scalar according to its tag name. | 169 /// Parses a scalar according to its tag name. |
| 198 YamlScalar _parseByTag(ScalarEvent scalar) { | 170 YamlScalar _parseByTag(ScalarEvent scalar) { |
| 199 switch (scalar.tag) { | 171 switch (scalar.tag) { |
| 200 case "tag:yaml.org,2002:null": return _parseNull(scalar); | 172 case "tag:yaml.org,2002:null": |
| 201 case "tag:yaml.org,2002:bool": return _parseBool(scalar); | 173 var result = _parseNull(scalar); |
| 202 case "tag:yaml.org,2002:int": return _parseInt(scalar); | 174 if (result != null) return result; |
| 203 case "tag:yaml.org,2002:float": return _parseFloat(scalar); | 175 throw new YamlException("Invalid null scalar.", scalar.span); |
| 204 case "tag:yaml.org,2002:str": return _parseString(scalar); | 176 case "tag:yaml.org,2002:bool": |
| 205 } | 177 var result = _parseBool(scalar); |
| 206 throw new YamlException('Undefined tag: ${scalar.tag}.', scalar.span); | 178 if (result != null) return result; |
| 207 } | 179 throw new YamlException("Invalid bool scalar.", scalar.span); |
| 208 | 180 case "tag:yaml.org,2002:int": |
| 209 /// Parses a null scalar. | 181 var result = _parseNumber(scalar, allowFloat: false); |
| 210 YamlScalar _parseNull(ScalarEvent scalar) { | 182 if (result != null) return result; |
| 211 // TODO(nweiz): add ScalarStyle and implicit metadata to the scalars. | 183 throw new YamlException("Invalid int scalar.", scalar.span); |
| 212 if (_nullRegExp.hasMatch(scalar.value)) { | 184 case "tag:yaml.org,2002:float": |
| 213 return new YamlScalar.internal(null, scalar.span, scalar.style); | 185 var result = _parseNumber(scalar, allowInt: false); |
| 214 } else { | 186 if (result != null) return result; |
| 215 return null; | 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); |
| 216 } | 192 } |
| 217 } | 193 } |
| 218 | 194 |
| 219 /// Parses a boolean scalar. | 195 /// Parses [scalar], which may be one of several types. |
| 220 YamlScalar _parseBool(ScalarEvent scalar) { | 196 YamlScalar _parseScalar(ScalarEvent scalar) => |
| 221 var match = _boolRegExp.firstMatch(scalar.value); | 197 _tryParseScalar(scalar) ?? new YamlScalar.internal(scalar.value, scalar); |
| 222 if (match == null) return null; | 198 |
| 223 return new YamlScalar.internal( | 199 /// Tries to parse [scalar]. |
| 224 match.group(1) != null, scalar.span, scalar.style); | 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 } |
| 225 } | 230 } |
| 226 | 231 |
| 227 /// Parses an integer scalar. | 232 /// Parse a null scalar. |
| 228 YamlScalar _parseInt(ScalarEvent scalar) { | 233 /// |
| 229 var match = _decimalIntRegExp.firstMatch(scalar.value); | 234 /// Returns a Dart `null` if parsing fails. |
| 230 if (match != null) { | 235 YamlScalar _parseNull(ScalarEvent scalar) { |
| 231 return new YamlScalar.internal( | 236 switch (scalar.value) { |
| 232 int.parse(match.group(0)), scalar.span, scalar.style); | 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; |
| 233 } | 290 } |
| 234 | 291 |
| 235 match = _octalIntRegExp.firstMatch(scalar.value); | 292 var secondChar = contents.codeUnitAt(1); |
| 236 if (match != null) { | 293 |
| 237 var n = int.parse(match.group(1), radix: 8); | 294 // Hexadecimal or octal integers. |
| 238 return new YamlScalar.internal(n, scalar.span, scalar.style); | 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 } |
| 239 } | 303 } |
| 240 | 304 |
| 241 match = _hexIntRegExp.firstMatch(scalar.value); | 305 // Int or float starting with a digit or a +/- sign. |
| 242 if (match != null) { | 306 if ((firstChar >= $0 && firstChar <= $9) || |
| 243 return new YamlScalar.internal( | 307 ((firstChar == $plus || firstChar == $minus) && |
| 244 int.parse(match.group(0)), scalar.span, scalar.style); | 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 } |
| 245 } | 355 } |
| 246 | 356 |
| 247 return null; | 357 return null; |
| 248 } | 358 } |
| 249 | |
| 250 /// Parses a floating-point scalar. | |
| 251 YamlScalar _parseFloat(ScalarEvent scalar) { | |
| 252 var match = _floatRegExp.firstMatch(scalar.value); | |
| 253 if (match != null) { | |
| 254 // YAML allows floats of the form "0.", but Dart does not. Fix up those | |
| 255 // floats by removing the trailing dot. | |
| 256 var matchStr = match.group(0).replaceAll(new RegExp(r"\.$"), ""); | |
| 257 return new YamlScalar.internal( | |
| 258 double.parse(matchStr), scalar.span, scalar.style); | |
| 259 } | |
| 260 | |
| 261 match = _infinityRegExp.firstMatch(scalar.value); | |
| 262 if (match != null) { | |
| 263 var value = match.group(1) == "-" ? -double.INFINITY : double.INFINITY; | |
| 264 return new YamlScalar.internal(value, scalar.span, scalar.style); | |
| 265 } | |
| 266 | |
| 267 match = _nanRegExp.firstMatch(scalar.value); | |
| 268 if (match != null) { | |
| 269 return new YamlScalar.internal(double.NAN, scalar.span, scalar.style); | |
| 270 } | |
| 271 | |
| 272 return null; | |
| 273 } | |
| 274 | |
| 275 /// Parses a string scalar. | |
| 276 YamlScalar _parseString(ScalarEvent scalar) => | |
| 277 new YamlScalar.internal(scalar.value, scalar.span, scalar.style); | |
| 278 } | 359 } |
| OLD | NEW |