nearley review
nearley 2.20.1 is an Earley parser generator for JavaScript. You write a grammar in a .ne file, compile it to JavaScript with nearleyc, feed text to a Parser, and inspect every valid parse. Earley parsing accepts left recursion and ambiguous context-free grammars that narrower generators may reject. The package also provides commands for parser tests, random valid-input generation, and railroad diagrams, while Moo supplies the recommended token layer. The current npm version has not changed since December 2020, and the repository's last push was in November 2024, so there is no recent release behavior to add.
nearley 2.20.1 installed in 1.3 seconds, used 1 MB on disk, and bundled to 2.9 KB gzipped in our sandbox with 0 audit findings. Choose it for left-recursive or intentionally ambiguous grammars; deterministic new parsers should compare the more active Peggy and Chevrotain projects.
We installed it
| Install | ✓ · 1.3s | 7 packages on disk · 1 MB |
| Import | ✓ | ESM import works · require() works · CommonJS package |
| Browser | 2.9 KB | gzipped (7.8 KB minified), bundled with esbuild |
| Types | — | no TypeScript types found |
| Known vulns | 0 | 0 critical · 0 high · 0 moderate · 0 low (npm audit) |
Answers from our run
Does nearley install cleanly?
Yes. In a fresh container with an empty cache, npm install nearley finished in 1 seconds, leaving 7 packages and 1 MB on disk. npm audit reported no known vulnerabilities.
How much does nearley add to a browser bundle?
2.9 KB gzipped (7.8 KB minified) when the whole package is bundled for the browser with esbuild. Importing only part of it is usually smaller.
Does nearley work with both ESM and CommonJS?
Yes. Both import 'nearley' and require('nearley') worked in Node 22 in our run. The package is published as CommonJS.
Does nearley include TypeScript types?
No type declarations were found in our install, so TypeScript users need their own declarations.
nearley or peggy: which should you use?
peggy: Use it for an actively maintained PEG generator with ordered, deterministic choices. nearley 2.20.1 installed in 1.3 seconds, used 1 MB on disk, and bundled to 2.9 KB gzipped in our sandbox with 0 audit findings.
When should you not use nearley?
A new parser needs active releases and prompt fixes. npm remains on 2.20.1 from 2020, while GitHub lists 199 open issues and pull requests.
Use it if
- Your grammar is left recursive or intentionally ambiguous, and receiving every valid parse is useful.
- Input arrives in chunks and the application can continue feeding the same parser until a complete result exists.
- A .ne grammar plus JavaScript postprocessors is easier for the team to review than a hand-written parser.
- Random grammar expansion and railroad diagrams will help test or explain a small language or file format.
- A new parser needs active releases and prompt fixes. npm remains on 2.20.1 from 2020, while GitHub lists 199 open issues and pull requests.
- The application expects one parse without checking. nearley returns all parses, and an ambiguous grammar can multiply work and results.
- You need recovery after an invalid token. The parser documentation says feeding more text cannot recover that Parser after the syntax error.
- Semantic predicates would dominate the grammar. The docs warn that reject changes the grammar class and can make parsing much slower.
- You want bundled TypeScript declarations, ESM exports, and no code-generation step. Our package check found none of those conveniences.
Setup reality
We installed nearley 2.20.1 in a clean Node 22 Bookworm container. npm finished in 1.3 seconds, left 7 packages on disk, and used 1 MB. The package declares 4 direct dependencies and no peer dependencies, with 140 KB unpacked. npm audit found no known vulnerabilities. It is CommonJS with no exports map; require and ESM import worked in our lab. No TypeScript declarations were found. Our browser bundle measured 7.8 KB minified and 2.9 KB gzipped.
Production use normally adds a grammar source, a nearleyc build command, and a generated JavaScript module. Decide whether the generated file is committed or rebuilt in every package and deployment job. A stale artifact is easy to ship when the .ne source changes without the compile step. Postprocessors shape nested grammar values into the AST and may execute arbitrary JavaScript, so grammar files are application code.
Parser.results can contain zero, one, or several values. Zero after the final input means the parse did not complete; several values reveal ambiguity. Always assert the intended cardinality instead of taking index zero. Repeated feed calls support chunked input, but a thrown syntax error ends useful work for that parser. Create a new Parser for another attempt.
Character rules are fine for small grammars. For a serious language, the docs recommend Moo tokens for speed and clearer errors. Install Moo separately, declare @lexer, and reference token names with percent syntax. Avoid reject as a normal semantic filter. Use nearley-unparse to generate accepted strings, then assert the produced AST and semantics in your test runner because valid syntax alone proves little.
Patterns
Generate a parser module compile-grammar
npx nearleyc src/expression.ne -o src/expression.jsAdd this to the build or commit the generated artifact. The runtime does not compile the .ne file by itself.
Require one complete parse parse-once
const nearley = require('nearley')
const grammar = require('./expression.js')
const parser = new nearley.Parser(nearley.Grammar.fromCompiled(grammar))
parser.feed('2 + 3')
if (parser.results.length !== 1) {
throw new Error(`expected one parse, got ${parser.results.length}`)
}
const ast = parser.results[0]Zero results means incomplete or invalid final input. More than one result means the grammar is ambiguous for this text.
Shape values in a postprocessor build-ast
@{%
function add(data) {
return { type: 'add', left: data[0], right: data[2] }
}
%}
expression -> number _ "+" _ number {% add %}
number -> [0-9]:+ {% d => Number(d[0].join('')) %}
_ -> [\s]:*Postprocessors receive each production value in an array. Convert the generated nesting to a stable AST here.
Collect a comma-separated list repeat-rule
word -> [a-z]:+ {% d => d[0].join('') %}
words -> word ("," _ word):* {%
d => [d[0], ...d[1].map((part) => part[2])]
%}
_ -> [\s]:*EBNF repetition produces nested arrays. Flatten them before the structure leaves the grammar.
Parse chunked input feed-chunks
const parser = new nearley.Parser(nearley.Grammar.fromCompiled(grammar))
parser.feed('if (ready) {')
parser.feed('run()')
parser.feed('}')
if (parser.results.length === 1) console.log(parser.results[0])Chunked feeds do not provide syntax recovery. Discard the parser after feed throws on an invalid token.
Attach a Moo lexer tokenize-with-moo
@{%
const moo = require('moo')
const lexer = moo.compile({
ws: { match: /\s+/, lineBreaks: true },
number: /0|[1-9][0-9]*/,
plus: '+',
})
%}
@lexer lexer
sum -> %number %ws:? %plus %ws:? %number {%
d => Number(d[0].value) + Number(d[4].value)
%}Install Moo separately. Lexer tokens expose value, line, column, and offset information for errors and AST locations.
Reuse another grammar file include-rules
@include "./number.ne"
main -> number _ "items" {% d => ({ count: d[0] }) %}
_ -> [\s]:*nearleyc expands includes at compile time. Keep rule names unique across included files.
Inspect a compiled grammar test-parser-cli
npx nearleyc src/expression.ne -o build/expression.js
npx nearley-test build/expression.js -i '2 + 3'The command is useful for inspection. Keep expected ASTs and ambiguity checks in automated tests.
Produce random accepted strings generate-inputs
npx nearley-unparse -s expression build/expression.js -n 25Generated input exercises grammar reachability. Parse it again and test the AST because accepted syntax may still have wrong semantics.
Write railroad diagrams draw-grammar
npx nearley-railroad src/expression.ne -o docs/expression-grammar.htmlRegenerate the HTML whenever the grammar changes; it is documentation output rather than an executable parser.
Save a parser checkpoint save-parser-state
const checkpoint = parser.save()
parser.feed(candidate)
// Restore and try a different continuation.
parser.restore(checkpoint)
parser.feed(alternative)A checkpoint supports controlled branching before an error. It does not repair a parser after invalid input has already been consumed.
Fail a test on several parses inspect-ambiguity
const parser = new nearley.Parser(nearley.Grammar.fromCompiled(grammar))
parser.feed(source)
expect(parser.results).toHaveLength(1)
expect(parser.results[0]).toEqual(expectedAst)Cardinality assertions catch ambiguity that a results[0] shortcut would hide.
Alternatives
| Package | Registry | Pick it when |
|---|---|---|
| peggy | npm | Use it for an actively maintained PEG generator with ordered, deterministic choices. |
| chevrotain | npm | Use it for a code-defined parser with explicit tokens, TypeScript tooling, and error recovery. |
| ohm-js | npm | Use it when grammar rules should stay separate from reusable semantic operations. |
More cli & tooling guides
chalk · commander · typescript · esbuild · yargs · click · the whole shelf →
How this guide is made: grounded in the library's documentation, release notes, changelog, and issue history, on a fixed rubric — not a hands-on install of every release. The 50 most-downloaded entries are additionally install-verified in clean containers. Corrections: contact the desk.

