parser icon indicating copy to clipboard operation
parser copied to clipboard

I have to choose between delayedCommit and andThen

Open BrianHicks opened this issue 8 years ago • 3 comments

commonmark has a bunch of block-level elements. The first one I implemented was thematic breaks (---, ***, but not mixed) I took care of that like this:

thematicBreak : Parser Block
thematicBreak =
    let
        anyBreakChar : Char -> Bool
        anyBreakChar c =
            c == '*' || c == '-' || c == '_'

        initial : Parser String
        initial =
            succeed identity
                |= keep (Exactly 1) anyBreakChar
                |. ignore zeroOrMore whitespace

        subsequent : String -> Parser ()
        subsequent breakChar =
            symbol breakChar |. ignore zeroOrMore whitespace
    in
    inContext "thematic break"
        (succeed ThematicBreak
            |. oneToThreeSpaces
            |. (initial |> andThen (\c -> repeat (AtLeast 2) (subsequent c)))
            |. eol
        )

When I implemented the next block level element, I realized this commits too early. "No problem," I thought, "I'll just use delayedCommit". But if there's a way to get both andThen and delayedCommit working on the same parser, I don't see it! I ended up with this:

thematicBreak : Parser Block
thematicBreak =
    let
        single : Char -> Parser ()
        single breakChar =
            ignore (Exactly 1) ((==) breakChar)
                |. ignore zeroOrMore whitespace

        lineOf : Char -> Parser ()
        lineOf breakChar =
            delayedCommit
                (oneToThreeSpaces |. single breakChar)
                (repeat (AtLeast 2) (single breakChar) |> map (always ()))
    in
    inContext "thematic break" <|
        succeed ThematicBreak
            |. oneOf
                [ lineOf '*'
                , lineOf '-'
                , lineOf '_'
                ]
            |. eol

This works, but it doesn't feel as good to me. I don't like repeating myself!

Edit: this is unclear, and a lot of text. Sorry! Concretely: the part that matters for indicating commitment is up to three spaces and then *, -, or _.

BrianHicks avatar Aug 14 '17 19:08 BrianHicks

I don't know about oneToThreeSpaces inside that function. You could make a function to check the indent level first, and then evaluate the next functions depending on the indent level. If is less than 4 spaces and not a tab, then can be any block minus code, if is greater or equal 4 spaces or a tab, then it's a code block. Makes sense?

pablohirafuji avatar Aug 14 '17 21:08 pablohirafuji

I just encountered the same problem. I would need a function of the signature:

delayedCommitAndThen: (a -> b -> c) -> Parser a -> (a -> Parser b) -> Parser c

but I could not find a way to define it properly. What it should do is it parses a, then uses this result to invoke the Parser b and only if Parser b makes some progress then it considers that it made some progress. I use that to parse blocks which should be indented according to previous context.

Note that this is a smaller building block than delayedCommitMap, because:

delayedCommitMap fun parser1 parser2 =
  delayedCommitAndThen fun parser1 (always parser2)

Is there any way to implement delayedCommitAndThen ? So far my following attempt is committing too early:

delayedCommitAndThen fun parserA aToParserB =
  Parser.andThen identity <|
  delayedCommitMap (\a aToParserB -> Parser.map (fun a) (aToParserB a))
    parserA
    (succeed aToParserB) -- This commits too early.

Thanks for your help.

MikaelMayer avatar Aug 07 '18 19:08 MikaelMayer

I just found an implementation:

delayedCommitAndThen: (a -> b -> value) -> Parser a -> (a -> Parser b) -> Parser value
delayedCommitAndThen func (Parser parseA) aToParserB =
  Parser <| \state1 ->
    case parseA state1 of
      Bad x _ ->
        Bad x state1

      Good a state2 ->
        let (Parser parseB) = aToParserB a in
        case parseB state2 of
          Good b state3 ->
            Good (func a b) state3

          Bad x state3 ->
            if state2.row == state3.row && state2.col == state3.col then
              Bad x state1
            else
              Bad x state3

which I pushed on my own fork of Elm parsers with documentation about delayedCommitAndThen. To use this fork, simply replace in elm-package.json the line of the parser 2.0.1 with the following:

"MikaelMayer/parser": "1.1.0 <= v < 2.0.0"

I encourage the authors to include this function in future releases.

MikaelMayer avatar Aug 07 '18 19:08 MikaelMayer