Parser Combinators

Parser combinators are a parsing technique from functional programming that let you build big parsers out of little parsers. Example of parsing combinator libraries include parsec in Haskell and parsimmon in JavaScript.

Parsing syntax in a macro without help is a bit of a chore since you must manage the state of the transformer context yourself; calls to mark and reset make following the parsing logic in the macro difficult. Parser combinators help by threading the context behind the scenes letting you focus on the logic of what you are trying to match.

For example, here is a macro written with parser combinators that matches a simplified class syntax:

import * as C from '@sweet-js/helpers/combinators' for syntax;
import { Either } from '@sweet-js/helpers/either' for syntax;

syntax class = ctx => {
  let comma = C.punctuatorWith(v => v === ',');
  let method = C.sequence(
    C.identifier,
    C.parensInner(C.sepBy(C.identifier, comma)),
    C.braces
  );
  let result = C.sequence(
    C.identifier,
    C.bracesInner(C.many(method))
  ).run(ctx);

  if (Either.isRight(result)) {
    return #`'Parse success!'`;
  }
  return #`'Parse failed!'`;
}

class C {
  f(a, b) {
    return a + b;
  }
}

Parser

Abstractly, a parser is just a function from the input stream to a value and the (potentially) modified input stream (or a failure). Concretely, the Parser() class provides the parser abstraction:

class Parser(runner)

With type parameter A, the type of the value that this parser produces.

Import the Parser() class via:

import Parser from '@sweet-js/helpers/parser';
Arguments:

For example, you can write a parser that consumes a single value like so:

import Parser from '@sweet-js/helpers/parser';
import { Maybe } from '@sweet-js/helpers/maybe';
import { Either, Left } from '@sweet-js/helpers/either';

let one = new Parser(ctx => {
  let i = ctx.head();
  if (Maybe.isJust(i)) {
    return Either.of([i.value, ctx.rest()]);
  } else {
    return new Left('no more tokens to consume');
  }
});

The static methods of Parser() are:

The prototype methods of Parser() are:

Combinators

sequence(...parsers)

Runs the provided parsers in sequence. The resulting parser only succeeds if all parsers succeed. The resulting parser contains an array of each of the sequenced parsers success. For example,

import * as C from '@sweet-js/helpers/combinators';
import Parser from '@sweet-js/helpers/parser';

C.sequence(Parser.of(1), Parser.of(2), Parser.of(3))

would result in a parser with [1, 2, 3] as its result.

Arguments:
  • parsers (... Parser <A>) – The parsers to sequence.
Return type:

Parser <A[]>

disj(a, b[, msg])

Returns a parser that attempts to run the a parser and if it fails runs the b parser instead.

Arguments:
  • a (Parser <A>) – The first parser to try.
  • b (Parser <A>) – The second parser to try.
  • msg (string) – An optional failure message if both parsers fail
Return type:

Parser <A>

many(p)

Returns a parser that runs parser p on the context until it no longer succeeds, producing an array of each success value.

Arguments:
  • p (Parser <A>) – The parser to run
Return type:

Parser <A[]>

many1(p)

Like many() but must match at least once.

Arguments:
  • p (Parser <A>) – The parser to run
Return type:

Parser <A[]>

sepBy(p, sep)

Like many() but must match the separator parser sep.

Arguments:
  • p (Parser <A>) – The parser to run
  • sep (Parser <B>) – The parser that matches a separator
Return type:

Parser <A[]>

sepBy1()

Like many1() but must match the separator parser sep.

Arguments:
  • p (Parser <A>) – The parser to run
  • sep (Parser <B>) – The parser that matches a separator
Return type:

Parser <A[]>

infixl(p, op)

Matches parser p as a left associative infix operator, transforming each operand pair with the binary function in op. Similar to sepBy() but uses op to “reduce” the result.

Arguments:
  • p (Parser <A>) – The parser to match.
  • op (Parser <(A, A) => A>) – The operator to match and apply
infixr(p, op)

Matches parser p as a right associative infix operator, transforming each operand pair with the binary function in op. Similar to sepBy() but uses op to “reduce” the result.

Arguments:
  • p (Parser <A>) – The parser to match.
  • op (Parser <(A, A) => A>) – The operator to match and apply
