loop : state -> (state -> Parser (Step state a)) -> Parser a
This can be helpful when parsing repeated structures, like a bunch of statements:
statements : Parser (List Stmt) statements = loop [] statementsHelp statementsHelp : List Stmt -> Parser (Step (List Stmt) (List Stmt)) statementsHelp revStmts = oneOf [ succeed (\stmt -> Loop (stmt :: revStmts)) |= statement |. spaces |. symbol ";" |. spaces , succeed () |> map (\_ -> Done (List.reverse revStmts)) ] -- statement : Parser Stmt
Notice that the statements are tracked in reverse as we Loop, and we reorder them only once we are Done. This is a very common pattern with loop!
Check out DoubleQuoteString.elm code for another example.
IMPORTANT NOTE: 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!
Note: Anything you can write with loop, you can also write as a parser that chomps some characters andThen calls itself with new arguments. The problem with calling andThen recursively is that it grows the stack, so you cannot do it indefinitely. So loop is important because it enables Tail-Call Elimination, allowing you to parse however many repeats you want.
~
Tail Call Elimination geeksforgeeks
What is tail call optimization? stackoverflow
Tail call wikipedia
Tail Call Elimination post by Christopher Roman