GavinJoyce

GavinJoyce

Trying to write a simple nimble_parsec parser

Hi,

I’m trying to write a parser using NimbleParsec v1.4.2 — Documentation which parses arbitrary text wrapped in a character like an underscore.

Here are some examples of what I’m hoping the parser will do:

"hello there" => ["hello there"]
"hello _Alex_!" => ["hello ", "_Alex_", "!"]
"Ũnicode is _allŏwed everywhere_ _ŏk?_" => ["Ũnicode is ", "_allŏwed everywhere_", "_ŏk?_"]

Here’s my attempt:

defmodule Txtparser do
  import NimbleParsec

  defcombinatorp :plain_string,
    utf8_string([], min: 1)
    |> lookahead_not(parsec(:wrapped_string))

  defcombinatorp :wrapped_string,
    string("_")
    |> parsec(:plain_string)
    |> string("_")

  defparsec(:parse,
    repeat(
      choice(
        [parsec(:wrapped_string), parsec(:plain_string)]
      )
    )
  )
end

But I think I’m misundertanding how lookahead_not works, as the following is happening

"hi _there_" => ["hi _there_"]

instead of this desired result:

"hi _there_" => ["hi ", "_there_"]

First Post!

ityonemo

ityonemo

maybe I’m missing something. how are you getting them split into separate chunks? When I run your code on "foo _bar_ foo" I get

{:ok, ["foo _bar_ foo"], "", %{}, {1, 0}, 13}

Most Liked

ityonemo

ityonemo

a few NimbleParsec tips:

  1. try not to use defcombinatorp
  2. don’t be afraid to break up into smaller testable chunks
  3. try to limit circular references.
  4. test test test, I like having the Mix.env == :test defparsec block pattern
  5. don’t be afraid to set variables in you “module-building-script”
  6. use post_traverse

here is what I got:

defmodule Np do
  import NimbleParsec

  basic_chars =
    utf8_string([not: ?_], min: 1)

  wrapped_string =
    string("_")
    |> concat(basic_chars)
    |> string("_")

  plain_string =
    times(
      utf8_char([])
      |> lookahead_not(wrapped_string),
      min: 1
    )
    |> choice([eos(), utf8_char([])])
    |> post_traverse(:post_plain)

  parser =
    repeat(
      choice([
        wrapped_string
        |> post_traverse(:post_wrapped),
        plain_string
      ])
    )

  defparsec(:parse, parser)

  defp post_wrapped(_rest, parsed, context, _line, _offset) do
    {[parsed |> Enum.reverse |> List.to_string], context}
  end

  defp post_plain(_rest, parsed, context, _line, _offset) do
    {[parsed |> Enum.reverse |> List.to_string], context}
  end


  if Mix.env() == :test do
    defparsec(:basic_chars, basic_chars)
    defparsec(:wrapped_string, wrapped_string)
    defparsec(:plain_string, plain_string)
  end
end

np_test.exs

defmodule NpTest do
  use ExUnit.Case

  defmacrop assert_parses(result, what) do
    quote bind_quoted: [result: result, what: what] do
      assert {:ok, ^result, _, _, _, _} = what
    end
  end

  defmacrop assert_leaves(result, leftover, what) do
    quote bind_quoted: [result: result, leftover: leftover, what: what] do
      assert {:ok, ^result, ^leftover, _, _, _} = what
    end
  end

  defmacrop assert_fails(what) do
    quote bind_quoted: [what: what] do
      assert {:error, _, _, _, _, _} = what
    end
  end

  test "basic_chars" do
    assert_parses ["foo"], Np.basic_chars("foo")
    assert_leaves ["foo "], "_foo_", Np.basic_chars("foo _foo_")
    assert_fails Np.basic_chars("_foo_")
  end

  test "wrapped_string" do
    assert_parses ["_", "foo", "_"], Np.wrapped_string("_foo_")
    assert_leaves ["_", "foo", "_"], " bar", Np.wrapped_string("_foo_ bar")
  end

  test "plain string" do
    assert_parses ["foo"], Np.plain_string("foo")
    assert_leaves ["foo "], "_bar_", Np.plain_string("foo _bar_")
  end
end
GavinJoyce

GavinJoyce

Amazing, thanks so much!. I’ll dig into this and play around with it now - thank you!

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
aadeshere1
I have a another noob question about loop. Since elixir is immutable, while loop is not directly possible. total = 10 while total != 0 ...
New
joeerl
Hello again - after a longish gap I’ve decided I really must dig into Elixir and see what’s been happening here - so I have a few questio...
New
jay1
Why is it that the mnesia database isn’t the most preferred database for use in Elixir/Phoenix?
New
greenz1
I have a phoenix application from which a user can download multiple(5-6) files of size 1MB. I couldn’t find anything related to sending ...
New
belgoros
I’m not a pro in using Regex and can’t figure out why the following behaviour happens, especially if we take into account the difference ...
New
fayddelight
I tried installing elixir 1.11.2 erlang 23.3.4 via asdf in my zsh shell. Enabled the versions locally and globally. When I list them ...
New

Other popular topics Top

minhajuddin
I have seen a lot of code which picks the first element from a list using Enum.at(0) instead of List.first. Is there a reason why people ...
New
greenz1
I have a phoenix application from which a user can download multiple(5-6) files of size 1MB. I couldn’t find anything related to sending ...
New
sen
Hi All, I set a environment variables in dev.exs , like below code. when i start server, how can i set the ${enable} value? thanks. d...
New
AstonJ
Seen any cool LiveView demos, sample apps or examples? Please post them here! :003:
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
senggen
Erlang/OTP 25 [erts-13.2.2] [source] [64-bit] [smp:8:8] [ds:8:8:10] [async-threads:1] 15:22:35.803 [error] gen_event {lager_file_backend...
New

We're in Beta

About us Mission Statement