ABNF Parser Generator
JavaScript APG is a robust ABNF Parser Generator that enables developers to create recursive-descent parsers directly from a superset of ABNF (SABNF). It is currently stable at version 4.4.0, with a release cadence that has seen several updates in the past year, indicating active development and maintenance. Key differentiators include its ability to generate parsers for both standard `apg-js` and the more lightweight `apg-lite` framework, and its focus on fixing issues related to large grammars with mutually-recursive rules, a significant improvement over its predecessors like `apg-js2`. The library is designed to produce efficient parsers and provides options for TypeScript-compatible grammar objects, making it suitable for modern JavaScript and TypeScript environments.
Common errors
-
ReferenceError: Buffer is not defined
cause Prior to `apg-js@4.2.0`, the library relied on global Node.js objects like `Buffer` without explicit imports, causing issues in non-Node.js environments or when bundled.fixUpgrade `apg-js` to version `4.2.0` or higher to resolve direct global Node.js dependency issues. -
Build error with browserify when using `require('node:buffer')` or similar 'node:' prefixed imports.cause Using the `node:` prefix with `require()` (e.g., `require('node:buffer')`) can cause compatibility problems with bundlers like `browserify`.fixThe library itself fixed this in `apg-js@4.2.0` by removing the `node:` prefix. Ensure your project and any custom tooling use the standard `require('buffer')` format where applicable. -
Parser fails or hangs unexpectedly on large or complex grammars with many mutually-recursive rules.
cause A major bug in the attributes algorithm of `apg-js2` and earlier versions could lead to parsing failures or performance issues with complex, mutually-recursive ABNF rules.fixMigrate your project to `apg-js@4.0.0` or a newer version, which contains a corrected attributes algorithm, and retest your grammars.
Warnings
- breaking `apg-js` versions prior to 4.2.0 could cause bundling issues in browser environments or with specific bundlers due to reliance on `this` context for exported functions and direct use of Node.js globals like `Buffer` without explicit imports.
- breaking In `apg-js` versions prior to 4.2.1, ABNF zero repetitions (e.g., `0char`, `0"x"`) were inconsistently and often incorrectly processed as empty string acceptors, sometimes failing unexpectedly.
- deprecated The use of zero repetitions (e.g., `0"x"`) for empty string acceptance in ABNF grammars is deprecated, even though `apg-js` 4.2.1+ supports it correctly. Not all APG implementations accept it, and it is slightly less efficient.
- breaking `apg-js` (version 4.x) is a complete rewrite and replacement for older packages like `apg-js2`, `apg-js2-lib`, `apg-js2-exp`, `apg-js2-api`, `apg-conv`, and `apg-conv-api`. It fixes a critical flaw in the attributes algorithm that could cause failures with large grammars and extensive mutual recursion in previous versions.
Install
-
npm install apg-js -
yarn add apg-js -
pnpm add apg-js
Imports
- ApgParser
const ApgParser = require('apg-js').ApgParser;import { ApgParser } from 'apg-js'; - apgUtilities
const apgUtilities = require('apg-js').apgUtilities;import { apgUtilities } from 'apg-js';
Quickstart
import { ApgParser } from 'apg-js';
// Define a simple grammar object programmatically. In a real scenario,
// this would often be generated from an ABNF file using the apg-js generator.
// This grammar parses a list of digits separated by commas.
const grammar = {
_grammar: 'SimpleList',
callbacks: {
list: () => {}, // No semantic action needed for this simple example
element: () => {},
digit: () => {}
},
rules: {
list: {
// list = element *("," element)
op: [
{ type: 1, p: 'element' },
{ type: 2, p: [
{ type: 7, p: ',' },
{ type: 1, p: 'element' }
]}
]
},
element: {
// element = 1*DIGIT
op: [{ type: 5, min: 1, max: 0, p: 'digit' }]
},
digit: {
// digit = %d48-57 ; '0'-'9'
op: [{ type: 3, min: 48, max: 57 }]
}
},
udts: {},
operators: [],
ast: {
defined: false,
nodes: []
}
};
const parser = new ApgParser(grammar);
const inputString1 = "1,23,456";
const result1 = parser.parse(inputString1);
if (result1.success) {
console.log(`Successfully parsed "${inputString1}"`);
} else {
console.error(`Failed to parse "${inputString1}": ${result1.state.toString()}`);
}
const inputString2 = "1,23,A456"; // Invalid input
const result2 = parser.parse(inputString2);
if (result2.success) {
console.log(`Successfully parsed "${inputString2}"`);
} else {
console.error(`Failed to parse "${inputString2}": ${result2.state.toString()}`);
}