New:Microsoft Teams Notifications Are Now Available in Socket.Learn more →
Get Started

parseman

Package Overview
Dependencies
Maintainers
1
Versions
82
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

parseman

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

Source
npmnpm
Version
0.50.7
Version published
Weekly downloads
567
-50.04%
Maintainers
1
Weekly downloads
 
Created
Source

Parséman — 100% Pure Parsing

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 compiled Parséman takes 148 µs; Chevrotain takes 336 µs. Only a purpose-built native edges it out: JSON.parse on JSON.

GraphQL parsing benchmarks

Two more results. The setup-free interpreter now leads every external parser at every measured JSON, CSV, and GraphQL size. The compiled CST path beats Lezer on the JSON CST fixture — 148 µs vs 583 µ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

  • 🧩 Combinators — literal, regex, sequence, choice, many, sepBy, token, peek, not, and more.
  • 🌀 Recursive rules — rules() 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 grammars — compose() a dialect onto a base grammar instead of forking it.
  • 🎛️ Context-sensitive parsing — withCtx / gate without mutating shared state.
  • 🧭 Scannerless routing — dispatch parses a broad shared head once and routes at the grammar boundary that matters.

🌳 Getting structure out

  • 🌲 CST / AST nodes — node() captures terminals, named fields, and trivia, with policies for wrapping and collapsing.
  • 🩹 Error recovery — keep parsing broken input and report every error.
  • ⚡ Incremental re-parsing — parseDoc 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 & EBNF — toRailroadHtml() 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 diagnostics — diagnoseGrammar() 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 147.76 µs vs Chevrotain's 335.75 µs, JSON 122.09 µs vs Chevrotain's 242.86 µs, CSV 75.02 µs vs Peggy's 433.52 µs. Native JSON.parse does JSON large in 40.61 µs. On the CST chart, runtime compile runs 151.87 µs against Lezer's 582.14 µs parse-only.

Those are the committed 0.50.2 charts, regenerated on 2026-08-29 on an M4 Pro with Node 24.11.1. They include small, medium, and large rows for JSON, GraphQL, and CST, plus small and large CSV rows.

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

Keywords

parser

FAQs

Package last updated on 20 Sep 2026

Related posts