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 🙏

© 2026 – Pkg Stats / Ryan Hefner

parseman

v0.48.1

Published

Parser combinators that compile to optimized JavaScript — use as a library or as a build-time macro

Readme

Parséman (PAR-zə-mahn)

Write parsers as ordinary functions. Ship them like hand-written parsers.

Parser combinators are pleasant to write and usually slow. Parser generators are fast and usually mean grammar files, generated code, and extra tooling. Parséman is a combinator library with an optional compiler, and it gives you both.

Parsing to JS values, the runtime-compiled artifact is the fastest general-purpose JS parser in the suite — ahead of every other library measured, at every grammar and every input size in that suite. Every parser in the suite builds real output: objects, row arrays, AST nodes. On a 7.7 kB GraphQL document Parséman takes 188 µs; Peggy takes 323 µs. Only a purpose-built native edges it out: JSON.parse on JSON.

GraphQL parsing benchmarks

Two more results. The compiled CST path beats Lezer on the JSON CST fixture — 242 µs vs 570 µs at 11.9 kB — while producing a richer tree carrying spans and trivia. And parseDoc stores parent-relative spans, so an in-place edit costs a fraction of a full reparse rather than a multiple of it. Results move with grammar shape, input size and runtime — which is why the suite ships with the library rather than only its conclusions.

You get there by writing normal code. Add the bundler plugin, mark one import, and the combinators you already wrote compile to an optimized TableProgram artifact — the compiler computes first sets, links direct bodies for proven shapes, and left-factors choices for you.

Grammars work the same in plain JavaScript: the macro compiles a .js grammar to the same output as a .ts one. The package ships one ESM implementation, loadable through either import or require() on the supported Node versions. Write in TypeScript and result types are inferred across the whole combinator chain — types you didn't have to write out.

Reach for it when you want CST/AST nodes with spans and trivia, error recovery and incremental re-parsing for editor tooling, or simply a fast parser for a DSL, config language, formatter, or linter.

Parséman is scannerless without giving up token-style routing. Literal or first-set-disjoint alternatives are a natural fit for choice(...); when several branches share a broad opener, dispatch(selector, when(...), otherwise(...)) parses that head once, then routes by the returned value or structural marker. CSS-like grammars use that for at-rules, identifier-or-function values, media features, and dialect routes where interpolation and dedicated syntax overlap.

📖 Full documentation: matthew-dean.github.io/parseman

Install

npm install parseman
# pnpm add parseman

Pre-1.0: minor versions may carry breaking changes — check the changelog before upgrading. Requires Node ^20.19.0 || >=22.13.0.

Quick start

import { literal, sequence, choice, regex, transform, parse } from 'parseman'

const method  = choice(literal('GET'), literal('POST'), literal('PUT'), literal('DELETE'))
const target  = regex(/[^\s]+/)
const version = regex(/1\.[01]/)

const requestLine = transform(
  sequence(method, literal(' '), target, literal(' HTTP/'), version),
  ([verb, , path, , ver]) => ({ verb, path, version: `HTTP/${ver}` })
)

parse(requestLine, 'GET /api/v1 HTTP/1.1')
// { ok: true, value: { verb: 'GET', path: '/api/v1', version: 'HTTP/1.1' }, span: ... }

New here? What is Parséman? walks the same ground more slowly.

Three modes, one grammar

The same combinator code runs three ways, with identical results:

  • Interpreter — zero setup, works anywhere (tests, REPLs, dynamic grammars).
  • Macro build — a bundler plugin evaluates your grammar at build time and inlines the result. The combinator import you mark with { type: 'macro' } disappears entirely; what ships is a compact table artifact using the shared parseman/table runtime. Executing it still goes through run()/parse(), so an app keeps Parseman as an ordinary dependency — see the three modes.)
  • compile() — the same optimizer, on demand at runtime.
// Add the plugin (vite.config.ts) and one import attribute — that's the whole change:
import { literal, sequence, choice } from 'parseman' with { type: 'macro' }

See The three modes.

What's in the box

