Skip to content

Proposal - parser and combinator modifiers #101

Description

@GregRos

I’ve been thinking about the solution I proposed to @Pamplemousse’s #91 and I think it can be taken further. I'd love some feedback

There are lots of combinators that can be considered to be modified versions of other combinators. For example, the many combinator has the versions:

  • manyTill
  • many1
  • manySepBy

And each of those often has multiple optional parameters for tweaking their functionality. This is taken from traditional parser combinator libraries like Parsec of course. But it does have downsides.

  • Documentation and functionality are duplicated.
  • Adding extra functionality means overburdening individual combinator interfaces:
    • Adding more optional parameters
    • Adding another similar combinator with a different name
    • It can be hard to tell which extra functionality is exposed via optional parameters and which is just done using other combinators.

A good example of the problem was in #35, where the user wanted to capture the separators in manySepBy. But that can't be a default since lots of people probably don't want that. So either adding an optional parameter or adding another parser, both having downsides.

What if instead of doing that, we have a single many combinator that supports lots of different modifiers to mix and match different additional functionality?

How it would work

Let’s take the many combinator as an example. First we’d have the basic many combinator. This combinator could be modified with features such as separators, minimum/maximum matches, and so on. This would be done as instance methods defined on individual combinators.

For example, many could have the methods:

  • till(tillParser)
  • min(minMatches)
  • max(maxMatches)
  • sep(sepParser)

So a full instantiation of the modified combinator could look like this:

const manyTillnewline1to3 = many().till(newline()).min(1).max(3).sep(",")

Specific operators could be shared among all combinators and parsers, like caseSense which can enable/disable case sense in the whole parser (#91).

You would still apply combinators using pipe to actual parsers. For example,

string("abc").pipe(
    manyTillnewline1to3, 
    then(string("abc"))
)

Advantages

The main advantage is the branching API. It groups extra features around core functionality instead of having a giant list of combinators.

Disadvantages

  1. Departs from the interface of libraries like Parsec.
  2. Unusual. I haven’t seen any combinator-based library do this.
  3. Will involve deprecating existing parsers to maintain uniformity. Or something along those lines. Maybe a separate import.

Method of rollout

If people like the idea, I’d like to do a gradual rollout. I think just replacing the interface with one major version change is too much. It should be spread over 1 – 3 version upgrades.

Feedback

What do you think?

  • Do you like it?
  • Do you see any problems?
  • Any tweaks to the design?
  • Do you know anywhere this kind of API was used?

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions