private bool Literal(InputBuffer buffer, out LiteralAmountToken literal)
        {
            var seenDecimalPoint = false;
            var digits           = new StringBuilder();

            // TODO: Would be good for this to use rules like the other parts
            // of the grammar, but for now let's keep it simple.
            while (buffer.HasNext() && (buffer.IsDigit() || buffer.Peek() == '.'))
            {
                var c = buffer.Next();

                if (c == '.' && seenDecimalPoint)
                {
                    // We only accept a single decimal point.
                    literal = null;

                    return(false);
                }

                seenDecimalPoint = c == '.';

                digits.Append(c);
            }

            if (!decimal.TryParse(digits.ToString(), out var amount))
            {
                literal = null;

                return(false);
            }

            literal = AmountToken.Literal(amount);

            return(true);
        }
        // fraction = literal-literal/literal | literal literal/literal | literal/literal
        private ParserRule[] GetFractionRules() => new ParserRule[]
        {
            // Mixed number fraction rule e.g. 1 1/2
            ParserRuleBuilder
            .New()
            .Token <LiteralAmountToken>(Literal)
            .Condition(buffer => buffer.TryConsume(' ') || buffer.TryConsume('-'))
            .Token <LiteralAmountToken>(Literal)
            .Condition(buffer => buffer.TryConsume('/'))
            .Token <LiteralAmountToken>(Literal)
            .Map(tokens =>
            {
                var wholeNumber = tokens[0] as LiteralAmountToken;
                var numerator   = tokens[1] as LiteralAmountToken;
                var denominator = tokens[2] as LiteralAmountToken;

                return(ParserRuleResult.Success(AmountToken.Fraction(wholeNumber, numerator, denominator)));
            })
            .Build(),

            // Standard fraction rule e.g. 1/2
            ParserRuleBuilder
            .New()
            .Token <LiteralAmountToken>(Literal)
            .Condition(buffer => buffer.TryConsume('/'))
            .Token <LiteralAmountToken>(Literal)
            .Map(tokens =>
            {
                var numerator   = tokens[0] as LiteralAmountToken;
                var denominator = tokens[1] as LiteralAmountToken;

                return(ParserRuleResult.Success(AmountToken.Fraction(wholeNumber: null, numerator, denominator)));
            })
            .Build()
        };
예제 #3
0
        IEnumerable <Token> RecognizeAmountRange(IEnumerable <Token> tokens, int start = 0)
        {
            if (start >= tokens.Count())
            {
                return(tokens);
            }

            AmountToken amountFrom = null;
            AmountToken amountTo   = null;
            Token       symbol     = null;

            var first = start;
            var i     = 0;

            for (; i < tokens.Count(); i++)
            {
                var token = tokens.ElementAt(i);

                if (amountFrom == null)
                {
                    if (token.Type == TokenType.Number)
                    {
                        first      = i;
                        amountFrom = token as AmountToken ?? new AmountToken(token);
                    }
                    continue;
                }

                if (symbol == null)
                {
                    if (token.Type == TokenType.Symbol && token.Value == "-")
                    {
                        symbol = token;
                        continue;
                    }
                    break;
                }

                if (token.Type == TokenType.Number)
                {
                    amountTo = token as AmountToken ?? new AmountToken(token);
                }
                break;
            }

            List <Token> adjusted = null;

            if (amountFrom != null &&
                symbol != null &&
                amountTo != null)
            {
                adjusted = tokens.Take(first).ToList();
                adjusted.Add(new AmountRangeToken(amountFrom, amountTo, amountFrom, symbol, amountTo));
                adjusted.AddRange(tokens.Skip(i + 1));
            }

            return(adjusted == null ? tokens : RecognizeAmountRange(adjusted, first));
        }
        /// <summary>
        /// Attempts to parse <paramref name="rawAmount"/> as an <see cref="AmountToken"/>.
        /// </summary>
        /// <param name="rawAmount">The raw amount to parse.</param>
        /// <param name="token">
        /// When this method returns, contains the token that was parsed from the buffer if
        /// the parsing succeeded, or <see langword="null"/> if the parsing failed.
        /// </param>
        /// <returns>
        /// <see langword="true"/> when the parsing succeeded; <see langword="false"/> otherwise.
        /// </returns>
        public bool TryParse(string rawAmount, out AmountToken token)
        {
            if (string.IsNullOrEmpty(rawAmount))
            {
                token = null;

                return(false);
            }

            var buffer = new InputBuffer(rawAmount);

            return(Amount(buffer, out token));
        }
        // range = fraction-fraction | literal-literal
        private ParserRule[] GetRangeRules() => new ParserRule[]
        {
            // Fractional range e.g. 1/4-1/3
            ParserRuleBuilder
            .New()
            .Token <FractionalAmountToken>(Fraction)
            .Condition(buffer => buffer.OptionallyConsume(' '))
            .Condition(buffer => buffer.TryConsume('-'))
            .Condition(buffer => buffer.OptionallyConsume(' '))
            .Token <FractionalAmountToken>(Fraction)
            .Map(tokens =>
            {
                var lowerBound = tokens[0] as FractionalAmountToken;
                var upperBound = tokens[1] as FractionalAmountToken;

                return(ParserRuleResult.Success(AmountToken.Range(lowerBound, upperBound)));
            })
            .Build(),

            // Literal range e.g. 1-2
            ParserRuleBuilder
            .New()
            .Token <LiteralAmountToken>(Literal)
            .Condition(buffer => buffer.OptionallyConsume(' '))
            .Condition(buffer => buffer.TryConsume('-'))
            .Condition(buffer => buffer.OptionallyConsume(' '))
            .Token <LiteralAmountToken>(Literal)
            .Map(tokens =>
            {
                var lowerBound = tokens[0] as LiteralAmountToken;
                var upperBound = tokens[1] as LiteralAmountToken;

                return(ParserRuleResult.Success(AmountToken.Range(lowerBound, upperBound)));
            })
            .Build()
        };