✍️ Writing grammars

  • 🧩 Combinatorsliteral, regex, sequence, choice, many, sepBy, token, peek, not, and more.
  • 🌀 Recursive rulesrules() for mutually recursive grammars; fully macro-compilable.
  • 🫧 Whitespace & trivia — grammar-defined filler skipping, with per-chunk kind capture.
  • 🎯 Ordered choice, done right — PEG semantics you control by ordering, with keyword boundaries that don't bite.
  • 🪶 Grammars the way you write them — no FIRST/FOLLOW sets to compute, no left-factoring choice(a·x, a·y) into a·(x | y) to satisfy the tool. The compiler does that for you.
  • 🧬 Extending grammarscompose() a dialect onto a base grammar instead of forking it.
  • 🎛️ Context-sensitive parsingwithCtx / gate without mutating shared state.
  • 🧭 Scannerless routingdispatch parses a broad shared head once and routes at the grammar boundary that matters.

🌳 Getting structure out

  • 🌲 CST / AST nodesnode() captures terminals, named fields, and trivia, with policies for wrapping and collapsing.
  • 🩹 Error recovery — keep parsing broken input and report every error.
  • Incremental re-parsingparseDoc re-parses just the edited subtree on each keystroke.
  • 💡 Editor / LSP integration — completions and lint keyed by rule name; the grammar stays pure structure.

🔬 Seeing what your grammar does

  • 🚂 Railroad diagrams & EBNFtoRailroadHtml() and toEBNF() generate the grammar reference from the parser, so the spec can't drift from what actually parses.
  • 🔎 Grammar observability — coverage ("which rules and choice arms ran?") and trace ("what did it try, select, and backtrack through?").
  • 🚦 First-char gating diagnosticsdiagnoseGrammar() tells you which choice lost its O(1) dispatch, names the overlapping arms, and says how to fix it. Run it in a test or a CI job — compiling reports nothing.
  • 📈 Performance guide — the levers that actually move a grammar, and how regexes lower.

Full API in the reference; how it stacks up against Peggy, Chevrotain, Lezer, tree-sitter, Parsimmon, Nearley and hand-written parsers in How Parséman compares.

Benchmarks

Benchmarked against Peggy, Parsimmon, Chevrotain, Nearley, Jison and Lezer on JSON, CSV and GraphQL, at three input sizes each. Each chart's legend names the libraries measured for that grammar.

Largest fixture of each, runtime compile against the fastest other library on that chart: GraphQL 188 µs vs Peggy's 323 µs, JSON 223 µs vs Chevrotain's 238 µs, CSV 114 µs vs Peggy's 422 µs. Native JSON.parse does JSON large in 50.3 µs. On the CST chart, runtime compile runs 242 µs against Lezer's 570 µs parse-only.

Those are the committed 0.48.0 charts, regenerated on 2026-08-14 on an M4 Pro with Node 25.9.0. They include the restored small rows for JSON, CSV, GraphQL, and CST.

JSON parsing benchmarks

CSV parsing benchmarks

JSON CST parsing benchmarks

Per-fixture figures, initialization costs, hardware, grammar provenance and how to reproduce any of it: benchmarks guide. Results move with grammar shape, input size and runtime — which is why the suite ships with the library rather than only its conclusions. Speed levers for your own grammars: performance guide.

The GraphQL fixture is a real grammar, parsing executable documents — queries, mutations, fragments, directives, every value type — into typed AST nodes, so the numbers come from a spec-shaped language rather than a toy.

Developing

pnpm install
pnpm test         # interpreter + compiled parity, ordered-choice semantics
pnpm typecheck
pnpm build        # ESM + .d.ts → dist/
pnpm docs:dev     # this documentation site, locally

Benchmark and chart tasks (pnpm bench, bench:svg, bench:parseman, …) are described in the benchmarks guide.

The frozen 0.48 runtime architecture and release specification is docs/design/parseman-0.48.md. Detailed measurements and rejected experiments remain in the linked evidence registers rather than being treated as current design.

License

MIT © Matthew Dean