| 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:source_span/source_span.dart'; | 7 import 'package:source_span/source_span.dart'; |
| 8 | 8 |
| 9 import 'equality.dart'; | 9 import 'equality.dart'; |
| 10 import 'event.dart'; | 10 import 'event.dart'; |
| 11 import 'parser.dart'; | 11 import 'parser.dart'; |
| 12 import 'yaml_document.dart'; | 12 import 'yaml_document.dart'; |
| 13 import 'yaml_exception.dart'; | 13 import 'yaml_exception.dart'; |
| 14 import 'yaml_node.dart'; | 14 import 'yaml_node.dart'; |
| 15 | 15 |
| 16 /// Matches YAML null. | 16 // A singleton class which corresponds to no value having been scanned. |
| 17 final _nullRegExp = new RegExp(r"^(null|Null|NULL|~|)$"); | 17 class _NoValue { |
| 18 | 18 const _NoValue(); |
| 19 /// Matches a YAML bool. | 19 } |
| 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 | 20 |
| 41 /// A loader that reads [Event]s emitted by a [Parser] and emits | 21 /// A loader that reads [Event]s emitted by a [Parser] and emits |
| 42 /// [YamlDocument]s. | 22 /// [YamlDocument]s. |
| 43 /// | 23 /// |
| 44 /// This is based on the libyaml loader, available at | 24 /// This is based on the libyaml loader, available at |
| 45 /// https://github.com/yaml/libyaml/blob/master/src/loader.c. The license for | 25 /// https://github.com/yaml/libyaml/blob/master/src/loader.c. The license for |
| 46 /// that is available in ../../libyaml-license.txt. | 26 /// that is available in ../../libyaml-license.txt. |
| 47 class Loader { | 27 class Loader { |
| 48 /// The underlying [Parser] that generates [Event]s. | 28 /// The underlying [Parser] that generates [Event]s. |
| 49 final Parser _parser; | 29 final Parser _parser; |
| (...skipping 76 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 126 var alias = _aliases[event.name]; | 106 var alias = _aliases[event.name]; |
| 127 if (alias != null) return alias; | 107 if (alias != null) return alias; |
| 128 | 108 |
| 129 throw new YamlException("Undefined alias.", event.span); | 109 throw new YamlException("Undefined alias.", event.span); |
| 130 } | 110 } |
| 131 | 111 |
| 132 /// Composes a scalar node. | 112 /// Composes a scalar node. |
| 133 YamlNode _loadScalar(ScalarEvent scalar) { | 113 YamlNode _loadScalar(ScalarEvent scalar) { |
| 134 var node; | 114 var node; |
| 135 if (scalar.tag == "!") { | 115 if (scalar.tag == "!") { |
| 136 node = _parseString(scalar); | 116 node = new YamlScalar.internal(scalar.value, scalar.span, scalar.style); |
| 137 } else if (scalar.tag != null) { | 117 } else if (scalar.tag != null) { |
| 138 node = _parseByTag(scalar); | 118 node = _scanByTag(scalar); |
| 139 } else { | 119 } else { |
| 140 node = _parseNull(scalar); | 120 node = _scanScalar(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 } | 121 } |
| 146 | 122 |
| 147 _registerAnchor(scalar.anchor, node); | 123 _registerAnchor(scalar.anchor, node); |
| 148 return node; | 124 return node; |
| 149 } | 125 } |
| 150 | 126 |
| 151 /// Composes a sequence node. | 127 /// Composes a sequence node. |
| 152 YamlNode _loadSequence(SequenceStartEvent firstEvent) { | 128 YamlNode _loadSequence(SequenceStartEvent firstEvent) { |
| 153 if (firstEvent.tag != "!" && firstEvent.tag != null && | 129 if (firstEvent.tag != "!" && firstEvent.tag != null && |
| 154 firstEvent.tag != "tag:yaml.org,2002:seq") { | 130 firstEvent.tag != "tag:yaml.org,2002:seq") { |
| (...skipping 32 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 187 var key = _loadNode(event); | 163 var key = _loadNode(event); |
| 188 var value = _loadNode(_parser.parse()); | 164 var value = _loadNode(_parser.parse()); |
| 189 children[key] = value; | 165 children[key] = value; |
| 190 event = _parser.parse(); | 166 event = _parser.parse(); |
| 191 } | 167 } |
| 192 | 168 |
| 193 setSpan(node, firstEvent.span.expand(event.span)); | 169 setSpan(node, firstEvent.span.expand(event.span)); |
| 194 return node; | 170 return node; |
| 195 } | 171 } |
| 196 | 172 |
| 173 // Value returned by the scanning functions if no valid value was scanned. |
| 174 static const _kNoValue = const _NoValue(); |
| 175 |
| 197 /// Parses a scalar according to its tag name. | 176 /// Parses a scalar according to its tag name. |
| 198 YamlScalar _parseByTag(ScalarEvent scalar) { | 177 YamlScalar _scanByTag(ScalarEvent scalar) { |
| 178 var str = scalar.value; |
| 179 var len = str.length; |
| 180 var ch0 = (len == 0) ? null : str.codeUnitAt(0); |
| 181 |
| 182 var value = _kNoValue; |
| 199 switch (scalar.tag) { | 183 switch (scalar.tag) { |
| 200 case "tag:yaml.org,2002:null": return _parseNull(scalar); | 184 case "tag:yaml.org,2002:null": |
| 201 case "tag:yaml.org,2002:bool": return _parseBool(scalar); | 185 value = _scanNull(str, ch0, len); |
| 202 case "tag:yaml.org,2002:int": return _parseInt(scalar); | 186 break; |
| 203 case "tag:yaml.org,2002:float": return _parseFloat(scalar); | 187 case "tag:yaml.org,2002:bool": |
| 204 case "tag:yaml.org,2002:str": return _parseString(scalar); | 188 value = _scanBool(str, ch0, len); |
| 189 break; |
| 190 case "tag:yaml.org,2002:int": |
| 191 value = _scanNumber(str, ch0, len, true, false); |
| 192 break; |
| 193 case "tag:yaml.org,2002:float": |
| 194 value = _scanNumber(str, ch0, len, false, true); |
| 195 break; |
| 196 case "tag:yaml.org,2002:str": |
| 197 value = str; |
| 198 break; |
| 199 default: |
| 200 throw new YamlException('Undefined tag: ${scalar.tag}.', scalar.span); |
| 205 } | 201 } |
| 206 throw new YamlException('Undefined tag: ${scalar.tag}.', scalar.span); | 202 if (value != _kNoValue) { |
| 207 } | 203 return new YamlScalar.internal(value, scalar.span, scalar.style); |
| 208 | |
| 209 /// Parses a null scalar. | |
| 210 YamlScalar _parseNull(ScalarEvent scalar) { | |
| 211 // TODO(nweiz): add ScalarStyle and implicit metadata to the scalars. | |
| 212 if (_nullRegExp.hasMatch(scalar.value)) { | |
| 213 return new YamlScalar.internal(null, scalar.span, scalar.style); | |
| 214 } else { | |
| 215 return null; | |
| 216 } | 204 } |
| 217 } | |
| 218 | |
| 219 /// Parses a boolean scalar. | |
| 220 YamlScalar _parseBool(ScalarEvent scalar) { | |
| 221 var match = _boolRegExp.firstMatch(scalar.value); | |
| 222 if (match == null) return null; | |
| 223 return new YamlScalar.internal( | |
| 224 match.group(1) != null, scalar.span, scalar.style); | |
| 225 } | |
| 226 | |
| 227 /// Parses an integer scalar. | |
| 228 YamlScalar _parseInt(ScalarEvent scalar) { | |
| 229 var match = _decimalIntRegExp.firstMatch(scalar.value); | |
| 230 if (match != null) { | |
| 231 return new YamlScalar.internal( | |
| 232 int.parse(match.group(0)), scalar.span, scalar.style); | |
| 233 } | |
| 234 | |
| 235 match = _octalIntRegExp.firstMatch(scalar.value); | |
| 236 if (match != null) { | |
| 237 var n = int.parse(match.group(1), radix: 8); | |
| 238 return new YamlScalar.internal(n, scalar.span, scalar.style); | |
| 239 } | |
| 240 | |
| 241 match = _hexIntRegExp.firstMatch(scalar.value); | |
| 242 if (match != null) { | |
| 243 return new YamlScalar.internal( | |
| 244 int.parse(match.group(0)), scalar.span, scalar.style); | |
| 245 } | |
| 246 | |
| 247 return null; | 205 return null; |
| 248 } | 206 } |
| 249 | 207 |
| 250 /// Parses a floating-point scalar. | 208 /// Code unit values. |
| 251 YamlScalar _parseFloat(ScalarEvent scalar) { | 209 static const _plus = 0x2B; |
| 252 var match = _floatRegExp.firstMatch(scalar.value); | 210 static const _minus = 0x2D; |
| 253 if (match != null) { | 211 static const _dot = 0x2E; |
| 254 // YAML allows floats of the form "0.", but Dart does not. Fix up those | 212 static const _0 = 0x30; |
| 255 // floats by removing the trailing dot. | 213 static const _9 = 0x39; |
| 256 var matchStr = match.group(0).replaceAll(new RegExp(r"\.$"), ""); | 214 static const _F = 0x46; |
| 257 return new YamlScalar.internal( | 215 static const _N = 0x4E; |
| 258 double.parse(matchStr), scalar.span, scalar.style); | 216 static const _T = 0x54; |
| 217 static const _f = 0x66; |
| 218 static const _n = 0x6E; |
| 219 static const _o = 0x6F; |
| 220 static const _t = 0x74; |
| 221 static const _x = 0x78; |
| 222 static const _tilde = 0x7E; |
| 223 |
| 224 /// Scan a scalar event's value. |
| 225 YamlScalar _scanScalar(ScalarEvent scalar) { |
| 226 final str = scalar.value; |
| 227 final len = str.length; |
| 228 final span = scalar.span; |
| 229 final style = scalar.style; |
| 230 |
| 231 var value = _kNoValue; |
| 232 |
| 233 // Quickly check for the empty string. |
| 234 if (len == 0) { |
| 235 // Empty string is equivalent to null. |
| 236 value = null; |
| 237 } else { |
| 238 // Dispatch on the first character. |
| 239 var ch0 = str.codeUnitAt(0); |
| 240 if ((ch0 == _dot) || (ch0 == _plus) || (ch0 == _minus) || |
| 241 ((ch0 >= _0) && (ch0 <= _9))) { |
| 242 // Recognize numbers (either int or double). |
| 243 value = _scanNumber(str, ch0, len, true, true); |
| 244 } else if ((len == 4) && ((ch0 == _n) || (ch0 == _N))) { |
| 245 value = _scanNull(str, ch0, len); |
| 246 } else if (((len == 4) && ((ch0 == _t) || (ch0 == _T))) || |
| 247 ((len == 5) && ((ch0 == _f) || (ch0 == _F)))) { |
| 248 value = _scanBool(str, ch0, len); |
| 249 } else if ((len == 1) && (ch0 == _tilde)) { |
| 250 // Special case of null be written as "~'. |
| 251 value = null; |
| 252 } |
| 259 } | 253 } |
| 260 | 254 |
| 261 match = _infinityRegExp.firstMatch(scalar.value); | 255 // If no value was found, set to the String value. |
| 262 if (match != null) { | 256 if (value == _kNoValue) { |
| 263 var value = match.group(1) == "-" ? -double.INFINITY : double.INFINITY; | 257 value = str; |
| 264 return new YamlScalar.internal(value, scalar.span, scalar.style); | |
| 265 } | 258 } |
| 266 | 259 return new YamlScalar.internal(value, span, style); |
| 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 } | 260 } |
| 274 | 261 |
| 275 /// Parses a string scalar. | 262 /// Scan the different versions of the null literal. |
| 276 YamlScalar _parseString(ScalarEvent scalar) => | 263 _scanNull(String str, int ch0, int len) { |
| 277 new YamlScalar.internal(scalar.value, scalar.span, scalar.style); | 264 if (len == 4) { |
| 265 if (((ch0 == _n) && (str == "null")) || |
| 266 ((ch0 == _N) && ((str == "Null") || (str == "NULL")))) { |
| 267 return null; |
| 268 } |
| 269 } else if ((len == 0) || ((len == 1) && (ch0 == _tilde))) { |
| 270 return null; |
| 271 } |
| 272 return _kNoValue; |
| 273 } |
| 274 |
| 275 /// Scan a boolean. |
| 276 _scanBool(String str, int ch0, int len) { |
| 277 if (len == 4) { |
| 278 if (((ch0 == _t) && (str == "true")) || |
| 279 ((ch0 == _T) && ((str == "True") || (str == "TRUE")))) { |
| 280 return true; |
| 281 } |
| 282 } else if (len == 5) { |
| 283 if (((ch0 == _f) && (str == "false")) || |
| 284 ((ch0 == _F) && ((str == "False") || (str == "FALSE")))) { |
| 285 return false; |
| 286 } |
| 287 } |
| 288 return _kNoValue; |
| 289 } |
| 290 |
| 291 |
| 292 /// Helper which returns the known "no value" when number parsing fails. |
| 293 static var _onNumError = (_) => null; |
| 294 |
| 295 /// Scan a number. Handles both integers and floats within one function if |
| 296 /// possible avoid rescanning in the general case. |
| 297 _scanNumber(String str, int ch0, int len, bool allow_int, bool allow_float) { |
| 298 // Quick check for single digit integers. |
| 299 if (len == 1) { |
| 300 var val = ch0 - _0; |
| 301 if ((val >= 0) && (val <= 9)) { |
| 302 return val; |
| 303 } |
| 304 return _kNoValue; |
| 305 } |
| 306 // Assume we are scanning a nicely formed number. |
| 307 var ch1 = str.codeUnitAt(1); |
| 308 if (allow_int && (ch0 == _0) && ((ch1 == _x) || (ch1 == _o))) { |
| 309 // Scanning hex or octal integers. |
| 310 var sub_str = str.substring(2); |
| 311 if (ch1 == _x) { |
| 312 var result = int.parse(sub_str, radix:16, onError: _onNumError); |
| 313 return (result != null) ? result : _kNoValue; |
| 314 } |
| 315 var result = int.parse(sub_str, radix: 8, onError: _onNumError); |
| 316 return (result != null) ? result : _kNoValue; |
| 317 } else if (((ch0 >= _0) && (ch0 <= _9)) || |
| 318 (((ch0 == _plus) || (ch0 == _minus)) && |
| 319 (ch1 >= _0) && (ch1 <= _9))) { |
| 320 // Either starting with a number or starting with a sign and number. |
| 321 var result = null; |
| 322 if (allow_int) { |
| 323 // Possible hex or octal numbers are handled above, so only allow |
| 324 // radix 10 here. |
| 325 result = int.parse(str, radix: 10, onError: _onNumError); |
| 326 } |
| 327 // If we failed to scan an int, try to scan as a double. |
| 328 if (allow_float && (result == null)) { |
| 329 result = double.parse(str, _onNumError); |
| 330 } |
| 331 return (result != null) ? result : _kNoValue; |
| 332 } else if (allow_float) { |
| 333 // Not the only possibility is to scan a float starting with a dot or |
| 334 // a sign and a dot. As well as the signed/unsigned infinity values and |
| 335 // not-a-numbers. |
| 336 if (((ch0 == _dot) && (ch1 >= _0) && (ch1 <= _9)) || |
| 337 ((ch0 == _minus) || (ch0 == _plus)) && (ch1 == _dot)) { |
| 338 // Starting with a . and a number or a sign followed by a dot. |
| 339 if (len == 5) { |
| 340 if (str == "+.inf" || str == "+.Inf" || str == "+.INF") { |
| 341 return double.INFINITY; |
| 342 } else if (str == "-.inf" || str == "-.Inf" || str == "-.INF") { |
| 343 return -double.INFINITY; |
| 344 } |
| 345 } |
| 346 var result = double.parse(str, _onNumError); |
| 347 return (result != null) ? result : _kNoValue; |
| 348 } else if ((len == 4) && (ch0 == _dot)) { |
| 349 if (str == ".inf" || str == ".Inf" || str == ".INF") { |
| 350 return double.INFINITY; |
| 351 } else if (str == ".nan" || str == ".NaN" || str == ".NAN") { |
| 352 return double.NAN; |
| 353 } |
| 354 } |
| 355 } |
| 356 return _kNoValue; |
| 357 } |
| 278 } | 358 } |
| OLD | NEW |