Пример #1
0
        /// <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);
        }
Пример #2
0
        /// <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);
        }
Пример #3
0
        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());
        }
Пример #4
0
        /// <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);
        }
Пример #5
0
        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);
        }
Пример #6
0
        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;
                    }
                }