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() };
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() };
/// <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; }
/// <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);
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)); }
public AmountRangeToken(AmountToken amountFrom, AmountToken amountTo, params Token[] tokens) : base(amountTo.Amount, tokens) { AmountFrom = amountFrom; AmountTo = amountTo; }