mattmower

mattmower

After watching Saša Jurić’s talk on the subject I built my own parser combinator library, Ergo, as a learning experience.

It’s reached a point of maturity that it’s worked quite well for the parsing tasks I’ve thrown at it, but now I’ve hit a problem I am struggling to think my way around.

Let’s say I have a parser for a value:

def value() do
  choice([
    number_value(),
    boolean_value(),
    string_value()
  ])
)

and now I want to deal with lists (I’m omitting to deal with whitespace for brevity):

def list_value() do
  sequence([
    char(?[),
    value(),
    many(
      sequence([
        char(?,)
        value()
      ])
    ),
    char(?])
  ])
)

def value() do
  choice([
    number_value(),
    boolean_value(),
    string_value(),
    list_value()
  ])
end

and now I have a problem in that list_value() and value() are mutually, infinitely, recursive.

I’m about 25 years from my compiler class so I am very hazy about anything from here onwards. I don’t think this is a left-recursive grammar issue because you consume the [ token (i.e. this is not A -> A𝛼|β). I think this is an artefact of building the parsers using function calls.

I’ve bent my brain around this problem and come up with nothing except that possibly this is one reason that NimbleParsec and ExSpirit use macros, rather than plain functions, so that recursion is possible.

Can anyone help me figure out what to do here?

Many thanks in advance.

Showing Posts 1 to 6

mattmower

mattmower OP

The answer came from Frerich on the elixir slack.

I’d gotten too far into think about the parsing aspect of the problem and didn’t see the purely functional aspect - eager vs lazy evaluation.

The solution in the end is simple, introduce an intermediate parser that sits between value() and list()

My first use of a macro in Elixir:

defmacro lazy(parser) do
    quote do
      Parser.new(
        :lazy,
        fn ctx -> Parser.invoke(unquote(parser), ctx) end
      )
    end
  end

Now I can write:

def value() do
  choice([
    number_value(),
    boolean_value(),
    string_value(),
    lazy(list_value())
  ])
)

And the recursion is broken.

Qqwy

Qqwy

TypeCheck Core Team

Congradulations on writing your first macro!

Indeed, this is one of the places where lazy evaluation makes an algorithm simpler.
Situations like this, where infinite recursion happens while building an intermediate structure for things that ‘may’ exist to an unknown (but finite) depth, come up from time to time.

The problem definitely is related to left-recursion. One could see it as left-recursion happening already before starting the parsing process itself.


In this particular example, you might be able to get away with a definition of lazy as a function (rather than a macro) like this:

def lazy(parser_producing_fun) do
  Parser.new(:lazy, fn cx -> Parser.invoke(parser_producing_fun.(), ctx) end
end

However, you’d need to call it as lazy(&list_value/0 instead of lazy(list_value()) so it alters the way it has to be used a little. Your choice :smiley:

IloSophiep

IloSophiep

I recently finally took the time to watch that video myself.

I am wondering if I’m confusing stuff when I’m thinking that Saśa Jurić touches on the topic in the video already - starting at around 49:57min he talks about the problem of a subquery being its own select_statement() parser function.

He ends up evaluating the term lazily, just like you ended up doing, so I think it is the same situation you were dealing with?

sasajuric

sasajuric

Author of Elixir In Action

I came here to say exactly this :slight_smile: The relevant part of the talk starts here. In the talk I opted for the function version (basically what @Qqwy suggests in the earlier response).

mattmower

mattmower OP

Ah, it is a while since I watched the video and I forgot that Saša covered the problem in it. Thanks anyway! Would have saved me a lot of bother if I had remembered that!

I did think about the function approach as an alternative but (esp in a case where the deferred parser may take arguments) I thought the macro was likely to be cleaner at a cost of being slightly less clear that something unusual is going on.

Perhaps I might change the name to LAZY or lazy! to make it clearer there’s something to take note of.

mattmower

mattmower OP

Yes, it’s kind of a left-recursion at the Elixir level. In my Ergo docs I’m calling this “Eager recursion” to distinguish from left-recursion in the grammar itself.

— All posts loaded —

Where Next? Top

Trending in Questions Top

itzmidinesh
Hi all, I’m working on a Phoenix project where I need to make external HTTP requests and I’m trying to choose between Req and HTTPoison....
New
yarrichar
I’m very new to Elixir, so sorry if this is a silly question: I am wondering what happens to the mailbox of a process when the machine i...
New
pragdave
How do I tell Hologram not to escape HTML when substituting into templates? {Textile.render(@product.long_description)} escapes the HTM...
New
Fl4m3Ph03n1x
Background When trying to execute mix release on a Windows 11 machine for a Phoenix project I get the following error: * assembling mark...
New
rlipscombe
I’d like to create a file with 600 permissions (rw-------), but there’s a race condition if I create it and then change the permissions. ...
New
saleyn
Is there a way to import record definitions in the iex shell for pretty printing (similar to the rr command in the erl shell)? This woul...
New
rlipscombe
When I use Mix.install with a git dependency, using git@github.com:... how do I change the SSH identity used? I’ve got two Github accoun...
New

Other Trending Topics Top

dominicletz
Hey there, I haven’t seen a list of useful Elixir specific skills for the popular AI tools yet so I’m starting one here with my first. ...
New
raa-ziq
Hi everyone :waving_hand: I wanted to share something I’ve been building — NowBlind, a random chat app designed to help people discover ...
New
mudasobwa
I fully migrated to my own harness from Anthropic/Gemini and I think it’s time to share it. Welcome DSH, the DeepSeek Harness, fully writ...
New
OndrejValenta
(I just needed to vent somewhere and LinkedIn is full of hope, or hype, I’m not sure which exactly) AI dream has many faces, but general...
New
kip
Localize is the next generation localisation library for Elixir. Think of it as ex_cldr version 3.0. The first version will be released ...
New
jimsynz
Beam Bots (or just BB for short) is a framework for building fault-tolerant robotics applications in Elixir using familiar OTP patterns. ...
New

We're in Beta

About us Mission Statement

Options

Thread Display Mode




Thread Preview

Skip Thread Previews