예제 #6
0
 /// <summary>
 /// Initializes a new instance of the <see cref="RangeAmountToken"/> class.
 /// </summary>
 /// <param name="lowerBound">
 /// A <see cref="FractionalAmountToken"/> instance representing the lower bound of the range.
 /// </param>
 /// <param name="upperBound">
 /// A <see cref="FractionalAmountToken"/> instance representing the upper bound of the range.
 /// </param>
 internal RangeAmountToken(FractionalAmountToken lowerBound, FractionalAmountToken upperBound)
 {
     LowerBound = lowerBound;
     UpperBound = upperBound;
 }
예제 #7
0
 /// <summary>
 /// Initializes a new instance of the <see cref="RangeAmountToken"/> class.
 /// </summary>
 /// <param name="lowerBound">
 /// A <see cref="LiteralAmountToken"/> instance representing the lower bound of the range.
 /// </param>
 /// <param name="upperBound">
 /// A <see cref="LiteralAmountToken"/> instance representing the upper bound of the range.
 /// </param>
 internal RangeAmountToken(LiteralAmountToken lowerBound, LiteralAmountToken upperBound)
 {
     LowerBound = lowerBound;
     UpperBound = upperBound;
 }
 private bool Amount(InputBuffer buffer, out AmountToken amount) =>
 ExecuteRules(GetAmountRules, buffer, out amount);
예제 #9
0
        private IEnumerable <Token> RecognizeAmountUnitTokens(IEnumerable <Token> tokens, int start = 0)
        {
            if (start >= tokens.Count())
            {
                return(tokens);
            }

            AmountToken amount = null;
            UnitToken   unit   = null;
            var         first  = tokens.Count() - 1;
            var         i      = start;

            for (; i < tokens.Count(); i++)
            {
                var token = tokens.ElementAt(i);
                if (amount == null)
                {
                    if (token.Type == TokenType.Number && !(token is AmountUnitToken))
                    {
                        first = i;
                        if (token is AmountToken)
                        {
                            amount = (AmountToken)token;
                        }
                        else
                        {
                            amount = new AmountToken(token);
                        }
                    }
                }
                else
                {
                    if (token.Type == TokenType.Symbol && token.Value == "-")
                    {
                        continue;
                    }

                    if (token.Type == TokenType.Unit)
                    {
                        unit = (UnitToken)token;
                    }
                    break;
                }

                if (token.Type == TokenType.Unit)
                {
                    if (amount == null)
                    {
                        first = i;
                    }

                    unit = (UnitToken)token;
                    break;
                }
            }

            List <Token> adjusted = null;

            if (amount != null)
            {
                adjusted = tokens.Take(first).ToList();
                adjusted.Add(new AmountUnitToken(
                                 amount.Amount,
                                 unit,
                                 (unit == null ? new Token[] { amount } : new Token[] { amount, unit })));
                adjusted.AddRange(tokens.Skip(first + 1 + (unit == null ? 0 : i - first)));
            }
            else if (unit != null)
            {
                adjusted = tokens.Take(first).ToList();
                adjusted.Add(new AmountUnitToken(
                                 1f,
                                 unit,
                                 new Token[] { unit }));
                adjusted.AddRange(tokens.Skip(first + 1));
            }

            return(RecognizeAmountUnitTokens(adjusted ?? tokens, first + 1));
        }
예제 #10
0
 public AmountRangeToken(AmountToken amountFrom, AmountToken amountTo, params Token[] tokens)
     : base(amountTo.Amount, tokens)
 {
     AmountFrom = amountFrom;
     AmountTo   = amountTo;
 }