/// <summary> /// Get [sign] part /// </summary> /// <param name="text"></param> /// <returns></returns> private static List <NumberPart> GetSignParts(string text) { if (string.IsNullOrEmpty(text)) { return(null); } var numberParts = new List <NumberPart>(); var prefixWhiteSpaces = GetPrefixWhiteSpaces(text); var inputText = text.Substring(prefixWhiteSpaces.Length); if (!string.IsNullOrEmpty(prefixWhiteSpaces)) { var numberPart = new NumberPart { Value = prefixWhiteSpaces, Type = NumberPart.NumberType.WhiteSpace }; numberParts.Add(numberPart); } var signRegex = new Regex(@"^\s*[+-]+", RegexOptions.IgnoreCase | RegexOptions.Singleline); var match = signRegex.Match(inputText); if (match.Success) { var numberPart = new NumberPart { Value = match.Value, Type = NumberPart.NumberType.Sign }; numberParts.Add(numberPart); } return(numberParts); }
/// <summary> /// Get [ws] [$] part /// </summary> /// <param name="text"></param> /// <returns></returns> private List <NumberPart> GetCurrencyParts(string text) { if (string.IsNullOrEmpty(text)) { return(null); } var numberParts = new List <NumberPart>(); var prefixWhiteSpaces = GetPrefixWhiteSpaces(text); var inputText = text.Substring(prefixWhiteSpaces.Length); if (!string.IsNullOrEmpty(prefixWhiteSpaces)) { var numberPart = new NumberPart { Value = prefixWhiteSpaces, Type = NumberPart.NumberType.WhiteSpace }; numberParts.Add(numberPart); } foreach (var currencySymbol in _currencySymbols) { if (inputText.StartsWith(currencySymbol, StringComparison.OrdinalIgnoreCase)) { var numberPart = new NumberPart { Value = currencySymbol, Type = NumberPart.NumberType.Currency }; numberParts.Add(numberPart); break; } } return(numberParts); }
public override string ToString() { var strFlt = new StringBuilder(); if (PrependZeroCount > 0) { for (var i = 0; i < PrependZeroCount; i++) { strFlt.Append('0'); } } strFlt.Append(NumberPart.ToString()); return(strFlt.ToString()); }
/// <summary> /// Get [integral-digits,]integral-digits[.[fractional-digits]] parts /// </summary> /// <param name="text"></param> /// <returns></returns> private List <NumberPart> GetIntegralAndFractionalParts(string text) { var parts = new List <NumberPart>(); var sections = SplitMultiCharSeparators(text); foreach (var part in sections) { if (part.Type == NumberPart.NumberType.Separator) { parts.Add(part); continue; } foreach (var chr in part.Value.ToCharArray()) { var type = GetNumberType(chr); var previousPart = parts.LastOrDefault(); if (previousPart != null && type == NumberPart.NumberType.Number && previousPart.Type == NumberPart.NumberType.Number) { previousPart.Value += chr; } else { var valuePart = new NumberPart { Value = chr.ToString(), Type = type }; valuePart.Message = valuePart.Type == NumberPart.NumberType.Invalid ? string.Format(PluginResources.NumberParser_Message_SeparatorIsNotRecognized, chr) : null; parts.Add(valuePart); } } } AssignNumericSeparatorTypes(parts); return(parts); }
private static List <NumberPart> CombineParts(IReadOnlyCollection <NumberPart> currencyParts, IReadOnlyCollection <NumberPart> signParts, IReadOnlyCollection <NumberPart> integralAndFractionalParts, NumberPart exponentialPart) { var numberParts = new List <NumberPart>(); if (currencyParts != null && integralAndFractionalParts.Count > 0) { numberParts.AddRange(currencyParts); } if (signParts != null && integralAndFractionalParts.Count > 0) { numberParts.AddRange(signParts); } if (integralAndFractionalParts != null && integralAndFractionalParts.Count > 0) { numberParts.AddRange(integralAndFractionalParts); } if (exponentialPart != null) { numberParts.Add(exponentialPart); } return(numberParts); }
public ValueParseResult TryParse(ReadOnlySpan <byte> readerSpan, out double result, out int consumedLength, out int lineSpan, out int colSpan) { result = double.NaN; consumedLength = 0; lineSpan = 1; colSpan = 0; // if span is empty, and no parsing occured, signal EOF immediately if (readerSpan.Length <= 0 && this._lastPart == NumberPart.None) { return(ValueParseResult.EOF); } // if we are not continuing, check what we're parsing if (this.Buffer.Length == 0) { switch (readerSpan[consumedLength++]) { // a number in JSON can begin with - or digits 0-9 case JsonTokens.NumberSign: this._currentStructure = NumberStructure.HasSign; this._lastPart = NumberPart.NumberSign; break; // digit zero is a bit special in that if it's the first digit in a number, it becomes the only // legal digit before decimal point, hence special handling for it case JsonTokens.Digit0: this._currentStructure = NumberStructure.LeadingZero; this._lastPart = NumberPart.FirstDigit; break; // digits 1-9 are also valid as starting characters of a number, and unlike 0, they do not // restrict pre-decimal point digit count (though IEEE754 64-bit binary float limits still apply) case JsonTokens.Digit1: case JsonTokens.Digit2: case JsonTokens.Digit3: case JsonTokens.Digit4: case JsonTokens.Digit5: case JsonTokens.Digit6: case JsonTokens.Digit7: case JsonTokens.Digit8: case JsonTokens.Digit9: this._currentStructure = NumberStructure.LeadingNonzero; this._lastPart = NumberPart.FirstDigit; break; // not a legal character default: if (Rune.DecodeFromUtf8(readerSpan, out var rune, out _) != OperationStatus.Done) { rune = default; } return(ValueParseResult.Failure("Unexpected token, expected 0-9 or -.", rune)); } } // if we got empty when previous parsing occured, just don't parse, it's an end-of-content marker var completedParsing = false; if (readerSpan.Length > 0) { var offByOne = false; // try reading the number while (consumedLength < readerSpan.Length) { switch (readerSpan[consumedLength++]) { // digit 0 is special // if it's the first digit in the non-fractional part, it is the only legal digit before decimal point // otherwise it behaves like a regular digit // this means it can appear: // - as first digit before decimal point // - as non-first digit before decimal point, if first digit was not a 0 // - as a digit after decimal point before exponent mark // - as a digit after exponent mark or exponent sign // see: https://www.json.org/img/number.png case JsonTokens.Digit0: if (this._lastPart == NumberPart.FirstDigit && this._currentStructure.HasFlag(NumberStructure.LeadingZero)) { return(_cleanup(this, ValueParseResult.Failure("Digit in illegal separator. Expected decimal point.", new Rune(readerSpan[consumedLength - 1])))); } if (this._lastPart == NumberPart.NumberSign) { this._currentStructure |= NumberStructure.LeadingZero; this._lastPart = NumberPart.FirstDigit; } else { this._lastPart = this._lastPart switch { NumberPart.FirstDigit => NumberPart.Digit, NumberPart.FractionDot => NumberPart.FractionDigit, NumberPart.ExponentMarker or NumberPart.ExponentSign => NumberPart.ExponentDigit, _ => this._lastPart }; } break; // non-0 digits can appear: // - as first digit before decimal points // - as non-first digit before decimal point, if first digit was not a 0 // - as a digit after decimal point before exponent mark // - as a digit after exponent mark or exponent sign // see: https://www.json.org/img/number.png case JsonTokens.Digit1: case JsonTokens.Digit2: case JsonTokens.Digit3: case JsonTokens.Digit4: case JsonTokens.Digit5: case JsonTokens.Digit6: case JsonTokens.Digit7: case JsonTokens.Digit8: case JsonTokens.Digit9: if (this._lastPart == NumberPart.FirstDigit && this._currentStructure.HasFlag(NumberStructure.LeadingZero)) { return(_cleanup(this, ValueParseResult.Failure("Digit in illegal separator. Expected decimal point.", new Rune(readerSpan[consumedLength - 1])))); } if (this._lastPart == NumberPart.NumberSign) { this._currentStructure |= NumberStructure.LeadingNonzero; this._lastPart = NumberPart.FirstDigit; } else { this._lastPart = this._lastPart switch { NumberPart.FirstDigit => NumberPart.Digit, NumberPart.FractionDot => NumberPart.FractionDigit, NumberPart.ExponentMarker or NumberPart.ExponentSign => NumberPart.ExponentDigit, _ => this._lastPart }; } break; // decimal separator can appear only after at least one digit, and only once case JsonTokens.DecimalSeparator: if (this._lastPart != NumberPart.Digit && this._lastPart != NumberPart.FirstDigit) { return(_cleanup(this, ValueParseResult.Failure("Unexpected decimal separator.", new Rune('.')))); } this._currentStructure |= NumberStructure.Fraction; this._lastPart = NumberPart.FractionDot; break; // exponent marker can appear only after at least one digit, or at least one digit after // decimal point, and only once, regardless of variety case JsonTokens.ExponentSmall: case JsonTokens.ExponentCapital: if (this._lastPart != NumberPart.FirstDigit && this._lastPart != NumberPart.Digit && this._lastPart != NumberPart.FractionDigit) { return(_cleanup(this, ValueParseResult.Failure("Unexpected exponent marker.", new Rune(readerSpan[consumedLength - 1])))); } this._currentStructure |= NumberStructure.Exponent; this._lastPart = NumberPart.ExponentMarker; if (this._currentStructure.HasFlag(NumberStructure.Fraction)) { this._currentStructure |= NumberStructure.FractionValid; } break; // exponent sign can appear only after exponent marker case JsonTokens.NumberSign: case JsonTokens.ExponentSignPositive: if (this._lastPart != NumberPart.ExponentMarker) { return(_cleanup(this, ValueParseResult.Failure("Unexpected exponent sign.", new Rune(readerSpan[consumedLength - 1])))); } this._currentStructure |= NumberStructure.SignedExponent; this._lastPart = NumberPart.ExponentSign; break; // this is a situation where a non number-character is encountered // this is invalid if immediately after number sign, decimal point, exponent marker, or // exponent sign, otherwise consider it a completed number default: switch (this._lastPart) { case NumberPart.NumberSign: case NumberPart.FractionDot: case NumberPart.ExponentMarker: case NumberPart.ExponentSign: if (Rune.DecodeFromUtf8(readerSpan[(consumedLength - 1)..], out var rune, out _) != OperationStatus.Done) { rune = default; } return(_cleanup(this, ValueParseResult.Failure("Unexpected token, expected 0-9.", rune))); } offByOne = true; completedParsing = true; break; } // if parsing is completed, do not attempt to resume if (completedParsing) { break; } }