Parsinator

Parsinator lets you build small well-defined parsers in JavaScript or TypeScript which can be combined together to accomplish just about any parsing task.

Find it on GitHub: sufianrhazi/parsinator
Install it via: npm install parsinator

What it does

Parsinator uses parser combinators to build structured data from string input. Unlike other ways of parsing data, Parser Combinators are:

Parsinator is inspired by the excellent parsec Haskell library.

In short, parsers are functions which take a parser state (string input and parse offset) and return a value and a new parser state. Parsinator allows you to easily define and combine parsers to produce structured data from string input.

A simple example

Let's parse a hello world-style greeting and gauge its excitement level:

var greeting = Parsinator.fromGenerator(function *() {
    var intro = yield Parsinator.regex(/[hH]ello, /);
    var who = yield Parsinator.until(Parsinator.str("!"));
    var exclamations = yield Parsinator.many(Parsinator.str("!"));
    return {
      who: who,
      excitement: exclamations.length
    };
});

Parsinator.runToEnd(greeting, "Hello, Parsinator!");
// { who: "Parsinator", excitement: 1 }
Parsinator.runToEnd(greeting, "hello, there!!!!");
// { who: "there", excitement: 4 }

A powerful example

Let's build a parser which evaluates a mathematical expression:

var spaces = Parsinator.regex(/\s*/);
var token = (parser) => Parsinator.surround(spaces, parser, spaces);
var float = token(Parsinator.regex(/[0-9]+(\.[0-9]*)?/));
var number = Parsinator.map(float, (str) => parseFloat(str));

function makeOpParser(opstr, action) {
  var opParser = token(Parsinator.str(opstr));
  return Parsinator.map(opParser, (_str) => action);
}

var neg = makeOpParser('-', (val) => -val);
var fac = makeOpParser('!', (val) => {
  if (val < 0) {
    throw new Error('Factorial on a negative number: ' + val);
  }
  if ((val|0) !== val) {
    throw new Error('Factorial on a non-integer: ' + val);
  }
  var acc = 1;
  for (; val > 0; val--) acc *= val;
  return acc;
});
var sum = makeOpParser('+', (x, y) => x + y);
var sub = makeOpParser('-', (x, y) => x - y);
var mul = makeOpParser('*', (x, y) => x * y);
var div = makeOpParser('/', (x, y) => {
  if (y === 0) throw new Error('Division by zero');
  return x / y;
});
var exp = makeOpParser('^', (x, y) => Math.pow(x, y));

var evalMath = Parsinator.buildExpressionParser([
    { fixity: "prefix", parser: neg },
    { fixity: "postfix", parser: fac },
    { fixity: "infix", associativity: "right", parser: exp },
    { fixity: "infix", associativity: "left",  parser: mul },
    { fixity: "infix", associativity: "left",  parser: div },
    { fixity: "infix", associativity: "left",  parser: sum },
    { fixity: "infix", associativity: "left",  parser: sub }
], () => Parsinator.choice([
    Parsinator.surround(
      token(Parsinator.str("(")),
      evalMath,
      token(Parsinator.str(")"))
    ),
    number
]));

This parser is able to parse and evaluate any mathematical expression involving negation, exponents, multiplication, and summation. Go ahead and try it out!

A practical example

Let's parse structured data out of a name/email/url string:

"Abba Cadabra <abba@cadabra.com> (http://magic.website/)"

import * as Parser from 'parsinator';

const emailParser = Parser.between(Parser.str("<"), Parser.str(">"));

const urlParser = Parser.between(Parser.str("("), Parser.str(")"));

const infoParser = Parser.fromGenerator(function *() {
    const name = yield Parser.until(Parser.choice([
        Parser.str("<"),
        Parser.str("("),
        Parser.end
    ]));
    const email = yield Parser.maybe(emailParser);
    yield Parser.regex(/\s*/);
    const url = yield Parser.maybe(urlParser);
    yield Parser.end;
    return {
        name: name.trim(),
        email: email,
        url: url
    };
});

Parser.run(infoParser, "Parsinator");
// { name: "Parsinator", email: null, url: null }

Parser.run(infoParser,
  "Abba Cadabra <abba@cadabra.com> (http://magic.website)");
// { name: "Abba Cadabra", email: "abba@cadabra.com", url: "http://magic.website" }

Parser.run(infoParser,
  "Béla Bartók (https://www.britannica.com/biography/Bela-Bartok)"
);
// { name: "Béla Bartók" email: null, url: "https://www.britannica.com/biography/Bela-Bartok" }
Parser.run(infoParser,
  "马云 <jack@1688.com>"
);
// { name: "马云", email: "jack@1688.com", url: null }

Target platforms