I have to choose between delayedCommit and andThen
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 _.
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?
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.
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.