TypeScript Parser Combinator

0.3.4 · maintenance · verified Sun Apr 19

typescript-parsec is a parser combinator library designed for TypeScript, allowing developers to construct parsers quickly with a concise API. It provides utilities for lexing (tokenizing input) and parsing (defining grammar rules) using a functional, combinatory approach. As of version 0.3.4, it is still in a pre-1.0 development phase, meaning API stability might evolve, though it has been maintained by Microsoft. The library's release cadence is not strictly defined, but updates occur periodically based on needs and contributions to the underlying Microsoft research projects. It differentiates itself by being explicitly typed for TypeScript, offering robust type inference and safety for parser definitions, making it suitable for building language front-ends directly within a TypeScript codebase. Its focus is on developer experience, providing a clear and efficient way to define complex grammars with strong type guarantees, leveraging the TypeScript ecosystem effectively.

Common errors

Warnings

Install

Imports

Quickstart

Demonstrates how to define a simple arithmetic expression parser using `typescript-parsec`, including lexer definition, parser rules for terms, factors, and expressions, and the evaluation of an example string.

import { buildLexer, expectEOF, expectSingleResult, rule, Token } from 'typescript-parsec';
import { alt, apply, kmid, lrec_sc, seq, str, tok } from 'typescript-parsec';

enum TokenKind {
    Number,
    Add,
    Sub,
    Mul,
    Div,
    LParen,
    RParen,
    Space
}

const lexer = buildLexer([
    [true, /^\d+(\.\d+)?/g, TokenKind.Number],
    [true, /^\+/g, TokenKind.Add],
    [true, /^-/g, TokenKind.Sub],
    [true, /^\*/g, TokenKind.Mul],
    [true, /^\//g, TokenKind.Div],
    [true, /^\(/g, TokenKind.LParen],
    [true, /^\)/g, TokenKind.RParen],
    [false, /^\s+/g, TokenKind.Space]
]);

function applyNumber(value: Token<TokenKind.Number>): number {
    return +value.text;
}

function applyUnary(value: [Token<TokenKind>, number]): number {
    switch (value[0].text) {
        case '+': return +value[1];
        case '-': return -value[1];
        default: throw new Error(`Unknown unary operator: ${value[0].text}`);
    }
}

function applyBinary(first: number, second: [Token<TokenKind>, number]): number {
    switch (second[0].text) {
        case '+': return first + second[1];
        case '-': return first - second[1];
        case '*': return first * second[1];
        case '/': return first / second[1];
        default: throw new Error(`Unknown binary operator: ${second[0].text}`);
    }
}

const TERM = rule<TokenKind, number>();
const FACTOR = rule<TokenKind, number>();
const EXP = rule<TokenKind, number>();

TERM.setPattern(
    alt(
        apply(tok(TokenKind.Number), applyNumber),
        apply(seq(alt(str('+'), str('-')), TERM), applyUnary),
        kmid(str('('), EXP, str(')'))
    )
);

FACTOR.setPattern(
    lrec_sc(TERM, seq(alt(str('*'), str('/')), TERM), applyBinary)
);

EXP.setPattern(
    lrec_sc(FACTOR, seq(alt(str('+'), str('-')), FACTOR), applyBinary)
);

function evaluate(expr: string): number {
    return expectSingleResult(expectEOF(EXP.parse(lexer.parse(expr))));
}

console.log(`(1 + 2) * (3 + 4) = ${evaluate('(1 + 2) * (3 + 4)')}`);

view raw JSON →