<commit_before>using System.Diagnostics.CodeAnalysis;
using static Parsley.Grammar;

namespace Parsley.Tests;

class GrammarTests
{
    static readonly Parser<string> Fail = (ref ReadOnlySpan<char> input, ref Position position, [NotNullWhen(true)] out string? value, [NotNullWhen(false)] out string? expectation) =>
    {
        expectation = "unsatisfiable expectation";
        value = null;
        return false;
    };
    static readonly Parser<char> Digit = Single(char.IsDigit, "Digit");
    static readonly Parser<char> Letter = Single(char.IsLetter, "Letter");

    readonly Parser<char> A, B, COMMA;
    readonly Parser<string> AB;

    public GrammarTests()
    {
        A = Single('A');
        B = Single('B');

        AB = from a in A
            from b in B
            select $"{a}{b}";

        COMMA = Single(',');
    }

    public void CanFailWithoutConsumingInput()
    {
        Fail.FailsToParse("ABC", "ABC", "unsatisfiable expectation expected");
    }

    public void CanDetectTheEndOfInputWithoutAdvancing()
    {
        EndOfInput.Parses("").ShouldBe("");
        EndOfInput.FailsToParse("!", "!", "end of input expected");
    }

    public void CanDemandThatAGivenParserRecognizesTheNextConsumableInput()
    {
        Letter.Parses("A").ShouldBe('A');
        Letter.FailsToParse("0", "0", "Letter expected");

        Digit.FailsToParse("A", "A", "Digit expected");
        Digit.Parses("0").ShouldBe('0');
    }

    public void CanDemandThatAGivenTokenLiteralAppearsNext()
    {
        A.Parses("A").ShouldBe('A');
        A.PartiallyParses("A!", "!").ShouldBe('A');
        A.FailsToParse("B", "B", "A expected");
    }

    public void ApplyingARuleZeroOrMoreTimes()
    {
        var parser = ZeroOrMore(AB);

        parser.Parses("").ShouldBeEmpty();

        parser.PartiallyParses("AB!", "!")
            .Single().ShouldBe("AB");

        parser.PartiallyParses("ABAB!", "!")
            .ShouldBe(new[] { "AB", "AB" });

        parser.FailsToParse("ABABA!", "!", "B expected");

        Parser<string> succeedWithoutConsuming = (ref ReadOnlySpan<char> input, ref Position position, [NotNullWhen(true)] out string? value, [NotNullWhen(false)] out string? expectation) =>
        {
            expectation = null;
            value = "ignored value";
            return true;
        };
        var infiniteLoop = () =>
        {
            ReadOnlySpan<char> input = "";
            Position position = new(1, 1);
            ZeroOrMore(succeedWithoutConsuming)(ref input, ref position, out _, out _);
        };

        infiniteLoop
            .ShouldThrow<Exception>()
            .Message.ShouldBe("Parser encountered a potential infinite loop at position (1, 1).");
    }

    public void ApplyingARuleOneOrMoreTimes()
    {
        var parser = OneOrMore(AB);

        parser.FailsToParse("", "", "A expected");

        parser.PartiallyParses("AB!", "!")
            .Single().ShouldBe("AB");

        parser.PartiallyParses("ABAB!", "!")
            .ShouldBe(new[] { "AB", "AB" });

        parser.FailsToParse("ABABA!", "!", "B expected");

        Parser<string> succeedWithoutConsuming = (ref ReadOnlySpan<char> input, ref Position position, [NotNullWhen(true)] out string? value, [NotNullWhen(false)] out string? expectation) =>
        {
            expectation = null;
            value = "ignored value";
            return true;
        };
        var infiniteLoop = () =>
        {
            ReadOnlySpan<char> input = "";
            Position position = new(1, 1);
            OneOrMore(succeedWithoutConsuming)(ref input, ref position, out _, out _);
        };

        infiniteLoop
            .ShouldThrow<Exception>()
            .Message.ShouldBe("Parser encountered a potential infinite loop at position (1, 1).");
    }

