A monadic LL(infinity) parser combinator library
—
Quality
Pending
Does it follow best practices?
Impact
Pending
No eval scenarios have been run
Functions that combine multiple parsers into more complex parsing patterns. Combinators are the core building blocks for creating sophisticated parsers from simpler components.
Combinators that parse multiple elements in sequence.
/**
* Parses all given parsers in sequence, returns array of results
* @param {...Parser} parsers - Parsers to run in sequence
* @returns {Parser} Parser that returns array of all results
*/
Parsimmon.seq(p1, p2, ...pn);
/**
* Parses all parsers in sequence, applies mapper function to results
* @param {...Parser} parsers - Parsers to run in sequence
* @param {Function} mapper - Function applied to results array
* @returns {Parser} Parser that returns mapped result
*/
Parsimmon.seqMap(p1, p2, ...pn, mapper);
/**
* Parses parsers in sequence, returns object with named results
* @param {...(Parser|Array)} args - Parsers or [name, parser] pairs
* @returns {Parser} Parser that returns object with named properties
*/
Parsimmon.seqObj(...args);Usage Examples:
// Basic sequence - returns array
const pair = Parsimmon.seq(
Parsimmon.digits,
Parsimmon.string(","),
Parsimmon.digits
);
pair.parse("12,34"); // { status: true, value: ["12", ",", "34"] }
// Sequence with transformation
const coordinate = Parsimmon.seqMap(
Parsimmon.digits.map(Number),
Parsimmon.string(","),
Parsimmon.digits.map(Number),
(x, comma, y) => ({ x, y })
);
coordinate.parse("10,20"); // { status: true, value: { x: 10, y: 20 } }
// Named sequence - returns object
const point = Parsimmon.seqObj(
["x", Parsimmon.digits.map(Number)],
Parsimmon.string(","),
["y", Parsimmon.digits.map(Number)]
);
point.parse("15,25"); // { status: true, value: { x: 15, y: 25 } }Combinators that try multiple parsing alternatives.
/**
* Tries parsers in order, returns result of first success
* @param {...Parser} parsers - Parsers to try in order
* @returns {Parser} Parser that succeeds with first successful alternative
*/
Parsimmon.alt(p1, p2, ...pn);Usage Examples:
// Parse different number formats
const number = Parsimmon.alt(
Parsimmon.regexp(/0x[0-9a-fA-F]+/).map(s => parseInt(s, 16)), // hex
Parsimmon.regexp(/0b[01]+/).map(s => parseInt(s.slice(2), 2)), // binary
Parsimmon.regexp(/[0-9]+/).map(Number) // decimal
);
number.parse("42"); // { status: true, value: 42 }
number.parse("0x2A"); // { status: true, value: 42 }
number.parse("0b101010"); // { status: true, value: 42 }
// Parse keywords or identifiers
const keywordOrId = Parsimmon.alt(
Parsimmon.string("if"),
Parsimmon.string("else"),
Parsimmon.string("while"),
Parsimmon.regexp(/[a-zA-Z][a-zA-Z0-9]*/)
);Combinators for parsing lists with separators.
/**
* Parses zero or more content items separated by separator
* @param {Parser} content - Parser for list items
* @param {Parser} separator - Parser for separator between items
* @returns {Parser} Parser that returns array of content items
*/
Parsimmon.sepBy(content, separator);
/**
* Parses one or more content items separated by separator
* @param {Parser} content - Parser for list items
* @param {Parser} separator - Parser for separator between items
* @returns {Parser} Parser that returns array of content items
*/
Parsimmon.sepBy1(content, separator);Usage Examples:
// Parse comma-separated numbers
const numberList = Parsimmon.sepBy(
Parsimmon.digits.map(Number),
Parsimmon.string(",").trim(Parsimmon.optWhitespace)
);
numberList.parse("1,2,3"); // { status: true, value: [1, 2, 3] }
numberList.parse("42"); // { status: true, value: [42] }
numberList.parse(""); // { status: true, value: [] }
// Parse at least one item
const nonEmptyList = Parsimmon.sepBy1(
Parsimmon.letters,
Parsimmon.string("|")
);
nonEmptyList.parse("a|b|c"); // { status: true, value: ["a", "b", "c"] }
nonEmptyList.parse(""); // { status: false, ... }
// Parse function arguments
const args = Parsimmon.string("(")
.then(Parsimmon.sepBy(
Parsimmon.regexp(/[^,)]+/).trim(Parsimmon.optWhitespace),
Parsimmon.string(",")
))
.skip(Parsimmon.string(")"));
args.parse("(a, b, c)"); // { status: true, value: ["a", "b", "c"] }Combinators for handling recursive grammars and forward references.
/**
* Creates a lazy parser for recursive grammars
* @param {Function} fn - Function that returns a parser
* @returns {Parser} Parser that evaluates lazily
*/
Parsimmon.lazy(fn);
/**
* Creates a lazy parser with description
* @param {string} description - Description for error messages
* @param {Function} fn - Function that returns a parser
* @returns {Parser} Parser that evaluates lazily
*/
Parsimmon.lazy(description, fn);Usage Examples:
// Recursive JSON-like structure
const value = Parsimmon.lazy(() => Parsimmon.alt(
Parsimmon.regexp(/"[^"]*"/).map(s => s.slice(1, -1)), // string
Parsimmon.digits.map(Number), // number
array,
object
));
const array = Parsimmon.string("[")
.then(Parsimmon.sepBy(value, Parsimmon.string(",").trim(Parsimmon.optWhitespace)))
.skip(Parsimmon.string("]"));
const object = Parsimmon.string("{")
.then(Parsimmon.sepBy(
Parsimmon.seqMap(
Parsimmon.regexp(/"[^"]*"/).map(s => s.slice(1, -1)),
Parsimmon.string(":").trim(Parsimmon.optWhitespace),
value,
(key, colon, val) => [key, val]
),
Parsimmon.string(",").trim(Parsimmon.optWhitespace)
))
.skip(Parsimmon.string("}"))
.map(pairs => Object.fromEntries(pairs));
// Arithmetic expressions with precedence
const expr = Parsimmon.lazy("expression", () => addition);
const factor = Parsimmon.alt(
Parsimmon.digits.map(Number),
Parsimmon.string("(").then(expr).skip(Parsimmon.string(")"))
);
const term = Parsimmon.sepBy1(factor, Parsimmon.string("*"))
.map(factors => factors.reduce((a, b) => a * b));
const addition = Parsimmon.sepBy1(term, Parsimmon.string("+"))
.map(terms => terms.reduce((a, b) => a + b));Framework for creating complete parsing languages with interdependent rules.
/**
* Creates a language object with interdependent parsers
* @param {Object} parsers - Object with parser-generating functions
* @returns {Object} Language object with parser properties
*/
Parsimmon.createLanguage(parsers);Usage Examples:
// Create a simple expression language
const MathLang = Parsimmon.createLanguage({
// Basic tokens
number: () => Parsimmon.regexp(/[0-9]+/).map(Number),
operator: () => Parsimmon.oneOf("+-*/"),
// Expressions using other rules
factor: (r) => Parsimmon.alt(
r.number,
Parsimmon.string("(").then(r.expr).skip(Parsimmon.string(")"))
),
term: (r) => Parsimmon.seqMap(
r.factor,
Parsimmon.seq(Parsimmon.oneOf("*/"), r.factor).many(),
(first, rest) => rest.reduce((acc, [op, val]) =>
op === "*" ? acc * val : acc / val, first)
),
expr: (r) => Parsimmon.seqMap(
r.term,
Parsimmon.seq(Parsimmon.oneOf("+-"), r.term).many(),
(first, rest) => rest.reduce((acc, [op, val]) =>
op === "+" ? acc + val : acc - val, first)
)
});
// Use the language
MathLang.expr.parse("2 + 3 * 4"); // { status: true, value: 14 }
// JSON parser using createLanguage
const JsonLang = Parsimmon.createLanguage({
value: (r) => Parsimmon.alt(
r.string, r.number, r.boolean, r.null, r.array, r.object
),
string: () => Parsimmon.regexp(/"((?:\\.|[^"\\])*)"/,1),
number: () => Parsimmon.regexp(/-?(0|[1-9][0-9]*)([.][0-9]+)?([eE][+-]?[0-9]+)?/).map(Number),
boolean: () => Parsimmon.alt(Parsimmon.string("true"), Parsimmon.string("false")).map(x => x === "true"),
null: () => Parsimmon.string("null").result(null),
array: (r) => Parsimmon.string("[")
.then(r.value.sepBy(Parsimmon.string(",").trim(Parsimmon.optWhitespace)))
.skip(Parsimmon.string("]")),
object: (r) => Parsimmon.string("{")
.then(Parsimmon.seqMap(
r.string,
Parsimmon.string(":").trim(Parsimmon.optWhitespace),
r.value,
(key, _, value) => [key, value]
).sepBy(Parsimmon.string(",").trim(Parsimmon.optWhitespace)))
.skip(Parsimmon.string("}"))
.map(pairs => Object.fromEntries(pairs))
});Install with Tessl CLI
npx tessl i tessl/npm-parsimmon