233 lines
8 KiB
C#
233 lines
8 KiB
C#
using MathInterpreter.Core;
|
|
|
|
namespace MathInterpreter.Test;
|
|
|
|
public sealed class ExpressionScannerTests
|
|
{
|
|
[Theory]
|
|
[InlineData("3abc", 3, 1, "abc")]
|
|
[InlineData("35gh", 35, 2, "gh")]
|
|
[InlineData("3afc2", 3, 1, "afc2")]
|
|
[InlineData(" 3abc", 3, 1, "abc")]
|
|
[InlineData("2 3abc", 23, 2, "abc")]
|
|
[InlineData("2 3 abc", 23, 2, "abc")]
|
|
[InlineData("78944", 78944, 5, "")]
|
|
[InlineData("12?!", 12, 2, "?!")]
|
|
public void ScanNumber_Simple_Unsigned(string expression, int expectedValue, int expectedLength,
|
|
string expectedRemainingExpression)
|
|
{
|
|
ScanResult<int> result = ExpressionScanner.ScanNumber(expression.AsSpan(), false);
|
|
|
|
result.Value.Should().Be(expectedValue);
|
|
result.Length.Should().Be(expectedLength);
|
|
result.RemainingExpression.ToString().Should().Be(expectedRemainingExpression);
|
|
}
|
|
|
|
[Theory]
|
|
[InlineData("+3abc", 3, 1, "abc")]
|
|
[InlineData("+5ab3c", 5, 1, "ab3c")]
|
|
[InlineData("+356abc", 356, 3, "abc")]
|
|
[InlineData("-3abc", -3, 1, "abc")]
|
|
[InlineData("-8ab2c", -8, 1, "ab2c")]
|
|
[InlineData("-37abc", -37, 2, "abc")]
|
|
[InlineData(" - 3abc", -3, 1, "abc")]
|
|
[InlineData("+ 3abc", 3, 1, "abc")]
|
|
public void ScanNumber_Simple_Signed(string expression, int expectedValue, int expectedLength,
|
|
string expectedRemainingExpression)
|
|
{
|
|
ScanResult<int> result = ExpressionScanner.ScanNumber(expression.AsSpan(), true);
|
|
|
|
result.Value.Should().Be(expectedValue);
|
|
result.Length.Should().Be(expectedLength);
|
|
result.RemainingExpression.ToString().Should().Be(expectedRemainingExpression);
|
|
}
|
|
|
|
[Theory]
|
|
[InlineData("cba")]
|
|
[InlineData("c1b2a3")]
|
|
[InlineData(" t")]
|
|
[InlineData(" t2")]
|
|
public void ScanNumber_NoDigits(string invalidExpression)
|
|
{
|
|
var action = BuildScanNumberWrapper(invalidExpression, false);
|
|
|
|
action.Should().Throw<NumberFormatException>()
|
|
.WithMessage($"Unable to read number from beginning of '{invalidExpression}'");
|
|
}
|
|
|
|
[Theory]
|
|
[InlineData("+56")]
|
|
[InlineData("-90")]
|
|
public void ScanNumber_SignNotAllowed(string invalidExpression)
|
|
{
|
|
var action = BuildScanNumberWrapper(invalidExpression, false);
|
|
|
|
action.Should().Throw<NumberFormatException>()
|
|
.WithMessage($"Unable to read number from beginning of '{invalidExpression}' " +
|
|
"(Sign is not allowed here)");
|
|
}
|
|
|
|
[Theory]
|
|
[InlineData("++56")]
|
|
[InlineData("+-56")]
|
|
[InlineData("--90")]
|
|
[InlineData("-+90")]
|
|
public void ScanNumber_MultipleSignNotAllowed(string invalidExpression)
|
|
{
|
|
var action = BuildScanNumberWrapper(invalidExpression, true);
|
|
|
|
action.Should().Throw<NumberFormatException>()
|
|
.WithMessage($"Unable to read number from beginning of '{invalidExpression}' " +
|
|
"(Sign may occur only once)");
|
|
}
|
|
|
|
[Theory]
|
|
[InlineData("a+56")]
|
|
[InlineData("b-56")]
|
|
[InlineData("hello")]
|
|
[InlineData("-")]
|
|
[InlineData("+")]
|
|
[InlineData(" ")]
|
|
[InlineData(" a ")]
|
|
[InlineData(" a6 ")]
|
|
[InlineData("?1")]
|
|
[InlineData("")]
|
|
public void ScanNumber_Invalid(string invalidExpression)
|
|
{
|
|
var action = BuildScanNumberWrapper(invalidExpression, true);
|
|
|
|
action.Should().Throw<NumberFormatException>()
|
|
.WithMessage($"Unable to read number from beginning of '{invalidExpression}'");
|
|
}
|
|
|
|
[Theory]
|
|
[InlineData("+", Operator.Plus, "")]
|
|
[InlineData("-abc", Operator.Minus, "abc")]
|
|
[InlineData("* abc", Operator.Multiply, " abc")]
|
|
[InlineData("/abc ", Operator.Divide, "abc ")]
|
|
public void ScanOperator_Simple(string expression, Operator expectedOperator, string remainingExpression)
|
|
{
|
|
ScanResult<Operator> result = ExpressionScanner.ScanOperator(expression);
|
|
|
|
result.Value.Should().Be(expectedOperator);
|
|
result.Length.Should().Be(1);
|
|
result.RemainingExpression.ToString().Should().Be(remainingExpression);
|
|
}
|
|
|
|
[Theory]
|
|
[InlineData("&")]
|
|
[InlineData("abc")]
|
|
[InlineData(" abc")]
|
|
[InlineData("g /")]
|
|
public void ScanOperator_Invalid(string expression)
|
|
{
|
|
var action = BuildScanOperatorWrapper(expression);
|
|
|
|
action.Should().Throw<OperatorException>()
|
|
.WithMessage($"Unknown operator: {expression.TrimStart()[0]}");
|
|
}
|
|
|
|
[Fact]
|
|
public void ScanOperator_Empty()
|
|
{
|
|
var action = BuildScanOperatorWrapper(string.Empty);
|
|
|
|
action.Should().Throw<OperatorException>().WithMessage("Empty expression");
|
|
}
|
|
|
|
[Theory]
|
|
[InlineData("123abc", 123, 3, "abc")]
|
|
[InlineData("23.49cba", 23.49, 5, "cba")]
|
|
[InlineData("0.45ab2c", 0.45, 4, "ab2c")]
|
|
[InlineData("0.452ab1c", 0.452, 5, "ab1c")]
|
|
public void ScanOperand_Simple(string expression, double expectedValue, int expectedLength,
|
|
string remainingExpression)
|
|
{
|
|
ScanResult<double> result = ExpressionScanner.ScanOperand(expression);
|
|
|
|
result.Value.Should().BeApproximately(expectedValue, double.Epsilon);
|
|
result.Length.Should().Be(expectedLength);
|
|
result.RemainingExpression.ToString().Should().Be(remainingExpression);
|
|
}
|
|
|
|
[Theory]
|
|
[InlineData("+123", 123, 3, "")]
|
|
[InlineData("+123abc", 123, 3, "abc")]
|
|
[InlineData("+123.12abc", 123.12, 6, "abc")]
|
|
[InlineData("-123", -123, 3, "")]
|
|
[InlineData("-123cb", -123, 3, "cb")]
|
|
[InlineData("-123.89cb", -123.89, 6, "cb")]
|
|
public void ScanOperand_Simple_Signed(string expression, double expectedValue, int expectedLength,
|
|
string remainingExpression)
|
|
{
|
|
ScanResult<double> result = ExpressionScanner.ScanOperand(expression);
|
|
|
|
result.Value.Should().BeApproximately(expectedValue, double.Epsilon);
|
|
result.Length.Should().Be(expectedLength);
|
|
result.RemainingExpression.ToString().Should().Be(remainingExpression);
|
|
}
|
|
|
|
[Theory]
|
|
[InlineData("abc")]
|
|
[InlineData("")]
|
|
[InlineData(" ")]
|
|
[InlineData("cb43")]
|
|
public void ScanOperand_Invalid_NoNumber(string expression)
|
|
{
|
|
BuildScanOperandWrapper(expression).Should()
|
|
.Throw<ExpressionFormatException>()
|
|
.WithMessage("Operand is missing");
|
|
}
|
|
|
|
[Theory]
|
|
[InlineData(".12")]
|
|
[InlineData(" .12")]
|
|
[InlineData(".12.55")]
|
|
public void ScanOperand_Invalid_Integral(string expression)
|
|
{
|
|
BuildScanOperandWrapper(expression).Should()
|
|
.Throw<ExpressionFormatException>()
|
|
.WithMessage("Unable to parse integral part of number");
|
|
}
|
|
|
|
[Theory]
|
|
[InlineData("123.a")]
|
|
[InlineData("123. ")]
|
|
[InlineData("123. b")]
|
|
[InlineData("123.g5")]
|
|
[InlineData("123.-5")]
|
|
[InlineData("123.+5")]
|
|
public void ScanOperand_Invalid_Fractional(string expression)
|
|
{
|
|
BuildScanOperandWrapper(expression).Should()
|
|
.Throw<ExpressionFormatException>()
|
|
.WithMessage("Unable to parse fractional part of number");
|
|
}
|
|
|
|
private static Action BuildScanNumberWrapper(string expression, bool allowSign)
|
|
{
|
|
return () =>
|
|
{
|
|
ReadOnlySpan<char> span = expression.AsSpan();
|
|
ExpressionScanner.ScanNumber(span, allowSign);
|
|
};
|
|
}
|
|
|
|
private static Action BuildScanOperandWrapper(string expression)
|
|
{
|
|
return () =>
|
|
{
|
|
ReadOnlySpan<char> span = expression.AsSpan();
|
|
ExpressionScanner.ScanOperand(span);
|
|
};
|
|
}
|
|
|
|
private static Action BuildScanOperatorWrapper(string expression)
|
|
{
|
|
return () =>
|
|
{
|
|
ReadOnlySpan<char> span = expression.AsSpan();
|
|
ExpressionScanner.ScanOperator(span);
|
|
};
|
|
}
|
|
}
|