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 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 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() .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() .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() .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() .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 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() .WithMessage($"Unknown operator: {expression.TrimStart()[0]}"); } [Fact] public void ScanOperator_Empty() { var action = BuildScanOperatorWrapper(string.Empty); action.Should().Throw().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 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 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() .WithMessage("Operand is missing"); } [Theory] [InlineData(".12")] [InlineData(" .12")] [InlineData(".12.55")] public void ScanOperand_Invalid_Integral(string expression) { BuildScanOperandWrapper(expression).Should() .Throw() .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() .WithMessage("Unable to parse fractional part of number"); } private static Action BuildScanNumberWrapper(string expression, bool allowSign) { return () => { ReadOnlySpan span = expression.AsSpan(); ExpressionScanner.ScanNumber(span, allowSign); }; } private static Action BuildScanOperandWrapper(string expression) { return () => { ReadOnlySpan span = expression.AsSpan(); ExpressionScanner.ScanOperand(span); }; } private static Action BuildScanOperatorWrapper(string expression) { return () => { ReadOnlySpan span = expression.AsSpan(); ExpressionScanner.ScanOperator(span); }; } }