a few NimbleParsec tips:
- try not to use defcombinatorp
- don’t be afraid to break up into smaller testable chunks
- try to limit circular references.
- test test test, I like having the
Mix.env == :test
defparsec block pattern
- don’t be afraid to set variables in you “module-building-script”
- 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