# Improved parser logger

**URL:** <https://discourse.elm-lang.org/t/improved-parser-logger/5964>\
**Category:** Show and Tell\
**Created:** [June 24, 2020, 9:01pm UTC](https://discourse.elm-lang.org/t/improved-parser-logger/5964 "2020-06-24T21:01:49Z")\
**Posts on this page:** 4\
**Page:** 1

<div class="post-metadata">

**Author:** ![Janiczek](https://yyz1.discourse-cdn.com/flex035/user_avatar/discourse.elm-lang.org/janiczek/32/4516_2.png) [@Janiczek](https://discourse.elm-lang.org/u/Janiczek)\
**Post date:** [June 24, 2020, 9:01pm UTC](https://discourse.elm-lang.org/t/improved-parser-logger/5964/1 "2020-06-24T21:01:49Z")

</div>

Hello!

Some time ago I’ve [asked about your techniques for debugging parsers](https://discourse.elm-lang.org/t/techniques-for-debugging-parsers/3977) and shared a "`log` parser combinator" there.

I’ve since made it a bit better: instead of just logging the “enter” transitions and successful “exit” transitions, **it’s now also logging unsuccessful “exit” transitions**. 🎉

It does this by using a technique similar to the [“logging JSON decoder”](https://thoughtbot.com/blog/debugging-dom-event-handlers-in-elm): it fetches the source from the current parser and runs `Parser.run` itself, and logs the `Ok / Err` result. That allows us to see the unsuccessful results, along with the context stack, problem, and so on.

> **Here is the code:**
>
> ```
> shouldLog : String -> Bool
> shouldLog message =
> -- You can conditionally turn some of the logging on/off per function here.
> List.member message
> [ "moduleType"
> , "plainModuleType"
> , "portModuleType"
> , "effectModuleType"
> ]
> 
> {-| Beware: what this parser logs might sometimes be a lie.
> 
> To work it needs to run `Parser.run` inside itself, and it has no way to
> set the parser state itself to the state it was called in.
> 
> So it runs it with a different source string, zeroed offset, indent, position,
> basically with totally different parser state.
> 
> Some parsers might not work properly in the "inner" `Parser.run` call as a result
> if they depend on this state, and thus might log lies.
> 
> Note: the parser itself will still work as before, since we're not resetting the
> "outer" `Parser.run` state.
> 
> -}
> log : String -> Parser_ a -> Parser_ a
> log message parser =
> if shouldLog message then
> P.succeed
> (\source offsetBefore ->
> let
> _ =
> Debug.log "+++++++++++++++++ starting" message
> in
> ( source, offsetBefore )
> )
> |= P.getSource
> |= P.getOffset
> |> P.andThen
> (\( source, offsetBefore ) ->
> {- Kinda like that logging decoder from Thoughtbot:
> https://thoughtbot.com/blog/debugging-dom-event-handlers-in-elm
> 
> Basically we run `Parser.run` ourselves so that we can
> get at the context and say something more meaningful
> about the failure if it happens.
> -}
> let
> remainingSource =
> String.dropLeft offsetBefore source
> |> Debug.log "yet to parse "
> in
> let
> parseResult =
> -- the side-effecty part; this might log lies
> P.run
> (P.succeed
> (\parseResult_ innerOffset ->
> let
> _ =
> Debug.log "chomped string" (String.left innerOffset remainingSource)
> in
> parseResult_
> )
> |= parser
> |= P.getOffset
> )
> remainingSource
> |> Debug.log "parse result "
> in
> let
> _ =
> Debug.log "----------------- ending " message
> in
> parser
> )
> 
> else
> parser
> 
> ```

> **And here is an example output!**
>
> ```
> +++++++++++++++++ starting: "moduleType"
> yet to parse : "port module Foo.Bar exposing (..)"
> +++++++++++++++++ starting: "plainModuleType"
> yet to parse : "port module Foo.Bar exposing (..)"
> parse result : Err [{ col = 1, contextStack = [], problem = ExpectingModuleKeyword, row = 1 }]
> ----------------- ending : "plainModuleType"
> +++++++++++++++++ starting: "portModuleType"
> yet to parse : "port module Foo.Bar exposing (..)"
> chomped string: "port module"
> parse result : Ok PortModule
> ----------------- ending : "portModuleType"
> chomped string: "port module"
> parse result : Ok PortModule
> ----------------- ending : "moduleType"
> 
> ```

You can kinda make sense of it by counting the `+++` and `---` around. But beware ⬇

Disclaimer 1: It duplicates some of the output and so the trace isn’t precisely what happens during the execution. I believe it’s a consequence of nesting `log`-ed parsers together (multiple `Debug.log` runs because I can’t provide a way to turn off the logging inside the inner `Parser.run` call 🤷‍♂️)

Disclaimer 2: I believe it has a problem (didn’t run into it yet though) where because the inner `Parser.run` can’t replicate the parser state of the outer parser it lives in (there’s API in `elm/parser` for getting the state but not for setting it), some parsers that depend on the offset / indentation / … might return different result in the inner `Parser.run` call (and thus log different stuff) from the outer one (which stays the same, fortunately).

---

<div class="post-metadata">

**Author:** ![jxxcarlson](https://yyz1.discourse-cdn.com/flex035/user_avatar/discourse.elm-lang.org/jxxcarlson/32/866_2.png) [@jxxcarlson](https://discourse.elm-lang.org/u/jxxcarlson)\
**Post date:** [June 26, 2020, 1:58pm UTC](https://discourse.elm-lang.org/t/improved-parser-logger/5964/2 "2020-06-26T13:58:19Z")

</div>

Martin, this is a much-needed tool — have wanted something like it for a long time. I’ve copied the code and will use it next time I run into difficulties with making parsers.

Bravo!

---

<div class="post-metadata">

**Author:** ![Janiczek](https://yyz1.discourse-cdn.com/flex035/user_avatar/discourse.elm-lang.org/janiczek/32/4516_2.png) [@Janiczek](https://discourse.elm-lang.org/u/Janiczek)\
**Post date:** [June 28, 2020, 4:36pm UTC](https://discourse.elm-lang.org/t/improved-parser-logger/5964/3 "2020-06-28T16:36:27Z")

</div>

Thinking about this, it might be possible to get rid of the second downside (not being able to copy the state from the outer parser to the inner parser). What about fetching the current position and indentation and then instead of

```auto
Parser.run parser remainingString

```

we’d do

```auto
Parser.run
  (Parser.succeed identity
     |. Parser.spaces
     |= parser
     |> Parser.withIndentation oldIndentation
  )
  ("\n\n\n " ++ remainingString)
  -- the whitespace string dynamically generated
  -- from (row,col) position: \n for rows, ' ' for cols)

```

That should make it have the same state as the outer parser 🤔  
For some reason a version of this didn’t do what I wanted when I first tried this idea, but I’ll probably try again later, because I don’t see why it shouldn’t work.

---

<div class="post-metadata">

**Author:** ![system](https://canada1.discourse-cdn.com/flex035/uploads/elm_lang/original/1X/50a05e53677a2c3b47776d7abd0f113eb50193a1.png) [@system](https://discourse.elm-lang.org/u/system)\
**Post date:** [July 8, 2020, 4:36pm UTC](https://discourse.elm-lang.org/t/improved-parser-logger/5964/4 "2020-07-08T16:36:28Z")

</div>

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