TypeScript Parser Combinators (parser-ts)
`parser-ts` is a library providing string parser combinators for TypeScript, heavily influenced by the `purescript-eulalie` library and built upon the foundational `fp-ts` functional programming toolkit. It enables developers to construct complex parsers by combining simpler parsing functions in a declarative manner, leveraging `fp-ts`'s algebraic data types and functional patterns. The current stable version is 0.7.0, with releases occurring periodically to address bugs, introduce new combinators, and align with `fp-ts` peer dependency updates. Its key differentiators include its strong TypeScript typing, functional purity, and close integration with the `fp-ts` ecosystem, making it suitable for applications requiring robust and composable parsing logic within a functional TypeScript codebase. The library primarily focuses on string parsing and is often used for creating DSLs, configuration file parsers, or simple language frontends.
Common errors
-
Error: Cannot find module 'fp-ts' or npm WARN peer dependency fp-ts@^2.14.0
cause The `fp-ts` library is a peer dependency of `parser-ts` and must be installed explicitly in your project's `node_modules`.fixInstall `fp-ts` directly into your project: `npm install fp-ts@^2.14.0` or `yarn add fp-ts@^2.14.0`. Always check `parser-ts`'s `peerDependencies` for the exact compatible version range. -
Property 'value' does not exist on type 'Either<ParseError, [unknown, string]>'.
cause The `run` function of a `Parser` returns a result wrapped in `fp-ts/Either`. This `Either` type represents either a `Left` (parse error) or a `Right` (successful parse). You must handle both cases and extract the value safely.fixUse `fp-ts/Either`'s combinators like `match`, `fold`, `isLeft`, or `isRight` to safely access the parsed value. For example: `E.match(() => console.error('Error'), ([value, _]) => console.log('Parsed:', value))(result)`. -
Parsing consistently returns an `Left` error, even for seemingly valid input.
cause This typically indicates a subtle logic error in your parser combinator composition. Common issues include not handling whitespace, incorrect ordering of parsers (e.g., trying to parse a keyword after a more general identifier), or a parser consuming more input than intended.fixDebug your parser step-by-step. Use `P.run` with smaller, isolated parts of your input and parser definition to identify where the failure occurs. Pay close attention to optional parsers (`P.optional`) and sequence combinators (`P.apS`, `P.sequenceS`) to ensure they match your input's structure.
Warnings
- breaking Version 0.7.0 upgraded the `fp-ts` peer dependency to `^2.14.0`. Projects using an older `fp-ts` version (e.g., `<2.14.0`) will need to upgrade `fp-ts` to a compatible version, or `npm`/`yarn` might report peer dependency conflicts and potentially install an incompatible version, leading to type mismatches or runtime errors.
- gotcha Prior to version 0.6.12, the `string` parser (and other parsers built on deep recursion, such as `many` and `many1`) could exceed the JavaScript engine's recursion limit when attempting to parse very long input strings. This would result in a stack overflow error at runtime.
- gotcha `parser-ts` is deeply integrated with `fp-ts` and leverages advanced functional programming concepts (e.g., Monads, Applicatives, Type Classes, `pipe` for composition). Developers unfamiliar with `fp-ts` or functional TypeScript may experience a steeper learning curve.
Install
-
npm install parser-ts -
yarn add parser-ts -
pnpm add parser-ts
Imports
- string
import { string } from 'parser-ts'import { string } from 'parser-ts/string' - Parser
import { Parser } from 'parser-ts'import { Parser, run } from 'parser-ts/Parser' - pipe
import { pipe } from 'parser-ts'import { pipe } from 'fp-ts/function'
Quickstart
import { pipe } from 'fp-ts/function';
import * as P from 'parser-ts/Parser';
import * as S from 'parser-ts/string';
import * as C from 'parser-ts/char';
import * as E from 'fp-ts/Either';
// Define a parser for one or more digits, then convert to an integer
const integerParser = pipe(
S.recognize(C.many1(C.digit)), // `recognize` captures the matched string
P.map(parseInt) // `map` transforms the parsed string to a number
);
// Define a parser for a literal comma character
const commaParser = C.char(',');
// Define a parser for a list of integers separated by commas
// `sepBy` applies `integerParser`, consuming `commaParser` in between
const commaSeparatedIntegersParser = pipe(
integerParser,
P.sepBy(commaParser)
);
// Helper function to run the parser and log results
const parseNumbers = (input: string) => {
console.log(`\nAttempting to parse: "${input}"`);
const result = P.run(commaSeparatedIntegersParser, input);
E.match(
(error) => console.error('Parse Error:', error.message, 'at position', error.offset),
([value, remaining]) => console.log('Parsed Value:', value, '| Remaining Input:', remaining.length === 0 ? 'None' : `"${remaining}"`)
)(result);
};
// Example Usage
parseNumbers('1,2,3,4');
parseNumbers('100');
parseNumbers('1, 2, 3'); // Fails due to unexpected space
parseNumbers('abc');
parseNumbers('1,2,abc');