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
Popular in Questions
Other popular 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
- #channels
- #elixirconf
- #exunit
- #discussion
- #code-sync
- #javascript
- #podcasts
- #onsite
- #dialyzer
- #docker
- #authentication
- #umbrella
- #full-time-contract
- #podcasts-by-brainlid
- #ecto-query
- #elixir-ls
- #phoenix_html
- #iex
- #blog-post
- #graphql
- #genstage
- #ai
- #websockets
- #supervisor
- #elixirconf-us
- #advent-of-code
- #distillery
- #processes
- #api
- #forms
- #metaprogramming
- #security
- #hex










