| Index: lib/src/loader.dart
|
| diff --git a/lib/src/loader.dart b/lib/src/loader.dart
|
| index 07929579c14654eafceaa702faf0acd7dc643d5a..e1a59e1409bf0c15b3aaeb7fd986e1301d5a3763 100644
|
| --- a/lib/src/loader.dart
|
| +++ b/lib/src/loader.dart
|
| @@ -13,30 +13,10 @@ import 'yaml_document.dart';
|
| import 'yaml_exception.dart';
|
| import 'yaml_node.dart';
|
|
|
| -/// Matches YAML null.
|
| -final _nullRegExp = new RegExp(r"^(null|Null|NULL|~|)$");
|
| -
|
| -/// Matches a YAML bool.
|
| -final _boolRegExp = new RegExp(r"^(?:(true|True|TRUE)|(false|False|FALSE))$");
|
| -
|
| -/// Matches a YAML decimal integer like `+1234`.
|
| -final _decimalIntRegExp = new RegExp(r"^[-+]?[0-9]+$");
|
| -
|
| -/// Matches a YAML octal integer like `0o123`.
|
| -final _octalIntRegExp = new RegExp(r"^0o([0-7]+)$");
|
| -
|
| -/// Matches a YAML hexidecimal integer like `0x123abc`.
|
| -final _hexIntRegExp = new RegExp(r"^0x[0-9a-fA-F]+$");
|
| -
|
| -/// Matches a YAML floating point number like `12.34+e56`.
|
| -final _floatRegExp = new RegExp(
|
| - r"^[-+]?(\.[0-9]+|[0-9]+(\.[0-9]*)?)([eE][-+]?[0-9]+)?$");
|
| -
|
| -/// Matches YAML infinity.
|
| -final _infinityRegExp = new RegExp(r"^([+-]?)\.(inf|Inf|INF)$");
|
| -
|
| -/// Matches YAML NaN.
|
| -final _nanRegExp = new RegExp(r"^\.(nan|NaN|NAN)$");
|
| +// A singleton class which corresponds to no value having been scanned.
|
| +class _NoValue {
|
| + const _NoValue();
|
| +}
|
|
|
| /// A loader that reads [Event]s emitted by a [Parser] and emits
|
| /// [YamlDocument]s.
|
| @@ -133,15 +113,11 @@ class Loader {
|
| YamlNode _loadScalar(ScalarEvent scalar) {
|
| var node;
|
| if (scalar.tag == "!") {
|
| - node = _parseString(scalar);
|
| + node = new YamlScalar.internal(scalar.value, scalar.span, scalar.style);
|
| } else if (scalar.tag != null) {
|
| - node = _parseByTag(scalar);
|
| + node = _scanByTag(scalar);
|
| } else {
|
| - node = _parseNull(scalar);
|
| - if (node == null) node = _parseBool(scalar);
|
| - if (node == null) node = _parseInt(scalar);
|
| - if (node == null) node = _parseFloat(scalar);
|
| - if (node == null) node = _parseString(scalar);
|
| + node = _scanScalar(scalar);
|
| }
|
|
|
| _registerAnchor(scalar.anchor, node);
|
| @@ -194,85 +170,189 @@ class Loader {
|
| return node;
|
| }
|
|
|
| + // Value returned by the scanning functions if no valid value was scanned.
|
| + static const _kNoValue = const _NoValue();
|
| +
|
| /// Parses a scalar according to its tag name.
|
| - YamlScalar _parseByTag(ScalarEvent scalar) {
|
| + YamlScalar _scanByTag(ScalarEvent scalar) {
|
| + var str = scalar.value;
|
| + var len = str.length;
|
| + var ch0 = (len == 0) ? null : str.codeUnitAt(0);
|
| +
|
| + var value = _kNoValue;
|
| switch (scalar.tag) {
|
| - case "tag:yaml.org,2002:null": return _parseNull(scalar);
|
| - case "tag:yaml.org,2002:bool": return _parseBool(scalar);
|
| - case "tag:yaml.org,2002:int": return _parseInt(scalar);
|
| - case "tag:yaml.org,2002:float": return _parseFloat(scalar);
|
| - case "tag:yaml.org,2002:str": return _parseString(scalar);
|
| + case "tag:yaml.org,2002:null":
|
| + value = _scanNull(str, ch0, len);
|
| + break;
|
| + case "tag:yaml.org,2002:bool":
|
| + value = _scanBool(str, ch0, len);
|
| + break;
|
| + case "tag:yaml.org,2002:int":
|
| + value = _scanNumber(str, ch0, len, true, false);
|
| + break;
|
| + case "tag:yaml.org,2002:float":
|
| + value = _scanNumber(str, ch0, len, false, true);
|
| + break;
|
| + case "tag:yaml.org,2002:str":
|
| + value = str;
|
| + break;
|
| + default:
|
| + throw new YamlException('Undefined tag: ${scalar.tag}.', scalar.span);
|
| + }
|
| + if (value != _kNoValue) {
|
| + return new YamlScalar.internal(value, scalar.span, scalar.style);
|
| }
|
| - throw new YamlException('Undefined tag: ${scalar.tag}.', scalar.span);
|
| + return null;
|
| }
|
|
|
| - /// Parses a null scalar.
|
| - YamlScalar _parseNull(ScalarEvent scalar) {
|
| - // TODO(nweiz): add ScalarStyle and implicit metadata to the scalars.
|
| - if (_nullRegExp.hasMatch(scalar.value)) {
|
| - return new YamlScalar.internal(null, scalar.span, scalar.style);
|
| + /// Code unit values.
|
| + static const _plus = 0x2B;
|
| + static const _minus = 0x2D;
|
| + static const _dot = 0x2E;
|
| + static const _0 = 0x30;
|
| + static const _9 = 0x39;
|
| + static const _F = 0x46;
|
| + static const _N = 0x4E;
|
| + static const _T = 0x54;
|
| + static const _f = 0x66;
|
| + static const _n = 0x6E;
|
| + static const _o = 0x6F;
|
| + static const _t = 0x74;
|
| + static const _x = 0x78;
|
| + static const _tilde = 0x7E;
|
| +
|
| + /// Scan a scalar event's value.
|
| + YamlScalar _scanScalar(ScalarEvent scalar) {
|
| + final str = scalar.value;
|
| + final len = str.length;
|
| + final span = scalar.span;
|
| + final style = scalar.style;
|
| +
|
| + var value = _kNoValue;
|
| +
|
| + // Quickly check for the empty string.
|
| + if (len == 0) {
|
| + // Empty string is equivalent to null.
|
| + value = null;
|
| } else {
|
| - return null;
|
| + // Dispatch on the first character.
|
| + var ch0 = str.codeUnitAt(0);
|
| + if ((ch0 == _dot) || (ch0 == _plus) || (ch0 == _minus) ||
|
| + ((ch0 >= _0) && (ch0 <= _9))) {
|
| + // Recognize numbers (either int or double).
|
| + value = _scanNumber(str, ch0, len, true, true);
|
| + } else if ((len == 4) && ((ch0 == _n) || (ch0 == _N))) {
|
| + value = _scanNull(str, ch0, len);
|
| + } else if (((len == 4) && ((ch0 == _t) || (ch0 == _T))) ||
|
| + ((len == 5) && ((ch0 == _f) || (ch0 == _F)))) {
|
| + value = _scanBool(str, ch0, len);
|
| + } else if ((len == 1) && (ch0 == _tilde)) {
|
| + // Special case of null be written as "~'.
|
| + value = null;
|
| + }
|
| }
|
| - }
|
|
|
| - /// Parses a boolean scalar.
|
| - YamlScalar _parseBool(ScalarEvent scalar) {
|
| - var match = _boolRegExp.firstMatch(scalar.value);
|
| - if (match == null) return null;
|
| - return new YamlScalar.internal(
|
| - match.group(1) != null, scalar.span, scalar.style);
|
| - }
|
| -
|
| - /// Parses an integer scalar.
|
| - YamlScalar _parseInt(ScalarEvent scalar) {
|
| - var match = _decimalIntRegExp.firstMatch(scalar.value);
|
| - if (match != null) {
|
| - return new YamlScalar.internal(
|
| - int.parse(match.group(0)), scalar.span, scalar.style);
|
| + // If no value was found, set to the String value.
|
| + if (value == _kNoValue) {
|
| + value = str;
|
| }
|
| + return new YamlScalar.internal(value, span, style);
|
| + }
|
|
|
| - match = _octalIntRegExp.firstMatch(scalar.value);
|
| - if (match != null) {
|
| - var n = int.parse(match.group(1), radix: 8);
|
| - return new YamlScalar.internal(n, scalar.span, scalar.style);
|
| + /// Scan the different versions of the null literal.
|
| + _scanNull(String str, int ch0, int len) {
|
| + if (len == 4) {
|
| + if (((ch0 == _n) && (str == "null")) ||
|
| + ((ch0 == _N) && ((str == "Null") || (str == "NULL")))) {
|
| + return null;
|
| + }
|
| + } else if ((len == 0) || ((len == 1) && (ch0 == _tilde))) {
|
| + return null;
|
| }
|
| + return _kNoValue;
|
| + }
|
|
|
| - match = _hexIntRegExp.firstMatch(scalar.value);
|
| - if (match != null) {
|
| - return new YamlScalar.internal(
|
| - int.parse(match.group(0)), scalar.span, scalar.style);
|
| + /// Scan a boolean.
|
| + _scanBool(String str, int ch0, int len) {
|
| + if (len == 4) {
|
| + if (((ch0 == _t) && (str == "true")) ||
|
| + ((ch0 == _T) && ((str == "True") || (str == "TRUE")))) {
|
| + return true;
|
| + }
|
| + } else if (len == 5) {
|
| + if (((ch0 == _f) && (str == "false")) ||
|
| + ((ch0 == _F) && ((str == "False") || (str == "FALSE")))) {
|
| + return false;
|
| + }
|
| }
|
| -
|
| - return null;
|
| + return _kNoValue;
|
| }
|
|
|
| - /// Parses a floating-point scalar.
|
| - YamlScalar _parseFloat(ScalarEvent scalar) {
|
| - var match = _floatRegExp.firstMatch(scalar.value);
|
| - 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 YamlScalar.internal(
|
| - double.parse(matchStr), scalar.span, scalar.style);
|
| - }
|
|
|
| - match = _infinityRegExp.firstMatch(scalar.value);
|
| - if (match != null) {
|
| - var value = match.group(1) == "-" ? -double.INFINITY : double.INFINITY;
|
| - return new YamlScalar.internal(value, scalar.span, scalar.style);
|
| - }
|
| + /// Helper which returns the known "no value" when number parsing fails.
|
| + static var _onNumError = (_) => null;
|
|
|
| - match = _nanRegExp.firstMatch(scalar.value);
|
| - if (match != null) {
|
| - return new YamlScalar.internal(double.NAN, scalar.span, scalar.style);
|
| + /// Scan a number. Handles both integers and floats within one function if
|
| + /// possible avoid rescanning in the general case.
|
| + _scanNumber(String str, int ch0, int len, bool allow_int, bool allow_float) {
|
| + // Quick check for single digit integers.
|
| + if (len == 1) {
|
| + var val = ch0 - _0;
|
| + if ((val >= 0) && (val <= 9)) {
|
| + return val;
|
| + }
|
| + return _kNoValue;
|
| }
|
| -
|
| - return null;
|
| + // Assume we are scanning a nicely formed number.
|
| + var ch1 = str.codeUnitAt(1);
|
| + if (allow_int && (ch0 == _0) && ((ch1 == _x) || (ch1 == _o))) {
|
| + // Scanning hex or octal integers.
|
| + var sub_str = str.substring(2);
|
| + if (ch1 == _x) {
|
| + var result = int.parse(sub_str, radix:16, onError: _onNumError);
|
| + return (result != null) ? result : _kNoValue;
|
| + }
|
| + var result = int.parse(sub_str, radix: 8, onError: _onNumError);
|
| + return (result != null) ? result : _kNoValue;
|
| + } else if (((ch0 >= _0) && (ch0 <= _9)) ||
|
| + (((ch0 == _plus) || (ch0 == _minus)) &&
|
| + (ch1 >= _0) && (ch1 <= _9))) {
|
| + // Either starting with a number or starting with a sign and number.
|
| + var result = null;
|
| + if (allow_int) {
|
| + // Possible hex or octal numbers are handled above, so only allow
|
| + // radix 10 here.
|
| + result = int.parse(str, radix: 10, onError: _onNumError);
|
| + }
|
| + // If we failed to scan an int, try to scan as a double.
|
| + if (allow_float && (result == null)) {
|
| + result = double.parse(str, _onNumError);
|
| + }
|
| + return (result != null) ? result : _kNoValue;
|
| + } else if (allow_float) {
|
| + // Not the only possibility is to scan a float starting with a dot or
|
| + // a sign and a dot. As well as the signed/unsigned infinity values and
|
| + // not-a-numbers.
|
| + if (((ch0 == _dot) && (ch1 >= _0) && (ch1 <= _9)) ||
|
| + ((ch0 == _minus) || (ch0 == _plus)) && (ch1 == _dot)) {
|
| + // Starting with a . and a number or a sign followed by a dot.
|
| + if (len == 5) {
|
| + if (str == "+.inf" || str == "+.Inf" || str == "+.INF") {
|
| + return double.INFINITY;
|
| + } else if (str == "-.inf" || str == "-.Inf" || str == "-.INF") {
|
| + return -double.INFINITY;
|
| + }
|
| + }
|
| + var result = double.parse(str, _onNumError);
|
| + return (result != null) ? result : _kNoValue;
|
| + } else if ((len == 4) && (ch0 == _dot)) {
|
| + if (str == ".inf" || str == ".Inf" || str == ".INF") {
|
| + return double.INFINITY;
|
| + } else if (str == ".nan" || str == ".NaN" || str == ".NAN") {
|
| + return double.NAN;
|
| + }
|
| + }
|
| + }
|
| + return _kNoValue;
|
| }
|
| -
|
| - /// Parses a string scalar.
|
| - YamlScalar _parseString(ScalarEvent scalar) =>
|
| - new YamlScalar.internal(scalar.value, scalar.span, scalar.style);
|
| }
|
|
|