Techniques for debugging Parsers?

I learned I struggle a lot with figuring out where have my various elm/parser parsers gone wrong.

So far I probably only have two techniques:


Print the dead ends as nicely as possible
expectEqualParseResult :
    String
    -> Result (List (P.DeadEnd ParseContext ParseProblem)) a
    -> Result (List (P.DeadEnd ParseContext ParseProblem)) a
    -> Expectation
expectEqualParseResult input expected actual =
    if actual == expected then
        Expect.pass

    else
        case actual of
            Err deadEnds ->
                Expect.fail
                    (String.join "\n"
                        (input
                            :: "===>"
                            :: "Err"
                            :: List.map deadEndToString deadEnds
                        )
                    )

            _ ->
                actual |> Expect.equal expected


deadEndToString : P.DeadEnd ParseContext ParseProblem -> String
deadEndToString deadEnd =
    let
        metadata =
            "("
                ++ String.fromInt (deadEnd.row - 1)
                ++ ","
                ++ String.fromInt (deadEnd.col - 1)
                ++ ") "
                ++ Debug.toString deadEnd.problem
    in
    String.join "\n    "
        ("\n"
            :: metadata
            :: "---- with context stack ----"
            :: List.map contextToString deadEnd.contextStack
        )


contextToString : { row : Int, col : Int, context : ParseContext } -> String
contextToString context =
    "("
        ++ String.fromInt (context.row - 1)
        ++ ","
        ++ String.fromInt (context.col - 1)
        ++ ") "
        ++ Debug.toString context.context
Example result
↓ ParserTest
↓ Stage.Parse.Parser.expr
↓ literal string
βœ— empty

    ""
    ===>
    Err


        (0,2) ExpectingDoubleQuote
        ---- with context stack ----
        (0,0) InString
        (0,0) InLiteral
        (0,0) InExpr


↓ ParserTest
↓ Stage.Parse.Parser.expr
↓ literal string
βœ— one space

    " "
    ===>
    Err


        (0,3) ExpectingDoubleQuote
        ---- with context stack ----
        (0,0) InString
        (0,0) InLiteral
        (0,0) InExpr


↓ ParserTest
↓ Stage.Parse.Parser.expr
↓ literal string
βœ— two numbers

    "42"
    ===>
    Err


        (0,4) ExpectingDoubleQuote
        ---- with context stack ----
        (0,0) InString
        (0,0) InLiteral
        (0,0) InExpr

`log` parser combinator
log : String -> Parser_ a -> Parser_ a
log message parser =
    P.succeed ()
        |> P.andThen
            (\() ->
                let
                    _ =
                        Debug.log "starting" message
                in
                P.succeed
                    (\source offsetBefore parseResult offsetAfter ->
                        let
                            _ =
                                Debug.log "-----------------------------------------------" message

                            _ =
                                Debug.log "source         " source

                            _ =
                                Debug.log "chomped string " (String.slice offsetBefore offsetAfter source)

                            _ =
                                Debug.log "parsed result  " parseResult
                        in
                        parseResult
                    )
                    |= P.getSource
                    |= P.getOffset
                    |= parser
                    |= P.getOffset
            )
Example result
starting: "many"
-----------------------------------------------: "many"
source         : "\"42\""
chomped string : "42\""
parsed result  : ['4','2','"']
starting: "many"
starting: "many"
-----------------------------------------------: "many"
source         : "\"\\\"\""
chomped string : "\\\"\""
parsed result  : ['"','"']
starting: "many"
starting: "many"

What are yours? What do you usually do when you need to debug a Parser?

9 Likes

What I feel might be very benefitial is some sort of Parser.debugRun which would return a trace of what parser ate what characters. Might disprove a lot of debugging hypotheses fast and give good insight into what’s actually happening :slight_smile:

6 Likes

My two stones:
Majority of my tests are only for parsers that I wrote. And only place where I actually practice TDD with Elm :slight_smile:

My technique is find smallest possible elements that I can write parser for,
after battery of tests have confirmed that I have nailed those, I start putting stuff together, essentially making small complex steps, that I can then combine.

Usually when I get to bigger and complex elements, that code reads really nicely and you can tell if something doesnt make sense. Hardest thing, as advertised, is when to backtrack, but couple of tests can help you find how to thread that needle trough :slight_smile:

Main

1 Like

This topic was automatically closed 10 days after the last reply. New replies are no longer allowed.