sounisi5011/ts-peg

View on GitHub
src/parser/internal/parser.ts

Summary

Maintainability
A
0 mins
Test Coverage
import {
    ActionFunc,
    ActionParser,
    MatchedTextParser,
    MatchPredicateParser,
    MatchPredicateType,
    OneOrMoreParser,
    OptionalParser,
    ParseFailureResult,
    ParseResult,
    ParserGenerator,
    TimesParser,
    ValueConverterParser,
    ZeroOrMoreParser,
} from '../../internal';
import {
    OneOrMoreReadonlyTuple,
    OneOrMoreTuple,
    RepeatTuple,
} from '../../types';
import { CacheStore } from '../../utils/cache-store';

export type ParserResultDataType<T extends Parser<unknown>> = T extends Parser<
    infer U
>
    ? U
    : never;

export abstract class Parser<TResult> {
    private readonly __parserGenerator: ParserGenerator;
    private readonly __memoStore = new CacheStore<
        [string, number, number],
        ParseResult<TResult>
    >();

    constructor(parserGenerator: ParserGenerator) {
        this.__parserGenerator = parserGenerator;
    }

    protected abstract __parse(
        input: string,
        offsetStart: number,
        stopOffset: number,
    ): ParseResult<TResult>;

    get parserGenerator(): ParserGenerator {
        return this.__parserGenerator;
    }

    get zeroOrMore(): Parser<TResult[]> {
        return new ZeroOrMoreParser(this);
    }

    get oneOrMore(): Parser<OneOrMoreTuple<TResult>> {
        return new OneOrMoreParser(this);
    }

    get optional(): Parser<TResult | undefined> {
        return new OptionalParser(this);
    }

    get text(): Parser<string> {
        return new MatchedTextParser(this);
    }

    times<TCount extends number>(
        count: TCount,
    ): Parser<RepeatTuple<TResult, TCount>>;

    times(count: number): Parser<TResult[]> {
        try {
            return new TimesParser(this, count);
        } catch (error) {
            if (error instanceof TypeError) {
                throw new TypeError('repeat count must be a positive integer');
            } else if (error instanceof RangeError) {
                throw new RangeError('repeat count must be a positive integer');
            }
            throw error;
        }
    }

    // Note: This line is **required**. DO NOT DELETE IT.
    //       If you do not specify a return type definition for actionFn, tuples are converted to arrays.
    //       However, this overload solves this problem.
    action<TActionRes extends OneOrMoreReadonlyTuple<unknown>>(
        actionFn: ActionFunc<TResult, TActionRes>,
    ): Parser<TActionRes>;

    action<TActionRes extends unknown>(
        actionFn: ActionFunc<TResult, TActionRes>,
    ): Parser<TActionRes>;

    action<TActionRes>(
        actionFn: ActionFunc<TResult, TActionRes>,
    ): Parser<TActionRes> {
        return new ActionParser(this, actionFn);
    }

    value<TValue extends boolean>(value: TValue): Parser<TValue>;
    value<TValue extends number>(value: TValue): Parser<TValue>;
    value<TValue extends string>(value: TValue): Parser<TValue>;
    value<TValue extends OneOrMoreReadonlyTuple<unknown>>(
        value: TValue,
    ): Parser<TValue>;

    value<TValue extends unknown>(value: TValue): Parser<TValue>;
    value<TValue>(value: TValue): Parser<TValue> {
        return new ValueConverterParser(this, value);
    }

    match(predicate: MatchPredicateType<TResult>): Parser<TResult> {
        if (arguments.length < 1) throw new Error('one argument required');
        return new MatchPredicateParser(this, predicate, {
            negative: false,
            errorMessage: {
                predicateType:
                    'only the Parser object or function can be specified as argument',
            },
        });
    }

    unmatch(predicate: MatchPredicateType<TResult>): Parser<TResult> {
        if (arguments.length < 1) throw new Error('one argument required');
        return new MatchPredicateParser(this, predicate, {
            negative: true,
            errorMessage: {
                predicateType:
                    'only the Parser object or function can be specified as argument',
            },
        });
    }

    parse(input: string, offsetStart: number = 0): TResult {
        const result = this.tryParse(input, offsetStart, Infinity);
        if (result instanceof ParseFailureResult) {
            throw new Error('Parse fail!');
        }
        if (result.offsetEnd < input.length) {
            throw new Error('Parse fail! end-of-input was not reached');
        }
        return result.data;
    }

    tryParse(
        input: string,
        offsetStart: number,
        stopOffset: number,
    ): ParseResult<TResult> {
        if (input.length < offsetStart)
            return new ParseFailureResult({ allowCache: true });

        return this.__memoStore.upsert(
            [input, offsetStart, stopOffset],
            cachedResult => {
                if (cachedResult.allowCache) return cachedResult;
                return this.__parse(input, offsetStart, stopOffset);
            },
            () => this.__parse(input, offsetStart, stopOffset),
        );
    }
}