lift2(f)

Lifts a binary function to work inside of a parser.

Arguments:
  • f ((A, B) => C) – The function to lift
Return type:

(Parser <A>, Parser <B>) => Parser <C>

Syntax specific combinators

identifier()

Returns a parser that matches a single identifier.

Return type:Parser <Term>
identifierWith(pred)

Returns a parser that matches a single identifier that also matches the provided predicate.

Arguments:
  • pred (string => boolean) – A predicate to test the matched syntax
Return type:

Parser <Term>

keyword()

Returns a parser that matches a single keyword.

Return type:Parser <Term>
keywordWith()

Returns a parser that matches a single keyword that also matches the provided predicate.

Arguments:
  • pred (string => boolean) – A predicate to test the matched syntax
Return type:

Parser <Term>

punctuator()

Returns a parser that matches a single punctuator (e.g. ., *, ;, etc.).

Return type:Parser <Term>
punctuatorWith()

Returns a parser that matches a single punctuator that also matches the provided predicate.

Arguments:
  • pred (string => boolean) – A predicate to test the matched syntax
Return type:

Parser <Term>

numeric()

Returns a parser that matches a single numeric.

Return type:Parser <Term>
numericWith()

Returns a parser that matches a single numeric that also matches the provided predicate.

Arguments:
  • pred (number => boolean) – A predicate to test the matched syntax
Return type:

Parser <Term>

string()

Returns a parser that matches a single string.

Return type:Parser <Term>
stringWith()

Returns a parser that matches a single string that also matches the provided predicate.

Arguments:
  • pred (string => boolean) – A predicate to test the matched syntax
Return type:

Parser <Term>

templateElement()

Returns a parser that matches a single template element.

Return type:Parser <Term>
templateElementWith()

Returns a parser that matches a single template element that also matches the provided predicate.

Arguments:
  • pred (string => boolean) – A predicate to test the matched syntax
Return type:

Parser <Term>

template()

Returns a parser that matches a single template.

Return type:Parser <Term>
templateWith()

Returns a parser that matches a single template that also matches the provided predicate.

Arguments:
  • pred (string => boolean) – A predicate to test the matched syntax
Return type:

Parser <Term>

regex()

Returns a parser that matches a single regex.

Return type:Parser <Term>
regexWith()

Returns a parser that matches a single regex that also matches the provided predicate.

Arguments:
  • pred (string => boolean) – A predicate to test the matched syntax
Return type:

Parser <Term>

braces()

Returns a parser that matches a single braces syntax.

Return type:Parser <Term>
brackets()

Returns a parser that matches a single brackets syntax.

Return type:Parser <Term>
parens()

Returns a parser that matches a single parens syntax.

Return type:Parser <Term>
delimiterInner(p)

Returns a parser that matches any delimiter that also matches the provided parser on the syntax inside the delimiter. For example,

import * as C from '@sweet-js/helpers/combinators';

C.delimiterInner(C.many(C.identifier()))

would match syntax like (), (foo bar).

Arguments:
  • p (Parser <A>) – The parser to run inside the delimiter.
Return type:

Parser <A>

bracesInner(p)

Returns a parser that matches any brace syntax that also matches the provided parser on the syntax inside the delimiter. For example,

import * as C from '@sweet-js/helpers/combinators';

C.bracesInner(C.many(C.identifier()))

would match syntax like {}, {foo bar}.

Arguments:
  • p (Parser <A>) – The parser to run inside the delimiter.
Return type:

Parser <A>

bracketsInner(p)

Returns a parser that matches any brackets syntax that also matches the provided parser on the syntax inside the delimiter. For example,

import * as C from '@sweet-js/helpers/combinators';

C.bracketsInner(C.many(C.identifier()))

would match syntax like [], [foo bar].

Arguments:
  • p (Parser <A>) – The parser to run inside the delimiter.
Return type:

Parser <A>

parensInner(p)

Returns a parser that matches any parenthses syntax that also matches the provided parser on the syntax inside the delimiter. For example,

import * as C from '@sweet-js/helpers/combinators';

C.parensInner(C.many(C.identifier()))

would match syntax like (), (foo bar).

Arguments:
  • p (Parser <A>) – The parser to run inside the delimiter.
Return type:

Parser <A>