Done
This commit is contained in:
parent
0643e89b36
commit
3adf8b4c50
4 changed files with 300 additions and 17 deletions
|
|
@ -2,4 +2,59 @@
|
|||
|
||||
namespace MathInterpreter.Core;
|
||||
|
||||
// TODO implement all exception classes
|
||||
public abstract class ExpressionException : Exception
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes <see cref="ExpressionException"/> with the specified error <paramref name="message"/> and the optional <paramref name="innerException"/>
|
||||
/// </summary>
|
||||
/// <param name="message">The error message</param>
|
||||
/// <param name="innerException">The optional exception</param>
|
||||
protected ExpressionException(string message, Exception? innerException = null)
|
||||
: base(message, innerException) { }
|
||||
}
|
||||
|
||||
public sealed class ExpressionFormatException : ExpressionException
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes <see cref="ExpressionFormatException"/> with the specified error <paramref name="message" /> and the optional <paramref name="innerException"/>
|
||||
/// </summary>
|
||||
/// <param name="message">The error message</param>
|
||||
/// <param name="innerException">The innerException</param>
|
||||
public ExpressionFormatException(string message, Exception? innerException = null)
|
||||
: base(message, innerException) { }
|
||||
}
|
||||
|
||||
public sealed class OperatorException : ExpressionException
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes <see cref="OperatorException"/> with the specified error <paramref name="message"/>
|
||||
/// </summary>
|
||||
/// <param name="message">The error message</param>
|
||||
public OperatorException(string message) : base(message) { }
|
||||
}
|
||||
|
||||
public sealed class NumberFormatException : ExpressionException
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes <see cref="NumberFormatException"/> with the <paramref name="expression"/> and the optional <paramref name="reason"/>
|
||||
/// </summary>
|
||||
/// <param name="expression">The expression</param>
|
||||
/// <param name="reason">The optional reason</param>
|
||||
public NumberFormatException(ReadOnlySpan<char> expression, string? reason = null)
|
||||
: base(FormatMessage(expression, reason)) { }
|
||||
|
||||
private static string FormatMessage(ReadOnlySpan<char> expression, string? reason = null)
|
||||
{
|
||||
reason = string.IsNullOrWhiteSpace(reason) ? string.Empty : $" ({reason})";
|
||||
return $"Unable to read number from beginning of '{expression}'{reason}";
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class NumberValueException : ExpressionException
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes <see cref="NumberValueException"/> with the specified error <paramref name="message"/>
|
||||
/// </summary>
|
||||
/// <param name="message">The error message</param>
|
||||
public NumberValueException(string message) : base(message) { }
|
||||
}
|
||||
|
|
@ -15,8 +15,28 @@ public static class ExpressionScanner
|
|||
/// <exception cref="OperatorException">Thrown if none or an unknown operator is found</exception>
|
||||
public static ScanResult<Operator> ScanOperator(ReadOnlySpan<char> expression)
|
||||
{
|
||||
// TODO
|
||||
throw new NotImplementedException();
|
||||
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>(Operator.Plus, 1, expression[(index + 1)..]),
|
||||
'-' => new ScanResult<Operator>(Operator.Minus, 1, expression[(index + 1)..]),
|
||||
'*' => new ScanResult<Operator>(Operator.Multiply, 1, expression[(index + 1)..]),
|
||||
'/' => new ScanResult<Operator>(Operator.Divide, 1, expression[(index + 1)..]),
|
||||
_ => throw new OperatorException($"Unknown operator: {op}")
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -28,8 +48,74 @@ public static class ExpressionScanner
|
|||
/// <exception cref="ExpressionFormatException">Thrown if a problem with the format of the number is found</exception>
|
||||
public static ScanResult<double> ScanOperand(ReadOnlySpan<char> expression)
|
||||
{
|
||||
// TODO
|
||||
throw new NotImplementedException();
|
||||
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<char> remaining = integralResult.RemainingExpression;
|
||||
index = 0;
|
||||
|
||||
while (index < remaining.Length && char.IsWhiteSpace(remaining[index]))
|
||||
{
|
||||
index++;
|
||||
}
|
||||
|
||||
if (index >= remaining.Length || remaining[index] != '.')
|
||||
{
|
||||
return new ScanResult<double>(integralValue, integralResult.Length, remaining);
|
||||
}
|
||||
|
||||
ReadOnlySpan<char> 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<double>(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");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -41,7 +127,70 @@ public static class ExpressionScanner
|
|||
/// <exception cref="NumberFormatException">Thrown if no or an invalid number is found</exception>
|
||||
public static ScanResult<int> ScanNumber(ReadOnlySpan<char> expression, bool allowSign)
|
||||
{
|
||||
// TODO
|
||||
throw new NotImplementedException();
|
||||
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<int>(value * sign, digitCount, expression[index..]);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,13 +8,10 @@ namespace MathInterpreter.Core;
|
|||
/// </summary>
|
||||
public sealed class MathExpression
|
||||
{
|
||||
// TODO
|
||||
/*
|
||||
private double? _leftOperand;
|
||||
private Operator? _operator;
|
||||
private double? _result;
|
||||
private double? _rightOperand;
|
||||
*/
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of <see cref="MathExpression" /> from the provided expression string.
|
||||
|
|
@ -39,8 +36,29 @@ public sealed class MathExpression
|
|||
{
|
||||
get
|
||||
{
|
||||
// TODO
|
||||
throw new NotImplementedException();
|
||||
if (_result.HasValue)
|
||||
{
|
||||
return _result.Value;
|
||||
}
|
||||
|
||||
if (!_leftOperand.HasValue || !_operator.HasValue || !_rightOperand.HasValue)
|
||||
{
|
||||
throw new UnreachableException("Expression is not fully parsed");
|
||||
}
|
||||
|
||||
double left = _leftOperand.Value;
|
||||
double right = _rightOperand.Value;
|
||||
|
||||
_result = _operator.Value switch
|
||||
{
|
||||
Operator.Plus => left + right,
|
||||
Operator.Minus => left - right,
|
||||
Operator.Multiply => left * right,
|
||||
Operator.Divide => left / right,
|
||||
_ => throw new UnreachableException("Unknown operator")
|
||||
};
|
||||
|
||||
return _result.Value;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -55,13 +73,69 @@ public sealed class MathExpression
|
|||
/// </exception>
|
||||
public override string ToString()
|
||||
{
|
||||
// TODO
|
||||
throw new NotImplementedException();
|
||||
if (!_leftOperand.HasValue || !_operator.HasValue || !_rightOperand.HasValue)
|
||||
{
|
||||
throw new UnreachableException("Expression is not fully parsed");
|
||||
}
|
||||
|
||||
string operatorSymbol = _operator.Value switch
|
||||
{
|
||||
Operator.Plus => "+",
|
||||
Operator.Minus => "-",
|
||||
Operator.Multiply => "*",
|
||||
Operator.Divide => "/",
|
||||
_ => throw new UnreachableException("Unknown operator")
|
||||
};
|
||||
|
||||
return $"{_leftOperand.Value} {operatorSymbol} {_rightOperand.Value}";
|
||||
}
|
||||
|
||||
private void ValidateAndParse(ReadOnlySpan<char> expression)
|
||||
{
|
||||
// TODO
|
||||
throw new NotImplementedException();
|
||||
int index = 0;
|
||||
while (index < expression.Length && char.IsWhiteSpace(expression[index]))
|
||||
{
|
||||
index++;
|
||||
}
|
||||
|
||||
if (index < expression.Length)
|
||||
{
|
||||
char firstChar = expression[index];
|
||||
if (firstChar is '+' or '*' or '/')
|
||||
{
|
||||
throw new OperatorException("Operator is missing");
|
||||
}
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var leftOperandResult = ExpressionScanner.ScanOperand(expression);
|
||||
var operatorResult = ExpressionScanner.ScanOperator(leftOperandResult.RemainingExpression);
|
||||
_leftOperand = leftOperandResult.Value;
|
||||
_operator = operatorResult.Value;
|
||||
|
||||
try
|
||||
{
|
||||
var rightOperandResult = ExpressionScanner.ScanOperand(operatorResult.RemainingExpression);
|
||||
_rightOperand = rightOperandResult.Value;
|
||||
|
||||
if (_operator == Operator.Divide && _rightOperand == 0)
|
||||
{
|
||||
throw new NumberValueException("Division by zero is not allowed");
|
||||
}
|
||||
}
|
||||
catch (ExpressionFormatException)
|
||||
{
|
||||
throw new ExpressionFormatException("Right operand is missing");
|
||||
}
|
||||
}
|
||||
catch (ExpressionException)
|
||||
{
|
||||
throw;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
throw new ExpressionFormatException("Unable to parse expression", ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,8 +18,13 @@ public readonly ref struct ScanResult<T> where T : struct
|
|||
/// <exception cref="ArgumentOutOfRangeException">Thrown if an invalid argument is provided</exception>
|
||||
public ScanResult(T value, int length, ReadOnlySpan<char> remainingExpression)
|
||||
{
|
||||
if (length < 0)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(length), "Length must be non-negative");
|
||||
}
|
||||
|
||||
Value = value;
|
||||
Length = -1; // TODO
|
||||
Length = length;
|
||||
RemainingExpression = remainingExpression;
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue