npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

warbler

v0.0.5

Published

A simple parser combinator library with emphasis on ease of use.

Downloads

3

Readme

Warbler

Warbler is a simple parser combinator library with an emphasis on ease of use.

Build status

Warbler is still experimental. Please open an issue if you find any bugs.

Install

npm install --save warbler

A motivating example

import W from 'warbler';

let operations = {
    '+': (a, b) => a + b,
    '*': (a, b) => a * b
};

let parser = W([W.integer, W('+', '*'), W.integer])
    .map(([lhs, op, rhs]) => operations[op](lhs, rhs));

let sum = parser('45+9');
let product = parser('2*5');

console.log(W.value(sum)); // => 54
console.log(W.value(product)); // => 10

For a more advanced example, look at test/json.js for a JSON parser.

Basics

A parser is a function that takes two arguments, a string of input data and an environment object, outputting a parser result. Typically, the result will form an abstract syntax tree but parsers can also produce arbitrary values, like in the example above where the parser produced numbers. A parser matching the string 'foo' in the beginning of the string 'foobar' would produce the following result:

{
    success: true,
    value: 'foo',
    rest: 'bar'
}

The environment argument is not always required, so a very simple parser has the following form:

let parseX = function (input) {
    let value = input.charAt(0);
    let success = value === 'x';
    let rest = input.slice(1);

    return { success, value, rest };
};

console.log(W.value(parseX('xyz'))); // => 'x'

This parser will succeed if the first character of the input is an 'x'. W.value retrieves the value contained in a parser result.

The real power of Warbler lies in combining small parsers to form more complex ones. One way of doing so is to put parsers in sequence. Sequences return an array with an element for every parser result. This can be done with either W.seq(parser1, ..., parserN) or by passing an array of parsers to the W function:

let parseThreeDigits = W([W.digit, W.digit, W.digit]);

console.log(W.value(parseThreeDigits('123yyyzzz'))); // => ['1', '2', '3']

Another useful combinator is W.or(parser1, ..., parserN) which takes multiple parsers and returns the result of the first one that is successful. This function is also available as a shorthand by passing multiple arguments to W:

let parseFruits = W('apple', 'banana', 'orange');

console.log(W.value(parseFruits('banana in panama'))); // => 'banana'

Warbler can also handle regexes:

let identifier = W(/[a-zA-Z_][a-zA-Z0-9_]*/);

console.log(W.value(identifier('christopherWalken'))); // => 'christopherWalken'

Mapping

Typically, you want to produce more complex results with your parsers. Warbler provides plenty of facilities for this through its mapping functions. A parser that is wrapped by W automatically gains a few functions that can be chained to manipulate the values of parser results. The .nth function will keep the specified indices of a sequence result, and discard the rest.

let instruction = W([
    'throw ',
    W('eggs', 'bricks'),
    ' at ',
    W('neighboring houses', 'Martin Shkreli')
]).nth(1, 3);

console.log(W.value(instruction('throw eggs at Martin Shkreli')));
// => ['eggs', 'Martin Shkreli']

The .nth function is however just syntactic sugar for the more general .map function, which allows you to return an arbitrary result based on the value captured by the parser. The following is equivalent:

let instruction = W([
    'throw ',
    W('eggs', 'bricks'),
    ' at ',
    W('neighboring houses', 'Martin Shkreli')
]).map(args => [args[1], args[3]]);

It is quite common to want to keep some parts of a sequence and throw others away, producing an object as a result. This is quite easy to accomplish with the W.mapSeq helper function.

let ifStatement = W.mapSeq(to => [
    'if',
    W(['(', expression, ')']).nth(1).map(to('expr')),
    block.map(to('block'))
]);

ifStatement('if(...){...}');
// Will produce a value like:
{
    expr: /* value of `expression` parser */,
    block: /* value of `block` parser */
}

Handling whitespace

It is quite common to want to exclude whitespace. This is easily accomplished with the W.terminals function, which lets you wrap a parser around every terminal.

let parser = W(['a', 'b', 'c']);
let skipFn = W.skip(W.whitespace);
let skipWs = W.terminals(skipFn)(parser);

parser('a   b    c'); // matches

The above example turns the parser function into the following form:

let parser = W([skipFn('a'), skipFn('b'), skipFn('c')])

Lazy name binding

Sometimes, your parsers need mutual or circular recursion, which will cause issues since one of the parsers must be defined before the other. This can be dealt with by wrapping one of the parsers in another parser function:

let a = W.or('y', b); // `b` is undefined :(
let b = W.or('x', a);

// Do this instead:
let a = W((input, env) => W.or('y', b)(input, env));
let b = W.or('x', a);

Handling errors

Error handling is a bit of a weak point at the moment. Warbler supports expectation errors.

let parser = W([
    'foo',
    W.expect(W.integer, 'integer'),
    'bar'
]);

W.value(parser('foofoobar')) // => '[1:4]: Expected integer, got "foobar"'

Line and column numbers are available in the environment, and the error reporting mechanisms can be further customized using them.

Reference

For now, read the source code at src/index.js. The following is an exhaustive list of all functions provided under W:

isSuccess
value
or
seq
many
opt
terminals
skip
wrap
mapSeq
list
digit
integer
whitespace
lazyMany
any
expect

And the following is an exhaustive list of all mapping functions:

map
nth
const
set
skip
skipLast
take
takeLast

License

MIT © Martin Svanberg