lop
raw JSON → 0.4.2 verified Sat Apr 25 auth: no javascript
lop is a JavaScript parsing library using parser combinators that emphasizes helpful error messages. Current stable version is 0.4.2, with infrequent releases. Unlike many parser libraries (e.g., PEG.js, nearley), lop integrates directly with tokenisers and provides cut operators to control backtracking and produce precise error locations. Its unique selling point is the generation of detailed, file-and-line-anchored error messages when parsing fails. It supports both automatic regex-based tokenisation and custom tokenisers. The library is untyped (no TypeScript definitions). It is lightweight with no dependencies.
Common errors
error Error: Expected keyword "then" but got integer "42" ↓
cause Parsing failed inside a rule after a cut, preventing backtracking.
fix
Check that the token sequence matches the rule exactly. The error indicates a specific mismatch after the cut point.
error TypeError: lop.Parser is not a constructor ↓
cause Using import or require incorrectly; likely used default import instead of named.
fix
Use const { Parser } = require('lop'); or import { Parser } from 'lop';
error Error: First argument to rules.firstOf must be a string (name), followed by rules ↓
cause Missing rule name as first argument to firstOf.
fix
Call rules.firstOf('expressionName', rule1, rule2) with a string name first.
error Error: The lop module does not have a default export ↓
cause Attempted import lop from 'lop' which is incorrect.
fix
Use import * as lop from 'lop' or import { specific } from 'lop'.
Warnings
gotcha Rules must be wrapped in lop.rule() to defer evaluation when referencing rules that are defined later. ↓
fix Use rules.rule(() => ...) or lop.rule(...) for mutually recursive rules.
gotcha Token values are the first regex capture group; if your regex has no groups, value is undefined. ↓
fix Add a capture group to your regex: /(\d+)/ instead of /\d+/.
gotcha The lexer's rule order matters: the first matching rule produces the token. Unrecognized characters become 'unrecognisedCharacter' tokens. ↓
fix List specific patterns before generic ones (e.g., keywords before identifiers).
deprecated No known deprecations in current version; the library is stable but low-maintenance.
gotcha Error messages reference 'File', 'Line number', and 'Character number' only if tokens have source info from StringSource. Using raw strings without source produces less informative errors. ↓
fix Wrap input in new StringSource(string, 'description') before tokenising.
Install
npm install lop yarn add lop pnpm add lop Imports
- lop.Parser wrong
const lop = require('lop'); const Parser = lop.Parser;correctimport { Parser } from 'lop'; - RegexTokeniser wrong
const RegexTokeniser = require('lop').RegexTokeniser;correctimport { RegexTokeniser } from 'lop'; - rules wrong
const { rules } = require('lop');correctimport { rules } from 'lop'; - StringSource wrong
const StringSource = require('lop').StringSource;correctimport { StringSource } from 'lop'; - default (none) wrong
import lop from 'lop';correctimport * as lop from 'lop';
Quickstart
import { Parser, rules, tokenOfType, token, sequence, firstOf, rule } from 'lop';
import { RegexTokeniser } from 'lop';
// Define tokeniser
const tokeniser = new RegexTokeniser([
{ name: 'integer', regex: /(\d+)/ },
{ name: 'keyword', regex: /(if|then|else)/ },
{ name: 'whitespace', regex: /(\s+)/ },
]);
class IntegerNode { constructor(value) { this.value = value; } }
class IfNode { constructor(condition, trueBranch, falseBranch) { this.condition = condition; this.trueBranch = trueBranch; this.falseBranch = falseBranch; } }
const integerRule = rules.then(
rules.tokenOfType('integer'),
(value) => new IntegerNode(parseInt(value, 10))
);
const expressionRule = rules.rule(() =>
rules.firstOf('expression', integerRule, ifRule)
);
const ifRule = rules.sequence(
rules.token('keyword', 'if'),
rules.sequence.cut(),
rules.sequence.capture(expressionRule),
rules.token('keyword', 'then'),
rules.sequence.capture(expressionRule),
rules.token('keyword', 'else'),
rules.sequence.capture(expressionRule)
).map((condition, trueBranch, falseBranch) =>
new IfNode(condition, trueBranch, falseBranch)
);
const parser = new Parser();
const tokens = tokeniser.tokenise('if 1 then 2 else 3');
try {
const result = parser.parseTokens(expressionRule, tokens);
console.log(result);
} catch (e) {
console.error(e.message);
}