sounisi5011/ts-peg

View on GitHub
src/parser/modifier/internal/any-or-more.ts

Summary

Maintainability
A
0 mins
Test Coverage
import {
    ConverterParser,
    ParseFailureResult,
    Parser,
    ParseResult,
    ParseSuccessResult,
} from '../../../internal';
import { CacheStore } from '../../../utils/cache-store';

const parserCache = new CacheStore<
    [Function, Parser<unknown>, number],
    AnyOrMoreParser<unknown, ParseSuccessResult<unknown>[]>
>();

type SuccessResultTuple2ResultTuple<T extends ParseSuccessResult<unknown>[]> = {
    [K in keyof T]: T[K] extends ParseSuccessResult<infer U> ? U : T[K];
};

export abstract class AnyOrMoreParser<
    TResult,
    TSuccessResultTuple extends ParseSuccessResult<TResult>[],
    TResultData extends TResult[] = SuccessResultTuple2ResultTuple<
        TSuccessResultTuple
    >
> extends ConverterParser<TResult, TResultData> {
    private readonly __resultsLengthLimit: number;

    constructor(
        prevParser: Parser<TResult>,
        { resultsLengthLimit = Infinity } = {},
    ) {
        super(prevParser);
        this.__resultsLengthLimit = resultsLengthLimit;

        const cachedParser = parserCache.upsertWithTypeGuard(
            [this.constructor, prevParser, resultsLengthLimit],
            undefined,
            () => this,
            (value): value is this => value instanceof this.constructor,
        );
        if (cachedParser && cachedParser !== this) return cachedParser;
    }

    protected abstract __resultsValidator(
        results: ParseSuccessResult<TResult>[],
    ): results is TSuccessResultTuple;

    protected __parse(
        input: string,
        offsetStart: number,
        stopOffset: number,
    ): ParseResult<TResultData> {
        const results: ParseSuccessResult<TResult>[] = [];

        let offsetNext = offsetStart;
        let allowCache = true;
        while (results.length < this.__resultsLengthLimit) {
            const result = this.__prevParser.tryParse(
                input,
                offsetNext,
                stopOffset,
            );
            allowCache = allowCache && result.allowCache;
            if (result instanceof ParseFailureResult) break;
            results.push(result);
            offsetNext = result.offsetEnd;
        }

        return this.__resultsValidator(results)
            ? new ParseSuccessResult({
                  offsetEnd: offsetNext,
                  dataGenerator: () =>
                      results.map(result => result.data) as TResultData,
                  allowCache,
              })
            : new ParseFailureResult({ allowCache });
    }
}