Understanding Parser Loop


#1

I would like to have a parser that can transform a string "[1,2,3,4,5] into an elm List [1,2,3,4,5]

I want to user loop for this and I am getting somewhere with this great post: https://korban.net/posts/elm/2018-09-07-introduction-elm-parser/

With that I was able to get here so far: https://ellie-app.com/4V96cbjjrKta1

So not exactly, but getting closer. Now unfortunately I cannot wrap my head around this part of the code:

stringHelp : List String -> Parser (Step (List String) (List String))
stringHelp nums =
    let
        checkNum numsSoFar num =
            if String.length num > 0 then
                Loop (num :: numsSoFar)

            else
                Done (List.reverse numsSoFar)
    in
    succeed (checkNum nums)
        |= (getChompedString <| chompWhile Char.isDigit)

The variables checkNum, numsSoFar and num don’t seem to be defined anywhere? How can I do String.length on num? I don’t have a definition of num before or inside the let closure.

        checkNum numsSoFar num =
            if String.length num > 0 then

I am sure I am missing something rather basic, pointing me to docs or tutorials to understand this would be a huge help too.

Edit. OK I understand let checkNum numsSoFar num now, basically checkNum is a function with arguments numSoFar and num. But I still struggle with num, where is that coming from? Also because after let we do a partial function call inside in without num?


#2

This code:

stringHelp : List String -> Parser (Step (List String) (List String))
stringHelp nums =
    let
        checkNum numsSoFar num =
            if String.length num > 0 then
                Loop (num :: numsSoFar)

            else
                Done (List.reverse numsSoFar)
    in
    succeed (checkNum nums)
        |= (getChompedString <| chompWhile Char.isDigit)

Is equivalent to this code:

stringHelp : List String -> Parser (Step (List String) (List String))
stringHelp nums =
    succeed (checkNum nums)
        |= (getChompedString <| chompWhile Char.isDigit)

checkNum : List String -> String -> Parser.Step (List String) (List String)
checkNum numsSoFar num =
    if String.length num > 0 then
        Loop (num :: numsSoFar)

    else
        Done (List.reverse numsSoFar)

So indeed, checkNum is just a function, that takes two arguments. It is passed the nums that stringHelp receives as its first argument. The second argument is eventually produced through (getChompedString <| chompWhile Char.isDigit).

The general pattern for a parser looks like this:

succeed someFunction -- function that uses/combines the things produced by the parser
  |= someParser -- provides the first argument
  |. otherParser -- parses something, but not used by the function
  |= yetAnotherParser -- provides the second argument

So, this whole stringHelp function is a function which

  • receives a List String signifying a stack of things parsed so far
  • returns a parser, which:
    • chomps a contiguous chunk of digits into a string
    • if the resulting string is empty, it says “okay, I’m done, nothing left to do” and succeeds with the stack reversed
    • if the resulting string is not empty, it says “okay, continuing!” and pushes the parsed string onto the stack.

#3

Thank you. This was really helpful and I managed to implement my little parser! Now I can parse a sum “function” with “values” into a list:

module Main exposing (main)

import Browser
import Html exposing (Html, button, div, h2, input, text)
import Html.Attributes exposing (class, placeholder, value)
import Html.Events exposing (onClick, onInput)
import Parser exposing (..)


main =
    Html.text <|
        Debug.toString <|
            run stringParser "sum,2,3,3,4,5,1,2"


stringParser =
    loop [] stringHelp


stringHelp stmt =
    let
        f acc num =
            if String.length num > 0 then
                Loop (num :: acc)

            else
                Done (List.reverse acc)
    in
    oneOf
        [ succeed (f stmt)
            |= (keyword "sum" |> getChompedString)
        , succeed (f stmt)
            |. symbol ","
            |= (chompWhile Char.isDigit |> getChompedString)
        , succeed (f stmt)
            |= (chompWhile Char.isDigit |> getChompedString)
        ]

It’s interesting to notice that right now if any of the succeed break the pipeline the entire parser stops. So for instance if I do “sum,1,2,3,abc,4,5,6” I get only values “sum,1,2,3” because at “abc” the Char.isDigit breaks the pipeline flow.

Also, you are right the |= passes parameters to the function and that parameter must be a String. The reason it must be a string it’s because of this:

if String.length num > 0 then

Since we are doing the above, num must be a string, hence all the return values of |= must be of type string too. This means even with |= keyword = "sum" you must to (keyword "sum" |> getChompedString) otherwise you get a Parser () type and that is not playing well with the String.length num.

I would like to add types to this now. Instead of having:

Ok ["sum","2","3","3","4","5","1","2"]

I would like a structure like

Ok Sum [2,3,3,4,5,1,2]

So I can create a fairly simple AST to then compute the values in the list based on the function (Sum, Multiply, etc.).

I’ll continue working on this. Thanks again!


closed #4

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