snapdragon is an extremely pluggable, powerful and easy-to-use parser-renderer factory.
Easy-to-use plugin system for creating powerful, fast and versatile parsers and compilers, with built-in source-map support.
Please consider following this project’s author, Jon Schlinkert, and consider starring the project to show your ❤️ and support.
Install with npm:
$ npm install --save snapdragon
Created by jonschlinkert and doowb.
Features
All of the examples in this document assume the following two lines of setup code exist first:
var Snapdragon = require('snapdragon');
var snapdragon = new Snapdragon();
Parse a string
var ast = snapdragon.parser
// parser handlers (essentially middleware)
// used for parsing substrings to create tokens
.set('foo', function () {})
.set('bar', function () {})
.parse('some string', options);
Compile an AST returned from .parse()
var result = snapdragon.compiler
// compiler handlers (essentially middleware),
// called on a node when the `node.type` matches
// the name of the handler
.set('foo', function () {})
.set('bar', function () {})
// pass the `ast` from the parse method
.compile(ast)
// the compiled string
console.log(result.output);
See the examples.
Parser handlers
Parser handlers are middleware functions responsible for matching substrings to create tokens:
Example handler
var ast = snapdragon.parser
.set('dot', function() {
var pos = this.position();
var m = this.match(/^\./);
if (!m) return;
return pos({
// the "type" will be used by the compiler later on,
// we'll go over this in the compiler docs
type: 'dot',
// "val" is the string captured by ".match",
// in this case that would be '.'
val: m[0]
});
})
.parse('.'[, options])
As a side node, it’s not scrictly required to set the type
on the token, since the parser will add it to the token if it’s undefined, based on the name of the handler. But it’s good practice since tokens aren’t always returned.
Example token
And the resulting tokens look something like this:
{
type: 'dot',
val: '.'
}
Position
Next, pos()
is called on the token as it’s returned, which patches the token with the position
of the string that was captured:
{ type: 'dot',
val: '.',
position:
{ start: { lineno: 1, column: 1 },
end: { lineno: 1, column: 2 } }}
Life as an AST node
When the token is returned, the parser pushes it onto the nodes
array of the “previous” node (since we’re in a tree, the “previous” node might be literally the last node that was created, or it might be the “parent” node inside a nested context, like when parsing brackets or something with an open or close), at which point the token begins its life as an AST node.
Wrapping up
In the parser calls all handlers and cannot find a match for a substring, an error is thrown.
Assuming the parser finished parsing the entire string, an AST is returned.
The compiler’s job is to take the AST created by the parser and convert it to a new string. It does this by iterating over each node on the AST and calling a function on the node based on its type
.
This function is called a “handler”.
Compiler handlers
Handlers are named middleware functions that are called on a node when node.type
matches the name of a registered handler.
var result = snapdragon.compiler
.set('dot', function (node) {
console.log(node.val)
//=> '.'
return this.emit(node.val);
})
If node.type
does not match a registered handler, an error is thrown.
Source maps
If you want source map support, make sure to emit the entire node as the second argument as well (this allows the compiler to get the node.position
).
var res = snapdragon.compiler
.set('dot', function (node) {
return this.emit(node.val, node);
})
This is a very basic example, but it shows how to parse a dot, then compile it as an escaped dot.
var Snapdragon = require('..');
var snapdragon = new Snapdragon();
var ast = snapdragon.parser
.set('dot', function () {
var pos = this.position();
var m = this.match(/^\./);
if (!m) return;
return pos({
type: 'dot',
val: m[0]
})
})
.parse('.')
var result = snapdragon.compiler
.set('dot', function (node) {
return this.emit('\\' + node.val);
})
.compile(ast)
console.log(result.output);
//=> '\.'
Create a new Parser
with the given input
and options
.
Params
input
{String}options
{Object}Example
var Snapdragon = require('snapdragon');
var Parser = Snapdragon.Parser;
var parser = new Parser();
Throw a formatted error message with details including the cursor position.
Params
msg
{String}: Message to use in the Error.node
{Object}returns
{undefined}Example
parser.set('foo', function(node) {
if (node.val !== 'foo') {
throw this.error('expected node.val to be "foo"', node);
}
});
Define a non-enumberable property on the Parser
instance. This is useful in plugins, for exposing methods inside handlers.
Params
key
{String}: propery nameval
{any}: property valuereturns
{Object}: Returns the Parser instance for chaining.Example
parser.define('foo', 'bar');
Create a new Node with the given val
and type
.
Params
val
{Object}type
{String}returns
{Object}: returns the Node instance.Example
parser.node('/', 'slash');
Mark position and patch node.position
.
returns
{Function}: Returns a function that takes a node
Example
parser.set('foo', function(node) {
var pos = this.position();
var match = this.match(/foo/);
if (match) {
// call `pos` with the node
return pos(this.node(match[0]));
}
});
Add parser type
with the given visitor fn
.
Params
type
{String}fn
{Function}Example
parser.set('all', function() {
var match = this.match(/^./);
if (match) {
return this.node(match[0]);
}
});
Get parser type
.
Params
type
{String}Example
var fn = parser.get('slash');
Push a node onto the stack for the given type
.
Params
type
{String}returns
{Object} token
Example
parser.set('all', function() {
var match = this.match(/^./);
if (match) {
var node = this.node(match[0]);
this.push(node);
return node;
}
});
Pop a token off of the stack of the given type
.
Params
type
{String}returns
{Object}: Returns a tokenExample
parser.set('close', function() {
var match = this.match(/^\}/);
if (match) {
var node = this.node({
type: 'close',
val: match[0]
});
this.pop(node.type);
return node;
}
});
Return true if inside a “set” of the given type
. Sets are created manually by adding a type to parser.sets
. A node is “inside” a set when an *.open
node for the given type
was previously pushed onto the set. The type is removed from the set by popping it off when the *.close
node for the given type is reached.
Params
type
{String}returns
{Boolean}Example
parser.set('close', function() {
var pos = this.position();
var m = this.match(/^\}/);
if (!m) return;
if (!this.isInside('bracket')) {
throw new Error('missing opening bracket');
}
});
Return true if node
is the given type
.
Params
node
{Object}type
{String}returns
{Boolean}Example
parser.isType(node, 'brace');
Get the previous AST node from the parser.stack
(when inside a nested context) or parser.nodes
.
returns
{Object}Example
var prev = this.prev();
Match regex
, return captures, and update the cursor position by match[0]
length.
Params
regex
{RegExp}returns
{Object}Example
// make sure to use the starting regex boundary: "^"
var match = this.match(/^\./);
Params
input
{String}returns
{Object}: Returns an AST with ast.nodes
Example
var ast = parser.parse('foo/bar');
Create a new Compiler
with the given options
.
Params
options
{Object}state
{Object}: Optionally pass a “state” object to use inside visitor functions.Example
var Snapdragon = require('snapdragon');
var Compiler = Snapdragon.Compiler;
var compiler = new Compiler();
Throw a formatted error message with details including the cursor position.
Params
msg
{String}: Message to use in the Error.node
{Object}returns
{undefined}Example
compiler.set('foo', function(node) {
if (node.val !== 'foo') {
throw this.error('expected node.val to be "foo"', node);
}
});
Concat the given string to compiler.output
.
Params
string
{String}node
{Object}: Optionally pass the node to use for position if source maps are enabled.returns
{String}: returns the stringExample
compiler.set('foo', function(node) {
this.emit(node.val, node);
});
Emit an empty string to effectively “skip” the string for the given node
, but still emit the position and node type.
Params
Example
// example: do nothing for beginning-of-string
snapdragon.compiler.set('bos', compiler.noop);
Define a non-enumberable property on the Compiler
instance. This is useful in plugins, for exposing methods inside handlers.
Params
key
{String}: propery nameval
{any}: property valuereturns
{Object}: Returns the Compiler instance for chaining.Example
compiler.define('customMethod', function() {
// do stuff
});
Add a compiler fn
for the given type
. Compilers are called when the .compile
method encounters a node of the given type to generate the output string.
Params
type
{String}fn
{Function}Example
compiler
.set('comma', function(node) {
this.emit(',');
})
.set('dot', function(node) {
this.emit('.');
})
.set('slash', function(node) {
this.emit('/');
});
Get the compiler of the given type
.
Params
type
{String}Example
var fn = compiler.get('slash');
Visit node
using the registered compiler function associated with the node.type
.
Params
node
{Object}returns
{Object}: returns the nodeExample
compiler
.set('i', function(node) {
this.visit(node);
})
Iterate over node.nodes
, calling visit on each node.
Params
node
{Object}returns
{Object}: returns the nodeExample
compiler
.set('i', function(node) {
utils.mapVisit(node);
})
Compile the given AST
and return a string. Iterates over ast.nodes
with mapVisit.
Params
ast
{Object}options
{Object}: Compiler optionsreturns
{Object}: returns the nodeExample
var ast = parser.parse('foo');
var str = compiler.compile(ast);
A few of the libraries that use snapdragon:
Breaking changes!
In an attempt to make snapdragon lighter, more versatile, and more pluggable, some major changes were made in this release.
parser.capture
was externalized to snapdragon-captureparser.capturePair
was externalized to snapdragon-capture-setBreaking changes!
Substantial breaking changes were made in v0.5.0! Most of these changes are part of a larger refactor that will be finished in 0.6.0, including the introduction of a Lexer
class.
Compiler
.render
method was renamed to .compile
Pull requests and stars are always welcome. For bugs and feature requests, please create an issue.
Running and reviewing unit tests is a great way to get familiarized with a library and its API. You can install dependencies and run tests with the following command:
$ npm install && npm test
(This project’s readme.md is generated by verb, please don’t edit the readme directly. Any changes to the readme must be made in the .verb.md readme template.)
To generate the readme, run the following command:
$ npm install -g verbose/verb#dev verb-generate-readme && verb
A few of the libraries that use snapdragon:
.captureSet()
method to snapdragon, for matching and capturing substrings that have… more | homepageCommits | Contributor |
---|---|
156 | jonschlinkert |
3 | doowb |
2 | danez |
1 | EdwardBetts |
Jon Schlinkert
Copyright © 2018, Jon Schlinkert.
Released under the MIT License.
This file was generated by verb-generate-readme, v0.6.0, on March 20, 2018.