loop-controls
Version:
break/continue controls for loops and higher-order functions (sync, async, concurrent). Designed to compose with ts-pattern.
83 lines (76 loc) • 2.4 kB
text/typescript
import { _Break as BaseBreak, _Continue as BaseContinue, type Control } from "./core.js";
export function forEach<T>(
iter: Iterable<T>,
fn: (item: T, control: Control, index: number) => void
): { broken: false } | { broken: true } {
class LoopBreak<T = unknown> extends BaseBreak<T> {}
class LoopContinue extends BaseContinue {}
const $: Control = {
break: (value?: unknown): never => { throw new LoopBreak(value); },
continue: (): never => { throw new LoopContinue(); },
};
let i = 0;
for (const item of iter) {
try {
fn(item, $, i++);
} catch (e) {
if (e instanceof LoopContinue) continue;
if (e instanceof LoopBreak) return { broken: true };
throw e;
}
}
return { broken: false };
}
export function reduce<T, A>(
iter: Iterable<T>,
seed: A,
fn: (acc: A, item: T, control: Control, index: number) => A
): A {
class LoopBreak<T = unknown> extends BaseBreak<T> {}
class LoopContinue extends BaseContinue {}
const $: Control = {
break: (value?: unknown): never => { throw new LoopBreak(value); },
continue: (): never => { throw new LoopContinue(); },
};
let acc = seed, i = 0;
for (const item of iter) {
try {
acc = fn(acc, item, $, i++);
} catch (e) {
if (e instanceof LoopContinue) continue;
if (e instanceof LoopBreak) return ((e as LoopBreak).value !== undefined ? (e as LoopBreak).value : acc) as A;
throw e;
}
}
return acc;
}
export function find<T>(
iter: Iterable<T>,
fn: (item: T, control: Control, index: number) => boolean
): T | undefined;
export function find<T, S extends T>(
iter: Iterable<T>,
fn: (item: T, control: Control, index: number) => item is S
): S | undefined;
export function find<T>(
iter: Iterable<T>,
fn: (item: T, control: Control, index: number) => boolean
): T | undefined {
class LoopBreak<T = unknown> extends BaseBreak<T> {}
class LoopContinue extends BaseContinue {}
const $: Control = {
break: (value?: unknown): never => { throw new LoopBreak(value); },
continue: (): never => { throw new LoopContinue(); },
};
let i = 0;
for (const item of iter) {
try {
if (fn(item, $, i++)) return item;
} catch (e) {
if (e instanceof LoopContinue) continue;
if (e instanceof LoopBreak) return (e as LoopBreak).value as T | undefined;
throw e;
}
}
return undefined;
}