dmitriid

dmitriid

NimbleParsec: confused about vas vs functions, can't figure out how to create reusable combinators

Hi all. I’m no stranger to parser combinators (see my long-abandoned pegjs for erlang). But there’s an issue I can’t seem to understand with NimbleParsec.

A very common thing to do in nearly every parser is to define skippable whitespace/blankspace (among many other common reusable combinators).

Example (from my own pegjs parser defnition):

Blankspace
  = (WhiteSpace / LineTerminatorSequence / Comment)*

WhiteSpace
  = "\t"
  / "\v"
  / "\f"
  / " "
  / "\u00A0"
  / "\uFEFF"
  / Zs

// https://www.compart.com/en/unicode/category/Zs
Zs = [\u0020\u00A0\u1680\u2000-\u200A\u202F\u205F\u3000]

// LineTerminatorSequence and Comment ommitted for brevity

And then this would be used, well everywhere :slight_smile:

// A rule is identifier=value
// There can be any number of whitespace in between
Rule
  = IdentifierName 
    Blankspace
    (StringLiteral Skippable)?
    "=" 
    Blankspace
    Expression
    EOS

Now, the trouble starts when converting this to NimbleParsec.

The first part is easy:

zs = utf8_char([0x0020, 0x00A0, 0x1680, 0x2000..0x200A, 0x202F, 0x205F, 0x3000])

whitespace_character =
  choice([
    ascii_char([?\t, ?\v, 32, ?\t]),
    utf8_char([0x00A0, 0xFEFF]),
    zs
  ])

blankspace = choice([whitespace_character, line_terminator_sequence]) |> repeat()

But then using it… how?

This will not work:

rule = repeat(ascii_char(not: 32)) |> blankspace

** (CompileError) undefined function blankspace/1

You can wrap it into additional repeat or optional but this is extremely redundant and code readability suffers:

## We have already defined blankspace as optional in its own definition
rule = repeat(ascii_char(not: 32)) |> optional(blankspace)

I’ve tried to convert it to a function:

def zs do
  utf8_char([0x0020, 0x00A0, 0x1680, 0x2000..0x200A, 0x202F, 0x205F, 0x3000])
end

def whitespace_character do
  choice([
    # space
    ascii_char([?\t, ?\v, 32, ?\t]),
    utf8_char([0x00A0, 0xFEFF]),
    zs()
  ])
  |> label("whitespace")
end

def blankspace do
  choice([whitespace_character()]) |> repeat()
end

rule = repeat(ascii_char(not: 32)) |> blankspace()

** (CompileError) undefined function blankspace/1

I’ve tried converting rule to a function, but nothing works :slight_smile:

So now I’m scratching my head and hoping that the collective wisdom of Elixir Forum will help me :slight_smile:

Marked As Solved

kip

kip

ex_cldr Core Team

That was a messy post because I hit send too fast. Sorry for the zillion quick edits. Basically:

  1. If you’ve defined combinators as function/0 then calling them requires they are wrapped in concat/1. Hence concat(blankspace()).

  2. You can alternatively define them with a default argument of empty() which is the empty combinator and apply combinators to that argument (my example above).

  3. Combinators needs to be in a separate module to the main defparsec and imported there because they are evaluated at compile time, not runtime.

I have a pretty straightforward example which might help.

Also Liked

kip

kip

ex_cldr Core Team

BTW, just in case its helpful (noticing your are working with unicode character classes), you may find ex_unicode_set useful. It can generator nimble_parsec lists from unicode sets that you can directly inside into unicode_char/1. See Unicode.Set.to_utf8_char/1. I’ve just noticed the docs need some work (there are none) but there is an example here.

Example

# Codepoints in the unicode Zs class (whitespace)
iex> Unicode.Set.to_utf8_char "\\p{Zs}"
{:ok, [32, 160, 5760, 8192..8202, 8239, 8287, 12288]}

# Codepoints NOT in the unicode Zs class
iex> Unicode.Set.to_utf8_char "\\P{Zs}"
{:ok,
 [
   not: 32,
   not: 160,
   not: 5760,
   not: 8192..8202,
   not: 8239,
   not: 8287,
   not: 12288
 ]}

Where Next?

Popular in Questions Top

jononomo
I am trying to figure out how Mix knows whether the environment is test, dev, or prod -- where is this set? Thanks.
New
sergio
In Ruby, I can go: User.find_by(email: "foobar@email.com").update(email: "hello@email.com") How can I do something similar in Elixir? ...
New
_russellb
I want to try my hand at web scraping. What tools/libraries do I need to use. I’m hoping to turn this into something professional so don’...
New
tduccuong
Hi, is there any work on GUI with Elixir, that is similar to Electron/Javascript? My idea is to bundle Phoenix and BEAM into a single se...
New
Jim
As a follow up to my earlier question: I have the code compiling and running but not getting a successful login from the rest server. ...
New
nsuchy
Hi. I’ve noticed that Windows Powershell has it’s own IEX command and you cannot access Elixir’s IEX due to the conflict. This isn’t a cr...
New
stefanchrobot
What’s the safe way to decode a JSON string into a struct? I want to avoid calling String.to_atom. Jason.decode can give me a map with st...
New
johnnyicon
Hi all, I've just started learning Elixir and Phoenix Framework, so please pardon my n00bness at this stage. I'm trying to use Postg...
New
skosch
To my knowledge, put_in, Map.update etc. all have the one limitation of not automatically creating intermediate keys when needed (for exa...
New
jay1
Why is it that the mnesia database isn’t the most preferred database for use in Elixir/Phoenix?
New

Other popular topics Top

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
SoCreat
i’m a new one to elixir which editor can i use vs code? or atom? Thanks! :smiley:
New
gshaw
What is the idiomatic way of matching for not nil in Elixir? E.g., First way: defp halt_if_not_signed_in(conn, signed_in_account) when...
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
joaquinalcerro
Hi there, I am working with Ecto-Postgresql and I need to call all of the records from a specific table but the table has 40,000 record...
New
axelson
This post is a wiki (feel free to hit the edit button near the bottom right of this post to add your own changes!) This post collects co...
239 45766 226
New
ycv005
I have followed this StackOverflow post to install the specific version of Erlang. And When I am running mix ecto.setup then getting fol...
New
malloryerik
Hi, this is for people who, like me, have had some friction using .html.heex templates in VSCode. The solution seems to be, in a hyphena...
New
johnnyicon
Hi all, I've just started learning Elixir and Phoenix Framework, so please pardon my n00bness at this stage. I'm trying to use Postg...
New
9mm
I am constructing a JSON object (map) and I need to conditionally set a field. I’m trying to write proper elixir-way code… and I’m at a l...
New

We're in Beta

About us Mission Statement