import type { ImplicitParjser, ParjsCombinator } from "../../index"; import type { CombinatorInput } from "../combinated"; import { Combinated } from "../combinated"; import { Issues } from "../issues"; import type { ParjserBase } from "../parser"; import { ResultKind } from "../result"; import type { ParsingState } from "../state"; import { wrapImplicit } from "../wrap-implicit"; export type ArrayWithSeparators = Normal[] & { separators: Separator[]; }; export function getArrayWithSeparators( things: T[], separators: S[] ): ArrayWithSeparators { const array = things as ArrayWithSeparators; array.separators = separators; return array; } export interface ManySepByOptions { delimeter: CombinatorInput; max?: number; } class ManySepBy extends Combinated> { type = "manySepBy"; expecting = this.source.expecting; constructor( source: CombinatorInput, private readonly _options: ManySepByOptions ) { super(source); } _apply(ps: ParsingState): void { const results: ArrayWithSeparators = getArrayWithSeparators([], []); const { source, _options: { delimeter, max = Infinity } } = this; source.apply(ps); if (ps.atLeast(ResultKind.HardFail)) { return; } else if (ps.isSoft) { ps.value = results; ps.kind = ResultKind.Ok; return; } let { position } = ps; results.push(ps.value as E); let i = 1; for (;;) { if (i >= max) break; delimeter.apply(ps); if (ps.isSoft) { break; } else if (ps.atLeast(ResultKind.HardFail)) { return; } results.separators.push(ps.value as Sep); source.apply(ps); if (ps.isSoft) { break; } else if (ps.atLeast(ResultKind.HardFail)) { return; } if (max >= Infinity && ps.position === position) { Issues.guardAgainstInfiniteLoop("manySepBy"); } results.push(ps.value as E); position = ps.position; i++; } ps.kind = ResultKind.Ok; ps.position = position; ps.value = results; } } /** * Applies the source parser repeatedly until it fails softly, with each pair of applications * separated by applying `delimeter`. Also terminates if `delimeter` fails softly. Yields all the * results of the source parser in an array. * * @template E The type of the source parser. * @template Sep The type of the delimeter (separator) parser. * @param delimeter Parser that separates two applications of the source. * @param max Optionally, then maximum number of times to apply the source parser. Defaults to * `Infinity`. */ export function manySepBy( delimeter: ImplicitParjser, max?: number ): ParjsCombinator>; export function manySepBy(implDelimeter: ImplicitParjser, max = Infinity) { const delimeter = wrapImplicit(implDelimeter) as ParjserBase; return (source: ImplicitParjser) => { return new ManySepBy(wrapImplicit(source), { delimeter, max }); }; }