Hi there!
I should preface this by saying I’m an absolute beginner to parser combinators and I’m pretty much an Elixir hobbyist so far.
In any case, I’ve very recently become interested in exploring them since watching Saša Jurić’s Parsing from first principles video and I’ve just started scratching the surface with nimble_parsec
.
The introductory datetime example got me thinking about how one might go about parsing integers with additional constraints. As a standalone example, how one would go about parsing a “valid month” value; e.g: in the range 1..12
with an optional leading 0
.
Strings like "1"
, "01"
and "12"
would be valid, returning 1
, 1
and 12
respectively.
Conversely, strings like "0"
, "00"
, "001"
and "13"
are invalid.
I am not sure what an idiomatic approach is here, so I suspect my various attempts so far have been very naive. The following is my best attempt, where I accept any integer between one or two characters and then validate with post_traverse
:
defmodule MonthParser do
import NimbleParsec
def valid_month?(_rest, [n] = args, context, _line, _offset) when n >= 1 and n <= 12,
do: {args, context}
def valid_day?(_rest, [n], _context, _line, _offset),
do: {:error, "Invalid month: #{n}"}
month =
integer(min: 1, max: 2)
|> post_traverse(:valid_month?)
|> eos()
defparsec :month, month
end
This seems satisfactory enough:
iex(1)> MonthParser.month "0"
{:error, "Invalid month: 0", "", %{}, {1, 0}, 1}
iex(2)> MonthParser.month "00"
{:error, "Invalid month: 0", "", %{}, {1, 0}, 2}
iex(3)> MonthParser.month "01"
{:ok, [1], "", %{}, {1, 0}, 2}
iex(4)> MonthParser.month "1"
{:ok, [1], "", %{}, {1, 0}, 1}
iex(5)> MonthParser.month "31"
{:ok, [31], "", %{}, {1, 0}, 2}
iex(6)> MonthParser.month "32"
{:error, "Invalid month: 32", "", %{}, {1, 0}, 2}
However, in the spirit of education I’d love to learn about better solutions. This being Elixir I assume there’s a far more elegant and succinct solution
Additionally, I am wondering in this example if range validation might be better somewhere else — in other words, maybe worrying about the validity of the values comes later? I am imagining a more complex scenario where a full date is being parsed, where the validity of the date depends on the month.
Thanks!