diff --git a/MathInterpreter/Core/Exceptions.cs b/MathInterpreter/Core/Exceptions.cs
index f6770ba..6ce7a78 100644
--- a/MathInterpreter/Core/Exceptions.cs
+++ b/MathInterpreter/Core/Exceptions.cs
@@ -2,4 +2,59 @@
namespace MathInterpreter.Core;
-// TODO implement all exception classes
+public abstract class ExpressionException : Exception
+{
+ ///
+ /// Initializes with the specified error and the optional
+ ///
+ /// The error message
+ /// The optional exception
+ protected ExpressionException(string message, Exception? innerException = null)
+ : base(message, innerException) { }
+}
+
+public sealed class ExpressionFormatException : ExpressionException
+{
+ ///
+ /// Initializes with the specified error and the optional
+ ///
+ /// The error message
+ /// The innerException
+ public ExpressionFormatException(string message, Exception? innerException = null)
+ : base(message, innerException) { }
+}
+
+public sealed class OperatorException : ExpressionException
+{
+ ///
+ /// Initializes with the specified error
+ ///
+ /// The error message
+ public OperatorException(string message) : base(message) { }
+}
+
+public sealed class NumberFormatException : ExpressionException
+{
+ ///
+ /// Initializes with the and the optional
+ ///
+ /// The expression
+ /// The optional reason
+ public NumberFormatException(ReadOnlySpan expression, string? reason = null)
+ : base(FormatMessage(expression, reason)) { }
+
+ private static string FormatMessage(ReadOnlySpan 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
+{
+ ///
+ /// Initializes with the specified error
+ ///
+ /// The error message
+ public NumberValueException(string message) : base(message) { }
+}
\ No newline at end of file
diff --git a/MathInterpreter/Core/ExpressionScanner.cs b/MathInterpreter/Core/ExpressionScanner.cs
index 430f2dd..026e14f 100644
--- a/MathInterpreter/Core/ExpressionScanner.cs
+++ b/MathInterpreter/Core/ExpressionScanner.cs
@@ -15,8 +15,28 @@ public static class ExpressionScanner
/// Thrown if none or an unknown operator is found
public static ScanResult ScanOperator(ReadOnlySpan 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.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}")
+ };
}
///
@@ -28,8 +48,74 @@ public static class ExpressionScanner
/// Thrown if a problem with the format of the number is found
public static ScanResult ScanOperand(ReadOnlySpan 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 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");
+ }
}
///
@@ -41,7 +127,70 @@ public static class ExpressionScanner
/// Thrown if no or an invalid number is found
public static ScanResult ScanNumber(ReadOnlySpan 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(value * sign, digitCount, expression[index..]);
}
}
diff --git a/MathInterpreter/Core/MathExpression.cs b/MathInterpreter/Core/MathExpression.cs
index 388c4e5..3392223 100644
--- a/MathInterpreter/Core/MathExpression.cs
+++ b/MathInterpreter/Core/MathExpression.cs
@@ -8,13 +8,10 @@ namespace MathInterpreter.Core;
///
public sealed class MathExpression
{
- // TODO
- /*
private double? _leftOperand;
private Operator? _operator;
private double? _result;
private double? _rightOperand;
- */
///
/// Creates a new instance of 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
///
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 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);
+ }
}
}
diff --git a/MathInterpreter/Core/ScanResult.cs b/MathInterpreter/Core/ScanResult.cs
index 6d9f645..dca8db9 100644
--- a/MathInterpreter/Core/ScanResult.cs
+++ b/MathInterpreter/Core/ScanResult.cs
@@ -18,8 +18,13 @@ public readonly ref struct ScanResult where T : struct
/// Thrown if an invalid argument is provided
public ScanResult(T value, int length, ReadOnlySpan remainingExpression)
{
+ if (length < 0)
+ {
+ throw new ArgumentOutOfRangeException(nameof(length), "Length must be non-negative");
+ }
+
Value = value;
- Length = -1; // TODO
+ Length = length;
RemainingExpression = remainingExpression;
}