Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

101 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

combine

Typed parser combinators for TypeScript (Deno + Node). Build small parsers, then compose them into a grammar.

CI JSR npm

Install

Deno (JSR)

import { seq, str } from "jsr:@claudiu-ceia/combine@^0.6.0";

Subpath imports are also supported:

import { recognizeAt } from "jsr:@claudiu-ceia/combine/nondeterministic";
import { createTracer } from "jsr:@claudiu-ceia/combine/perf";

Node (npm)

npm i @claudiu-ceia/combine

Note: the npm package is ESM-only.

import { seq, str } from "@claudiu-ceia/combine";

If you're in a CommonJS project, use a dynamic import:

(async () => {
  const { seq, str } = await import("@claudiu-ceia/combine");
  // Use seq and str here.
})();

Quickstart

Parsers are plain functions: (ctx) => Result<T>. Use parseAll when the parser must consume the complete input, or runParser for partial parsing and custom start offsets.

import {
  map,
  optional,
  parseAll,
  regex,
  seq,
  space,
  str,
  trim,
} from "@claudiu-ceia/combine";

const name = trim(regex(/[^!]+/, "name"));
const hello = map(
  seq(str("Hello,"), optional(space()), name, str("!")),
  ([, , who]) => who,
);

const result = parseAll(hello, "Hello, World!");

if (result.success) {
  console.log(result.value); // "World"
} else {
  console.error(result.expected, result.location);
}

Common Building Blocks

The library exports a lot of small pieces; these are the ones you'll likely reach for first:

  • Parsers: str, regex, digit, letter, int, double, space, eof
  • Composition: seq, any, either, oneOf, many, many1, optional
  • Transform: map, mapJoin, trim

If you like learning by examples, start with tests/.

Recursion (Grammars)

When a parser needs to reference itself (directly or indirectly), wrap the reference with lazy:

import { any, lazy, map, type Parser, seq, str } from "@claudiu-ceia/combine";

type Expr = { kind: "paren"; inner: Expr } | { kind: "lit"; value: string };

const lit: Parser<Expr> = map(str("x"), (value) => ({ kind: "lit", value }));
const paren: Parser<Expr> = map(
  seq(str("("), lazy(() => expr), str(")")),
  ([, inner]) => ({ kind: "paren", inner }),
);

// A tiny recursive expression: x | (expr)
const expr: Parser<Expr> = any(lit, paren);

If you're defining a larger mutually-recursive grammar, use defineLanguage with a map of production output types. It provides fully typed sibling parsers without making declaration order significant. See docs/guide.md.

Better Errors

For user-facing parsers, wrap important nodes with context(...), and commit to branches with cut(...) (to avoid confusing backtracking). To print failures:

import { formatErrorStack } from "@claudiu-ceia/combine";

if (!result.success) console.error(formatErrorStack(result));

Nondeterministic Recognizers

Most combinators are deterministic: they return a single success or failure. For tokenizer-like use cases where you want multiple simultaneous matches at the same input position, use the nondeterministic/recognizer module:

import { recognizeAt } from "jsr:@claudiu-ceia/combine/nondeterministic";

These combinators can return multiple successes; you must decide how (or whether) to advance the cursor.

More Examples

  • tests/ has the most coverage and real usage patterns
  • examples/ contains small runnable snippets

Guides

If you want the deeper explanations (recursion patterns, defineLanguage, error handling, cut vs context, and any vs furthest), see docs/guide.md.

The guide also covers the optional lexer layer (lexeme, symbol, keyword, createLexer) for trivia/comments, plus the library's UTF-16 offset policy.

License

MIT © Claudiu Ceia

About

An implementation of parser combinators for Typescript

Topics

Resources

Stars

5 stars

Watchers

1 watching

Forks

Releases

Packages

Used by

Contributors

Languages