mona-parser

Composable parsers

npm install mona-parser
15 downloads in the last week
63 downloads in the last month

Mona NPM version Build Status

mona is hosted at Github. mona is a public domain work, dedicated using CC0 1.0. Feel free to do whatever you want with it.

Quickstart

Install

mona is available through both NPM and Bower.

$ npm install mona-parser or $ bower install mona

Note that the bower version requires manually building the release.

You can also download a prebuilt UMD version of mona from the website:

Example

function csv() {
  return mona.splitEnd(line(), eol());
}

function line() {
  return mona.split(cell(), mona.string(","));
}

function cell() {
  return mona.or(quotedCell(),
                 mona.text(mona.noneOf(",\n\r")));

}

function quotedCell() {
  return mona.between(mona.string('"'),
                      mona.string('"'),
                      mona.text(quotedChar()));
}

function quotedChar() {
  return mona.or(mona.noneOf('"'),
                 mona.and(mona.string('""'),
                          mona.value('"')));
}

function eol() {
  var str = mona.string;
  return mona.or(str("\n\r"),
                 str("\r\n"),
                 str("\n"),
                 str("\r"),
                 "end of line");
}

function parseCSV(text) {
  return mona.parse(csv(), text);
}

parseCSV('foo,"bar"\n"b""az",quux\n');
// => [['foo', 'bar'], ['b"az', 'quux']]

Introduction

Writing parsers with mona involves writing a number of individually-testable parser constructors which return parsers that mona.parse() can then execute. These smaller parsers are then combined in various ways, even provided as part of libraries, in order to compose much larger, intricate parsers.

mona tries to do a decent job at reporting parsing failures when and where they happen, and provides a number of facilities for reporting errors in a human-readable way.

mona is based on smug, and Haskell's Parsec library.

Features

  • Short, readable, composable parsers
  • Includes a library of useful parsers and combinators
  • Returns arbitrary data from parsers, not necessarily a plain parse tree
  • Human-readable error messages with source locations
  • Facilities for improving your own parsers' error reports
  • Supports context-sensitive parsing (see examples/context.js)
  • Supports asynchronous, incremental parsing with parseAsync.
  • Node.js stream API support with parseStream, including piping support
  • Heavy test coverage (see src/mona-test.js)
  • Small footprint (less that 4kb gzipped and minified)
  • Fully documented API

Documentation

Documentation of the latest released version is available here. Docs are also included with the npm release. You can build the docs yourself by running npm install && make docs in the root of the source directory.

The documentation is currently organized as if mona had multiple modules, although all modules' APIs are exported through a single module/namespace, mona. That means that mona/api.parse() is available through mona.parse()

A Gentle Introduction

mona works by composing functions called parsers. These functions are created by so-called parser constructors. Most of the mona API exposes these constructors.

Primitive parsers

There are three primitive parsers in mona: value(), fail(), and token().

  • value() - results in its single argument, without consuming input.
  • fail() - fails unconditionally, without consuming input.
  • token() - consumes a single token, or character, from the input.

Simply creating a parser is not enough to execute a parser, though. We need to use the parse function, to actually execute the parser on an input string:

mona.parse(mona.value("foo"), ""); // => "foo"
mona.parse(mona.fail(), ""); // => throws an exception
mona.parse(mona.token(), "a"); // => "a"
mona.parse(mona.token(), ""); // => error, unexpected eof
The primitive combinator

These three parsers do not seem to get us much of anywhere, so we introduce our first combinator: bind(). bind() accepts a parser as its first argument, and a function as its second argument. The function will be called with the parser's result value only if the parser succeeds. The function must then return another parser, which will be used to determine bind()'s value:

mona.parse(mona.bind(mona.token(), function(character) {
  if (character === "a") {
    return mona.value("found an 'a'!");
  } else {
    return mona.fail();
  }
}), "a"); // => "found an 'a'!"
Basic utility combinators

bind(), of course, is just the beginning. Now that we know we can combine parsers, we can play with some of mona's fancier parsers and combinators. For example, the or combinator resolves to the first parser that succeeds, in the order they were provided, or fails if none of those parsers succeeded:

mona.parse(mona.or(mona.fail("nope"),
                   mona.fail("nope again"),
                   mona.value("this one!")),
           "");
// => "this one!"
mona.parse(mona.or(mona.fail("nope"),
                   mona.value("this one!"),
                   mona.value("but not this one")),
           "");
// => "this one!"

and() is another basic combinator. It succeeds only if all its parsers succeed, and resolves to the value of the last parser. Otherwise, it fails with the first failed parser's error.

mona.parse(mona.and(mona.value("foo"),
                    mona.value("bar")),
           "");
// => "bar"

Finally, there's the not() combinator. It's important to note that, regardless of its argument's result, not() will not consume input... it must be combined with something that does.

mona.parse(mona.and(mona.not(mona.token()), mona.value("end of input")), "");
// => "end of input"
Matching strings

The string() parser might come in handy: It results in a string matching a given string:

mona.parse(mona.string("foo"), "foo");
// => "foo"

And can of course be combined with some combinator to provide an alternative value:

monap.parse(mona.and(mona.string("foo"), mona.value("got a foo!")), "foo");
// => "got a foo!"

The is() parser can also be used to succeed or fail depending on whether the next token matches a particular predicate:

mona.parse(mona.is(function(x) { return x === "a"; }), "a");
// => "a"
Sequential syntax

Writing parsers by composing functions is perfectly fine and natural, and you might get quite a feel for it, but sometimes it's nice to have something that feels a bit more procedural. For situations like that, you can use sequence:

function parenthesized() {
  return mona.sequence(function(s) {
    // The s() function passed into `sequence()`'s callback
    // must be used to execute any parsers within the sequence.
    var open = s(mona.string("("));
    // open === "(" if the `string()` parser succeeds.
    var data = s(mona.token());
    var close = s(mona.string(")"));
    // The `sequence()` callback must return another parser, just like `bind()`.
    // Also like `bind()`, it can `return fail()` to fail the parser.
    return mona.value(data);
  });
}
mona.parse(parenthesized(), "(a)");
// => "a"

We can generalize this parser into a combinator by accepting an arbitrary parser as an input:

function parenthesized(parser) {
  return mona.sequence(function(s) {
    var open = s(mona.string("("));
    var data = s(parser); // Use the parser here!
    var close = s(mona.string(")"));
    return mona.value(data);
  });
}
mona.parse(parenthesized(mona.string("foo!")), "(foo!)");
// => "foo!"

Note that if the given parser consumes closing parentheses, this will fail:

mona.parse(parenthesized(mona.string("something)"), "(something)");
// => error, unexpected EOF
The Rest of It

Once you've got the basics down, you can explore mona's API for more interesting parsers. A variety of useful parsers are available for use, such as collect(), which collects the results of a parser into an array until the parser fails, or float(), which parses a floating-point number and returns the actual number. For more examples on how to use mona to create parsers for actual formats, take a look in the examples/ directory included with the project, which includes examples for json and csv.

Building

The npm version includes a build/ directory with both pre-built and minified UMD versions of mona which are loadable by both AMD and CommonJS module systems. UMD will define window.mona if neither AMD or CommonJS are used. To generate these files In bower, or if you fetched mona from source, simply run:

$ npm install
...dev dependencies installed...
$ make

And use build/mona.js or build/mona.min.js in your application.

npm loves you