    public void ApplyingARuleZeroOrMoreTimesInterspersedByASeparatorRule()
    {
        var parser = ZeroOrMore(AB, COMMA);
<commit_msg>Simplify tests which needlessly recreated the SucceedWithThisValue combinator.<commit_after>using System.Diagnostics.CodeAnalysis;
using static Parsley.Grammar;

namespace Parsley.Tests;

class GrammarTests
{
    static readonly Parser<string> Fail = (ref ReadOnlySpan<char> input, ref Position position, [NotNullWhen(true)] out string? value, [NotNullWhen(false)] out string? expectation) =>
    {
        expectation = "unsatisfiable expectation";
        value = null;
        return false;
    };
    static readonly Parser<char> Digit = Single(char.IsDigit, "Digit");
    static readonly Parser<char> Letter = Single(char.IsLetter, "Letter");

    readonly Parser<char> A, B, COMMA;
    readonly Parser<string> AB;

    public GrammarTests()
    {
        A = Single('A');
        B = Single('B');

        AB = from a in A
            from b in B
            select $"{a}{b}";

        COMMA = Single(',');
    }

    public void CanFailWithoutConsumingInput()
    {
        Fail.FailsToParse("ABC", "ABC", "unsatisfiable expectation expected");
    }

    public void CanDetectTheEndOfInputWithoutAdvancing()
    {
        EndOfInput.Parses("").ShouldBe("");
        EndOfInput.FailsToParse("!", "!", "end of input expected");
    }

    public void CanDemandThatAGivenParserRecognizesTheNextConsumableInput()
    {
        Letter.Parses("A").ShouldBe('A');
        Letter.FailsToParse("0", "0", "Letter expected");

        Digit.FailsToParse("A", "A", "Digit expected");
        Digit.Parses("0").ShouldBe('0');
    }

    public void CanDemandThatAGivenTokenLiteralAppearsNext()
    {
        A.Parses("A").ShouldBe('A');
        A.PartiallyParses("A!", "!").ShouldBe('A');
        A.FailsToParse("B", "B", "A expected");
    }

    public void ApplyingARuleZeroOrMoreTimes()
    {
        var parser = ZeroOrMore(AB);

        parser.Parses("").ShouldBeEmpty();

        parser.PartiallyParses("AB!", "!")
            .Single().ShouldBe("AB");

        parser.PartiallyParses("ABAB!", "!")
            .ShouldBe(new[] { "AB", "AB" });

        parser.FailsToParse("ABABA!", "!", "B expected");

        var succeedWithThisValue = "ignored value".SucceedWithThisValue();
        var infiniteLoop = () =>
        {
            ReadOnlySpan<char> input = "";
            Position position = new(1, 1);
            ZeroOrMore(succeedWithThisValue)(ref input, ref position, out _, out _);
        };

        infiniteLoop
            .ShouldThrow<Exception>()
            .Message.ShouldBe("Parser encountered a potential infinite loop at position (1, 1).");
    }

    public void ApplyingARuleOneOrMoreTimes()
    {
        var parser = OneOrMore(AB);

        parser.FailsToParse("", "", "A expected");

        parser.PartiallyParses("AB!", "!")
            .Single().ShouldBe("AB");

        parser.PartiallyParses("ABAB!", "!")
            .ShouldBe(new[] { "AB", "AB" });

        parser.FailsToParse("ABABA!", "!", "B expected");

        var succeedWithoutConsuming = "ignored value".SucceedWithThisValue();
        var infiniteLoop = () =>
        {
            ReadOnlySpan<char> input = "";
            Position position = new(1, 1);
            OneOrMore(succeedWithoutConsuming)(ref input, ref position, out _, out _);
        };

        infiniteLoop
            .ShouldThrow<Exception>()
            .Message.ShouldBe("Parser encountered a potential infinite loop at position (1, 1).");
    }

    public void ApplyingARuleZeroOrMoreTimesInterspersedByASeparatorRule()
    {
        var parser = ZeroOrMore(AB, COMMA);

{'fraction_non_alphanumeric': 0.13999197753710388, 'fraction_numerical': 0.003208985158443642, 'mean_word_length': 2.5738174868609653, 'pattern_counts': {'":': 0, '<': 27, '<?xml version=': 0, '>': 35, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '226735', 'n_tokens_mistral': 2238, 'n_tokens_neox': 2156, 'n_words': 473}