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.

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'.
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.
npm install lop
yarn add lop
pnpm add lop

Demonstrates a simple parser using lop with a regex tokeniser and parser combinators, including handling of cut and custom token types.

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);
}