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..]);
}
}