nearley
nearley is an Earley parser toolkit for JavaScript. You describe a grammar in a .ne file, compile it to a JavaScript module with nearleyc, then feed text to a Parser and read every valid result. Unlike parser generators limited to a narrower grammar class, nearley can accept ambiguous and left-recursive grammars. It also ships command-line tools for interactive tests, random valid-input generation, and railroad diagrams, plus optional Moo lexer integration for faster token-based parsing. That flexibility is useful for small languages and file formats, but it also makes ambiguity and grammar performance the developer's responsibility.
nearley remains unusually capable when Earley parsing, streaming feeds, or grammar fuzzing is the actual requirement. For a new deterministic language tool, its aging release and ambiguity burden make Peggy or Chevrotain the safer starting point.
Use it if
- You need a JavaScript parser generator that accepts left-recursive or ambiguous context-free grammars
- You want to stream input through repeated feed calls rather than parse one complete string at once
- You value grammar tooling such as nearley-test, nearley-unparse, and nearley-railroad
- You want to pair a grammar with Moo tokens and JavaScript postprocessors for a custom AST
- You want a parser with recent release momentum: npm is still at 2.20.1 and the canonical repository's last push was 2024-11-14
- You need ambiguity rejected automatically: Parser.results deliberately contains every parse, and the docs tell applications to check that its length is exactly one
- You need error recovery after invalid input: the parser docs say a syntax error cannot be recovered from by feeding more text
- You plan to use reject for semantic filtering: the grammar docs warn that reject makes grammars non-context-free and is often much slower
- You want a no-build runtime library: production use normally adds a .ne source file, a nearleyc compilation step, a generated module, and application-owned postprocessors
Setup reality
Install nearley locally and keep grammar compilation in project scripts instead of relying on a global binary. A normal project stores grammar.ne, runs nearleyc grammar.ne -o grammar.js, and imports the generated module at runtime. That generated file is an artifact, so decide whether to commit it or compile it in every build and package step. The parser returns an array because a grammar may have zero, one, or many results. An empty array after the final feed means the input is incomplete or invalid; more than one result means your grammar is ambiguous, and blindly taking results[0] hides the defect. Postprocessors receive child values, a zero-based location, and a reject sentinel. They are where you shape the AST, but returning reject is documented as a frequent performance trap. For serious grammars, add Moo and use token references; nearley's bundled character-class grammar helpers are convenient for prototypes but the docs say a lexer is faster. Streaming means feed can be called repeatedly, not that syntax errors are recoverable. If feed throws on a bad token, create a fresh parser. TypeScript postprocessors are supported through @preprocessor typescript, but the docs call out default-import behavior for compiled grammars. Tooling also needs care in CI: nearley-unparse generates valid random strings but does not prove semantic correctness, and nearley-test needs keepHistory when you want the internal parse table. The package declares no Node engine range in its current npm metadata, so pin and test your actual runtime rather than assuming a documented floor.
Patterns
Compile a grammar into JavaScriptcompile-grammar
npx nearleyc src/expression.ne -o src/expression.jsThe runtime consumes the generated JavaScript module, not the .ne file. Put this command in the build so generated code cannot go stale.
Parse input and require one resultparse-input
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];Never silently take results[0]. Zero results means incomplete or invalid final input; multiple results expose grammar ambiguity.
Build an AST in a postprocessordefine-postprocessor
@{%
function binary(data) {
return { type: 'add', left: data[0], right: data[2] };
}
%}
expression -> number _ "+" _ number {% binary %}
number -> [0-9]:+ {% d => Number(d[0].join('')) %}
_ -> [\s]:*Postprocessors receive an array containing each symbol's parsed value. Keep AST shaping here so application code does not depend on nested grammar arrays.
Express repetition with EBNF modifiersuse-ebnf
word -> [a-z]:+ {% d => d[0].join('') %}
words -> word ("," _ word):* {%
d => [d[0], ...d[1].map(part => part[2])]
%}
_ -> [\s]:*The :+ and :* expansions create nested arrays. Add a postprocessor immediately instead of leaking that generated shape into consumers.
Feed a parser in chunksstream-input
const parser = new nearley.Parser(nearley.Grammar.fromCompiled(grammar));
parser.feed('if (ready) {');
parser.feed('run()');
parser.feed('}');
const [program] = parser.results;Streaming allows repeated feed calls, but a thrown syntax error is terminal for that parser. Start a new Parser to recover.
Tokenize with Moointegrate-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)
%}With a lexer, reference tokens as %name and read token.value. The docs recommend a tokenizer over reject-heavy or character-by-character production grammars.
Split grammar files with includeimport-grammar
@include "./number.ne"
main -> number _ "items" {% d => ({ count: d[0] }) %}
_ -> [\s]:*Included files are expanded at compile time. Resolve paths from the grammar file and keep rule names from colliding.
Inspect a grammar from the command linetest-from-cli
npx nearleyc src/expression.ne -o /tmp/expression.js
printf '2 + 3' | npx nearley-test /tmp/expression.jsnearley-test prints parse results and can inspect parser state. It is an exploration tool, not a replacement for assertions in your test runner.
Generate valid random examplesgenerate-fuzz-input
npx nearley-unparse -s expression src/expression.js -n 25Generated strings exercise what the grammar accepts, but they do not prove the postprocessed AST or application semantics are correct.
Generate a railroad diagramdraw-railroad-diagram
npx nearley-railroad src/expression.ne -o docs/expression-grammar.htmlThe output is an HTML document containing SVG diagrams. Regenerate it when the source grammar changes.
Alternatives
| Package | Registry | Pick it when |
|---|---|---|
| peggy | npm | You want an actively maintained PEG generator with deterministic ordered choices and good browser support |
| chevrotain | npm | You prefer a fast code-defined parser toolkit with explicit tokens, recovery, and TypeScript-oriented tooling |
| ohm-js | npm | You want readable PEG-style grammars separated from semantic operations |