dunyakirkali
Idiomatic nimble_parsec
I am trying to implement a parser.
I want to parse a string that has the following format:
I did the following:
sample =
empty()
|> ascii_string([?A..?Z, ?0..?9], 2)
|> ascii_string([0..127], 4)
|> ascii_string([0..127], 4)
|> ignore(string("$"))
defparsec :parse, sample
As much as I like it, something tells me that it can be cleaner / more consice.
Any ideas?
Marked As Solved
kip
Using nimble_parsec I tend to prefer using a helper module for combinators rather than inline. I also tend to create semantic functions because sooner or later debugging a nimble_parsec combinator - and providing reasonable messages back to a user on parse errors - becomes a challenge.
Example parser
This is a pattern I usually follow, here just parsing your first example.
defmodule Parse.Helpers do
import NimbleParsec
def version() do
head()
|> concat(hex_integer(4))
|> label("version")
|> tag(:version)
end
def hex_integer(digits) do
ascii_string([?a..?f, ?A..?F, ?0..?9], digits)
|> reduce(:hex_to_integer)
end
def count() do
hex_integer(4)
|> label("count")
|> unwrap_and_tag(:count)
end
def head() do
ascii_string([?A..?Z, ?0..?9], 2)
|> label("head")
end
def tail() do
string("$")
|> label("tail")
|> unwrap_and_tag(:tail)
end
def hex_to_integer([string]) do
String.to_integer(string, 16)
end
end
defmodule Parser do
import NimbleParsec
import Parse.Helpers
defparsec :parse,
version()
|> concat(count())
|> concat(tail())
end
Example usage
iex> Parser.parse "AB98761234$"
{:ok, [version: ["AB", 39030], count: 4660, tail: "$"], "", %{}, {1, 0}, 11}
iex> Parser.parse "AB98761234"
{:error, "expected version, followed by count, followed by tail", "AB98761234",
%{}, {1, 0}, 0}
Also Liked
kip
Noting BTW that you can use binary pattern matching to parse variable length content. This article has a really good example for parsing PNG files.
kip
Given you’re heading down the nimble_parsec route I would stay in that domain and do something like:
defparsec :parse,
choice([
string("+ACK:GTHBD") |> parsec(:parse_ack),
string("+SACK:GTHBD") |> parsec(:parse_sack)
])
end
kip
OK, revised version. Not sure its any more “beautiful” than your version but may be food for thought:
case some_string do
<<h1::integer-8, h2::integer-8, version::binary-4, count::binary-4, "$">>
when (h1 in ?A..?Z or h1 in ?0..?9) and (h2 in ?A..?Z or h2 in ?0..?9)->
header = List.to_string [h1, h2]
version = String.to_integer(version, 16)
count = String.to_integer(count, 16)
{:ok, header, version, count}
_other ->
{:error, :invalid}
end








