dunyakirkali

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

kip

ex_cldr Core Team

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

kip

ex_cldr Core Team

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

kip

ex_cldr Core Team

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

kip

ex_cldr Core Team

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

Last Post!

dunyakirkali

dunyakirkali

Awesome!

Thnx a lot @kip

Where Next?

Popular in Questions Top

vegabook
I’m brand new to Phoenix and I have stripped one of the demo applications to the bone. I just want to get an svg up on the screen. Here i...
New
vertexbuffer
Hello, can anybody help here..? I have a list of players and I what to delete an element, but every for loop the list is reverting to ori...
New
lanycrost
Hi everyone! I need implement if…else if…else condition from my elixir code, and anymore of this control flow structures not work proper...
New
PeterCarter
There are pre-rolled solutions for other frameworks that do work. However, Phoenix does not seem to have these. Have people had good expe...
New
stefanluptak
Hello everybody, usually, I use a 29" ultra-wide monitor for VSCode which can easily accomodate explorer (files panel) + file with code ...
New
albydarned
Hello all! I am typing this post from my new MacBook Pro with the M1 chip. I’m loving it so far, and will probably use it as my daily dr...
New
Harrisonl
We have an ECS cluster with 4 services, where each task joins a single cluster, via discovery ECS discovery service. Currently when I de...
New

Other popular topics Top

New
hariharasudhan94
I would like to know what is the best IDE for elixir development?
New
jononomo
For some reason my phoenix channels are working for me in my local dev environment, but as soon as I deploy via Docker, I get a 403 error...
New
ashish173
I am using Ecto timestamps with postgres, I can see the timestamps() use the :naive_dateime but for my use case I wanted to store the ti...
New
shijith.k
I am trying to start a new phoenix project with elixir 1.9, but mix phx.new does not work. It says that ** (Mix) The task "phx.new" could...
New
saif
Hello everyone, Long time lurker first time poster here. I’ve recently begun working on Elixir full-time again! :raised_hands: It’s been...
New

We're in Beta

About us Mission Statement