using System; namespace MathInterpreter.Core; /// /// Provides utility methods for scanning character streams for certain parts of a maths expression /// public static class ExpressionScanner { /// /// Scans the given expression for an operator (+, -, *, /) /// /// Expression to process /// A scan result containing the operator found and the remaining part of the expression /// Thrown if none or an unknown operator is found public static ScanResult ScanOperator(ReadOnlySpan expression) { int index = 0; while (index < expression.Length && char.IsWhiteSpace(expression[index])) { index++; } if (index >= expression.Length) { throw new OperatorException("Empty expression"); } char op = expression[index]; return op switch { '+' => new ScanResult(Operator.Plus, 1, expression[(index + 1)..]), '-' => new ScanResult(Operator.Minus, 1, expression[(index + 1)..]), '*' => new ScanResult(Operator.Multiply, 1, expression[(index + 1)..]), '/' => new ScanResult(Operator.Divide, 1, expression[(index + 1)..]), _ => throw new OperatorException($"Unknown operator: {op}") }; } /// /// Scans the given expression for an operand which is a number which have to have an integral /// part and may have a fractional part. It may also have a sign (+, -) in front of it. /// /// Expression to process /// A scan result containing the parsed number and the remaining part expression /// Thrown if a problem with the format of the number is found public static ScanResult ScanOperand(ReadOnlySpan expression) { int index = 0; while (index < expression.Length && char.IsWhiteSpace(expression[index])) { index++; } if (index >= expression.Length) { throw new ExpressionFormatException("Operand is missing"); } if (expression[index] == '.') { throw new ExpressionFormatException("Unable to parse integral part of number"); } if (!(char.IsDigit(expression[index]) || (index < expression.Length - 1 && (expression[index] == '+' || expression[index] == '-') && char.IsDigit(expression[index + 1])))) { throw new ExpressionFormatException("Operand is missing"); } try { var integralResult = ScanNumber(expression, true); int integralValue = integralResult.Value; bool isNegative = integralValue < 0; ReadOnlySpan remaining = integralResult.RemainingExpression; index = 0; while (index < remaining.Length && char.IsWhiteSpace(remaining[index])) { index++; } if (index >= remaining.Length || remaining[index] != '.') { return new ScanResult(integralValue, integralResult.Length, remaining); } ReadOnlySpan fractionalPart = remaining[(index + 1)..]; try { var fractionalResult = ScanNumber(fractionalPart, false); double fractionalValue = fractionalResult.Value / Math.Pow(10, fractionalResult.Length); double result = Math.Abs(integralValue) + fractionalValue; if (isNegative) { result = -result; } return new ScanResult(result, integralResult.Length + 1 + fractionalResult.Length, fractionalResult.RemainingExpression); } catch (NumberFormatException) { throw new ExpressionFormatException("Unable to parse fractional part of number"); } } catch (NumberFormatException) { throw new ExpressionFormatException("Unable to parse integral part of number"); } } /// /// Scans the given expression for a whole number which may have a sign (+, -) in front of it /// /// Expression to process /// Flag indicating if a pre-fix sign (+, -) is allowed or not /// A scan result containing the parsed number and the remaining part expression /// Thrown if no or an invalid number is found public static ScanResult ScanNumber(ReadOnlySpan expression, bool allowSign) { int index = 0; int sign = 1; while (index < expression.Length && char.IsWhiteSpace(expression[index])) { index++; } if (index < expression.Length) { if (expression[index] == '+' || expression[index] == '-') { if (!allowSign) { throw new NumberFormatException(expression, "Sign is not allowed here"); } if (expression[index] == '-') { sign = -1; } index++; while (index < expression.Length && char.IsWhiteSpace(expression[index])) { index++; } if (index < expression.Length && (expression[index] == '+' || expression[index] == '-')) { throw new NumberFormatException(expression, "Sign may occur only once"); } } } int value = 0; int digitCount = 0; while (index < expression.Length) { char c = expression[index]; if (char.IsDigit(c)) { int digit = (int)char.GetNumericValue(c); value = value * 10 + digit; index++; digitCount++; } else if (char.IsWhiteSpace(c)) { index++; } else { break; } } if (digitCount == 0) { throw new NumberFormatException(expression); } return new ScanResult(value * sign, digitCount, expression[index..]); } }