John-Goff
Hey everyone,
I’m trying to write a parser for todo.txt using NimbleParsec, it’s my first time writing a parser as well as first time being exposed to the idea of parser combinators, so I’m not really sure how to express what I want. At the moment, I have a pretty basic parser which correctly does done state as well as priority of a task. I’m trying to parse dates next.
The todo.txt specification says that two dates can be provided, a start date and an end date. A line item with both a start and end date might look like this
x 2021-01-02 2021-01-01 Make a new years resolution
If two dates are provided, the end date must come first followed by the start date. If only one date is given, it should be the start date. Right now, I have my dates defined like this
date =
integer(4)
|> ignore(string("-"))
|> integer(2)
|> ignore(string("-"))
|> integer(2)
start_date =
date
|> post_traverse({TodoTex.ParserHelper, :map_date, [:end]})
|> lookahead_not(date)
end_date =
date
|> post_traverse({TodoTex.ParserHelper, :map_date, [:end]})
the function ParserHelper.map_date turns the three integers into a %Date{} struct, as well as adding :start or :end respectively. It looks like this:
def map_date(_rest, results, context, _line, _offset, tag) do
{[{:date, tag, apply(Date, :new!, Enum.reverse(results))}], context}
end
and finally my parser is created like so:
defparsec(
:todo,
optional(done)
|> optional(ignore(string(" ")))
|> optional(priority)
|> optional(ignore(string(" ")))
|> optional(choice([start_date, end_date]))
|> optional(ignore(string(" ")))
|> optional(end_date)
|> optional(ignore(string(" "))),
debug: true
)
When I run this parser with the string with two dates, both of them are tagged with :end. If I run the parser on a string with only one date it’s tagged with :end as well. How can I get the date to be correctly tagged as start if it’s either the only date or the last date given? Thanks.
Trending in Questions
Other Trending Topics
Categories:
Sub Categories:
Forums
Popular Tags
- #ecto
- #liveview
- #troubleshooting
- #learning-elixir
- #deployment
- #library
- #erlang
- #testing
- #genserver
- #mix
- #absinthe
- #remote-other
- #otp
- #plug
- #how-to-question
- #macros
- #postgres
- #elixirconf
- #channels
- #exunit
- #discussion
- #code-sync
- #javascript
- #podcasts
- #onsite
- #dialyzer
- #docker
- #authentication
- #umbrella
- #full-time-contract
- #podcasts-by-brainlid
- #ecto-query
- #blog-post
- #elixir-ls
- #ai
- #elixirconf-us
- #phoenix_html
- #iex
- #graphql
- #genstage
- #websockets
- #supervisor
- #advent-of-code
- #distillery
- #processes
- #api
- #forms
- #hex
- #security
- #metaprogramming










Showing Posts 1 to 5- Show Best Posts
- Show All (oldest first)
- Show All (newest first)
kip
Looks like you’re tagging both of these with
:endwhich might be part of the issue.In a more general case, this kind of challenge is better suited to an approach of parsing the longest match first, then shorter matches. This way you avoid having to do lookahead and you leverage the parser combinators automatic backtracking. Making everything optional also makes it harder to understand the intent and harder to debug. Using your example, I would think about something like this:
John-Goff
You’re totally right, I copy pasted some code there and forgot to change that
Thanks for the spot! When I change that to have the correct tag, I now get the start tag correctly applied when I only have one date, but when I have two dates the start tag and end tag are applied to the wrong dates. So progress, but still not correct. I’ll look into the approach you suggested, thanks!
kip
The reason is that in your line
optional(choice([start_date, end_date])), thestart_datewill match and be tagged with:startThen later on you have
optional(end_date)which will be tagged with:end.Overall your code is expected the dates to be in the order of
start_date end_dateand that sounds like what you are seeing.John-Goff
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:
kip
I think the intent is much clearer in your refactored code and I expect maintenance will be easier in the future as a result.