Thanks, I definitely think I understand parsers slightly more now. I tried to modify the code I had quickly to fix the ordering issue you pointed out, but I ended up refactoring it as you suggested, parsing both dates if both are available and only one otherwise. My final (for now) code looks like this, for anyone interested:
defmodule TodoTex.ParserHelper do
@moduledoc false
import NimbleParsec
def map_priority(pri), do: {:priority, <<pri>>}
def map_date(_rest, results, context, _line, _offset, tag) do
{[{:date, tag, apply(Date, :new!, Enum.reverse(results))}], context}
end
def map_end_and_start_date(_rest, results, context, _line, _offset) do
[start_day, start_month, start_year, end_day, end_month, end_year] = results
{[
{:date, :start, Date.new!(start_year, start_month, start_day)},
{:date, :end, Date.new!(end_year, end_month, end_day)}
], context}
end
def ignore_whitespace(combinator \\ empty()) do
combinator
|> ignore(repeat(ascii_char([?\s, ?\t])))
end
def done(combinator \\ empty()), do: combinator |> ascii_char([?x]) |> replace({:done, true})
def priority(combinator \\ empty()) do
combinator
|> ignore(string("("))
|> ascii_char([?A..?Z])
|> ignore(string(")"))
|> map({:map_priority, []})
end
def date(combinator \\ empty()) do
combinator
|> integer(4)
|> ignore(string("-"))
|> integer(2)
|> ignore(string("-"))
|> integer(2)
end
def start_date(combinator \\ empty()),
do: combinator |> date() |> post_traverse({:map_date, [:start]})
def end_and_start_date(combinator \\ empty()) do
combinator
|> date()
|> ignore_whitespace()
|> date()
|> post_traverse({:map_end_and_start_date, []})
end
end
defmodule TodoTex.TodoParser do
import NimbleParsec
import TodoTex.ParserHelper
defparsec(
:todo,
optional(done())
|> ignore_whitespace()
|> optional(priority())
|> ignore_whitespace()
|> optional(choice([end_and_start_date(), start_date()]))
|> ignore_whitespace()
)
end






















