Search code examples
c#parsingprefixsuperpower

Unable to combine 2 Superpower TextParser with Or with Span


I have 2 Superpower TextParser - each targets to parse a certain input - differentiated by prefix and parameters. I am trying to create a combined parser that gives out the result when either of the TextParser can parse the input. The output is a list with certain strings describing the input.

e.g. Some input and output pair as follows

"interrupt(20)" -> List { "interrupt", "20" }
"insert(20,12)" -> List {"insert", "20", "12" }
"delete(18)" -> Error

For the sample input and output pairs, I wrote the following in C# with superpower reference added.

// Works fine for "insert(12,24)"
string failingTest = "interrupt(12)";

TextParser<List<string>> identifier001 =
    from prefix in Span.EqualTo("insert")
    from open in Character.EqualTo('(')
    from num0 in Character.Numeric.Many()
    from comma1 in Character.EqualTo(',')
    from num1 in Character.Numeric.Many()
    from close in Character.EqualTo(')')
    select new List<string> { prefix.ToStringValue(), string.Join("", num0), string.Join("", num1) };

TextParser<List<string>> identifier002 =
    from prefix in Span.EqualTo("interrupt")
    from open in Character.EqualTo('(')
    from num0 in Character.Numeric.Many()
    from close in Character.EqualTo(')')
    select new List<string> { prefix.ToStringValue(), string.Join("", num0) };

TextParser<List<string>> combined =
    from span in identifier001.Or(identifier002)
    select span;

var res = combined.Parse(failingTest);
foreach (var item in res)
{
    Console.WriteLine(item);
}

My combined identifier cannot parse the input interrupt(12) and gives the following error

Syntax error (line 1, column 3): unexpected t, expected s.

Is there any proper way of doing the "Or" combination of the identifiers?


Solution

  • You have to call Try() after your first parser, then chain it with the Or() function, like this:

    TextParser<List<string>> combined =
        from span in identifier001.Try().Or(identifier002)
        select span;
    

    What happens is that your first parser identifier001 has already consumed the "in" portion of the string, which leaves identifier002 to try and parse the string "terrupt(20)" which fails. The call to Try() tells the parser to backtrack to the point of origin of the previous parser, which is what you want.

    The documentation on Try() is this:

    Construct a parser that tries one parser, and backtracks if unsuccessful so that no input appears to have been consumed by subsequent checks against the result.