parjs
Version:
Library for building parsers using combinators.
115 lines (108 loc) • 4.36 kB
text/typescript
import type { ImplicitParjser, ParjsCombinator } from "../../index";
import type { CombinatorInput } from "../combinated";
import { Combinated } from "../combinated";
import { Issues } from "../issues";
import { ResultKind } from "../result";
import type { ParsingState, UserState } from "../state";
import { wrapImplicit } from "../wrap-implicit";
import { pipe } from "./combinator";
import { qthen } from "./then";
const defaultProjection = <TSource>(sourceMatches: TSource[], till: unknown, state: unknown) =>
sourceMatches;
class ManyTill<TSource, TTill, TResult> extends Combinated<TSource, TResult> {
type = "manyTill";
expecting = `${this.source.expecting} or ${this._till.expecting}`;
constructor(
source: CombinatorInput<TSource>,
private readonly _till: CombinatorInput<TTill>,
private readonly _project: (source: TSource[], till: TTill, user: UserState) => TResult
) {
super(source);
}
_apply(ps: ParsingState): void {
let { position } = ps;
const arr: TSource[] = [];
let successes = 0;
for (;;) {
this._till.apply(ps);
if (ps.isOk) {
break;
} else if (ps.atLeast(ResultKind.HardFail)) {
// if till failed hard/fatally, we return the fail result.
return;
}
// backtrack to before till failed.
ps.position = position;
this.source.apply(ps);
if (ps.isOk) {
arr.push(ps.value as TSource);
} else if (ps.isSoft) {
// many failed softly before till...
ps.kind = successes === 0 ? ResultKind.SoftFail : ResultKind.HardFail;
return;
} else {
// many failed hard/fatal
return;
}
if (ps.position === position) {
Issues.guardAgainstInfiniteLoop("manyTill");
}
position = ps.position;
successes++;
}
ps.value = this._project(arr, ps.value as TTill, ps.userState);
ps.kind = ResultKind.Ok;
}
}
/**
* Tries to apply the source parser repeatedly until `till` succeeds. Yields the results of the
* source parser in an array.
*
* @param till The parser that indicates iteration should stop.
* @param pProject A projection to apply on the captured results.
*/
export function manyTill<TSource, TTill, TResult = TSource[]>(
till: ImplicitParjser<TTill>,
pProject: (source: TSource[], till: TTill, user: UserState) => TResult
): ParjsCombinator<TSource, TResult>;
export function manyTill<TSource, TTill>(
till: ImplicitParjser<TTill>
): ParjsCombinator<TSource, TSource[]>;
export function manyTill<TSource, TTill, TResult = TSource[]>(
till: ImplicitParjser<TTill>,
pProject?: (source: TSource[], till: TTill, user: UserState) => TResult
): ParjsCombinator<TSource, TResult> {
const tillResolved = wrapImplicit(till);
const project = pProject || defaultProjection;
return (source: ImplicitParjser<TSource>) => {
return new ManyTill<TSource, TTill, TResult>(
wrapImplicit(source),
tillResolved,
// eslint-disable-next-line @typescript-eslint/no-explicit-any
project as any
);
};
}
/**
* Applies `start` and then repeatedly applies the source parser until `pTill` succeeds. Similar to
* a mix of `between` and `manyTill`. Yields the results of the source parser in an array.
*
* @param start The initial parser to apply.
* @param pTill Optionally, the terminator. Defaults to `start`.
* @param projection Optionally, a projection to apply on the captured results.
*/
export function manyBetween<TSource, TStart, TTill = TStart, TResult = TSource[]>(
start: ImplicitParjser<TStart>,
pTill?: ImplicitParjser<TTill>,
projection?: (sources: TSource[], till: TTill | TStart, state: UserState) => TResult
): ParjsCombinator<TSource, TResult> {
const till: ImplicitParjser<TTill | TStart> = pTill || start;
return source => {
const wrapped = wrapImplicit(source);
return pipe(
start,
// eslint-disable-next-line @typescript-eslint/no-explicit-any
qthen(wrapped.pipe(manyTill(till, projection as any)))
);
};
}