# Why does this Parser loop never end?

**URL:** https://discourse.elm-lang.org/t/why-does-this-parser-loop-never-end/8904
**Category:** Learn
**Created:** [January 22, 2023, 8:14pm UTC](https://discourse.elm-lang.org/t/why-does-this-parser-loop-never-end/8904 "2023-01-22T20:14:33Z")
**Posts on this page:** 3
**Page:** 1

<div class="post-metadata">

### Author: ![P.Wadsworth](https://yyz1.discourse-cdn.com/flex035/user_avatar/discourse.elm-lang.org/p.wadsworth/32/4794_2.png) [@P.Wadsworth](https://discourse.elm-lang.org/u/P.Wadsworth)
#### Post date: [January 22, 2023, 8:14pm UTC](https://discourse.elm-lang.org/t/why-does-this-parser-loop-never-end/8904/1 "2023-01-22T20:14:33Z")

</div>

I am running into a parser recursion problem I can’t figure out. Any advice on what is causing the issue would be appreciated.

The following code works fine when the function `rawData` is defined with a finite number elements (as shown in the commented code immediately below). But does not halt (until the stack overflows) when defined with `Parser.loop` as shown in the code. The same loop construct works fine with all the other functions (e.g. `files` and `directories` )

```elm
module Reader exposing (..)

import Parser exposing (..)

type TermCmd
    = CD Argument
    | LS

type Argument
    = Home
    | UpOne
    | DownOne String

type Content
    = Dir String (List Content)
    | File Int String String

type alias RawData =
    List ( List TermCmd, List Content )

rawData : Parser RawData
rawData =
    loop [] <| loopHelper dataChunk -- This never ends...
-- succeed (\a b c d -> [a, b, c, d]) -- This works if I only need a known number of chunks
-- |= dataChunk
-- |= dataChunk
-- |= dataChunk
-- |= dataChunk

dataChunk : Parser ( List TermCmd, List Content )
dataChunk =
    succeed Tuple.pair
        |= commands
        |= contents

directory : Parser Content
directory =
    succeed Dir
        |. symbol "dir"
        |. spaces
        |= (chompUntilEndOr "\n"
                |> getChompedString
           )
        |= succeed []
        |. spaces

file : Parser Content
file =
    succeed File
        |= int
        |. spaces
        |= (chompWhile (\c -> c /= '.' && c /= '\n')
                |> getChompedString
           )
        |= (chompUntilEndOr "\n"
                |> getChompedString
                |> Parser.map (String.dropLeft 1)
           )
        |. spaces

command : Parser TermCmd
command =
    succeed identity
        |. symbol "$"
        |. spaces
        |= oneOf
            [ succeed CD
                |. symbol "cd"
                |. spaces
                |= argument
            , succeed LS
                |. symbol "ls"
            ]
        |. spaces

argument : Parser Argument
argument =
    oneOf
        [ succeed UpOne |. symbol ".."
        , succeed Home |. symbol "/"
        , succeed DownOne |= (chompUntilEndOr "\n" |> getChompedString)
        , problem "Bad argument"
        ]
        |. spaces

contents : Parser (List Content)
contents =
    let
        contentHelper revContent =
            oneOf
                [ succeed (\ctnt -> Loop (ctnt :: revContent))
                    |= file
                , succeed (\ctnt -> Loop (ctnt :: revContent))
                    |= directory
                , succeed ()
                    |> map (\_ -> Done (List.reverse revContent))
                ]
    in
    loop [] contentHelper

commands : Parser (List TermCmd)
commands =
    loop [] <| loopHelper command

directories : Parser (List Content)
directories =
    loop [] <| loopHelper directory

files : Parser (List Content)
files =
    loop [] <| loopHelper file

loopHelper : Parser a -> List a -> Parser (Step (List a) (List a))
loopHelper parser revContent =
    oneOf
        [ succeed (\ctnt -> Loop (ctnt :: revContent))
            |= parser
        , succeed ()
            |> map (\_ -> Done (List.reverse revContent))
        ]

```

```auto
sampleInput =
    "$ cd /\n$ ls\ndir a\n14848514 b.txt\n8504156 c.dat\ndir d\n$ cd a\n$ ls\ndir e\n29116 f\n2557 g\n62596 h.lst\n$ cd e\n$ ls\n584 i\n$ cd ..\n$ cd ..\n$ cd d\n$ ls\n4060174 j\n8033020 d.log\n5626152 d.ext\n7214296 k"

```

---

<div class="post-metadata">

### Author: ![P.Wadsworth](https://yyz1.discourse-cdn.com/flex035/user_avatar/discourse.elm-lang.org/p.wadsworth/32/4794_2.png) [@P.Wadsworth](https://discourse.elm-lang.org/u/P.Wadsworth)
#### Post date: [January 23, 2023, 1:59am UTC](https://discourse.elm-lang.org/t/why-does-this-parser-loop-never-end/8904/2 "2023-01-23T01:59:07Z")

</div>

Copied from Look Woodward’s reply on Stack Overflow for anyone else having trouble with this:

You can probably get a hint at what the problem is by running your four-step parser (i.e. the one starting `succeed (\a b c d -> [a, b, c, d])`) on an empty string. If you do this, you get the following result:

```auto
Ok [([],[]),([],[]),([],[]),([],[])]

```

Take a second to think about what you would get for a five-step parser, or a ten-step parser, or even a 100-step parser. `loop` provides a parser that can run for any number of steps.

The [Elm documentation for the `loop` function](https://package.elm-lang.org/packages/elm/parser/latest/Parser#loop) hints at your problem:

> Parsers like `succeed ()` and `chompWhile Char.isAlpha` can succeed without consuming any characters. So in some cases you may want to use `getOffset` to ensure that each step actually consumed characters. Otherwise you could end up in an infinite loop!

Your parser is encountering an infinite loop because it is outputting an infinitely long list of tuples, each of which has an empty list of commands. Your parser consumes no characters as it generates each such tuple, so it will loop forever.

It seems that in your case an empty list of commands makes no sense. So we must ensure that an empty list of commands causes an unsuccessful parse.

One way to do this is to write a variation of `loopHelper` that fails if the list is empty:

```auto
checkNonEmpty : List a -> Parser ()
checkNonEmpty list =
    if List.isEmpty list then
        problem "List is empty"

    else
        succeed ()

loopHelperNonEmpty : Parser a -> List a -> Parser (Step (List a) (List a))
loopHelperNonEmpty parser revContent =
    oneOf
        [ succeed (\ctnt -> Loop (ctnt :: revContent))
            |= parser
        , checkNonEmpty revContent
            |> map (\_ -> Done (List.reverse revContent))
        ]

```

(I couldn’t find an easy way to introduce `getOffset` here so I did something different.)

You then change the definition of `commands` to use this function instead of `loopHelper`:

```auto
commands : Parser (List TermCmd)
commands =
    loop [] <| loopHelperNonEmpty command

```

I made this change to your code and it generated the following output:

```auto
Ok
    [( [ CD Home, LS]
        , [Dir "a" [], File 14848514 "b" "txt", File 8504156 "c" "dat", Dir "d" [] ]
        )
    , ( [CD (DownOne "a"), LS]
        , [Dir "e" [], File 29116 "f" "", File 2557 "g" "", File 62596 "h" "lst" ]
        )
    , ( [CD (DownOne "e"), LS]
        , [File 584 "i" ""]
        )
    , ( [CD UpOne, CD UpOne, CD (DownOne "d"), LS]
        , [File 4060174 "j" "", File 8033020 "d" "log", File 5626152 "d" "ext", File 7214296 "k" ""]
        )
    ]

```

(I’ve formatted this for clarity. When investigating your code I just outputted the result of the parser into the browser window using `Debug.toString()`, but that would come out as one long line. I pasted it into VS Code, added a few linebreaks and got elm-format to format it into something nicer.)

---

<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: [February 2, 2023, 1:59am UTC](https://discourse.elm-lang.org/t/why-does-this-parser-loop-never-end/8904/3 "2023-02-02T01:59:44Z")

</div>

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