import "../_dnt.polyfills.js"; import { Guard, SmartExclude } from "./is.js" import { Run, Rune, RunicArgs, Runner, Unhandled } from "./Rune.js" import { Receipt } from "./Timeline.js" type NonIndexSignatureKeys = T extends T ? keyof { [K in keyof T as {} extends Record ? never : K]: T[K] } : never type Access> = [ | T[K] | Exclude< undefined, _K extends keyof any ? Record extends Record<_K, never> ? undefined : never : never >, ][0] /** @ts-ignore: assume it's a valid key */ type GetPath = P extends [infer K, ...infer Q] ? GetPath : T type EnsurePath = never extends P ? P extends [infer K, ...infer Q] ? | [K & keyof T, ...EnsurePath] | [keyof T, ...PropertyKey[]] : [(keyof T)?] : P export class ValueRune extends Rune { static override new( ctor: new(runner: Runner, ...args: A) => Run, ...args: A ) { return new ValueRune((runner) => new ctor(runner, ...args)) } map(fn: (value: T) => T2 | Promise): ValueRune { return ValueRune.new(RunMap, this, fn) } access

( this: ValueRune, ...keys: never extends P ? RunicArgs]> : { [K in keyof P]: P[K] | Rune } ): ValueRune }>, U | RunicArgs.U> access( this: ValueRune, ...keys: RunicArgs ): ValueRune> { return Rune.tuple([this, ...RunicArgs.resolve(keys)]).map(([value, ...keys]) => { for (const key of keys) { value = value[key] } return value }) } handle( guard: Guard, alt: (rune: ValueRune) => Rune, ): ValueRune | T3, U | U2> { return ValueRune.new( RunHandle, this, guard, alt(this as never), ) } unhandle(fn: Guard): ValueRune, U | U2> unhandle(fn: Guard): ValueRune { return ValueRune.new(RunUnhandle, this, fn) } throws( ...guards: { [K in keyof U2]: Guard } ): ValueRune throws(...guards: Array>): ValueRune { return ValueRune.new(RunThrows, this, guards) } rehandle( guard: Guard, ): ValueRune> rehandle( guard: Guard, alt: (rune: ValueRune) => Rune, ): ValueRune | U3> rehandle( guard: Guard, alt: (rune: ValueRune) => Rune = (x) => x as any, ): ValueRune | U3> { return ValueRune.new( RunRehandle, this, guard, alt( ValueRune.new(RunGetUnhandled, this) .filter((x) => x !== null && guard(x.value)) .map((x) => x!.value), ), ) } lazy() { return ValueRune.new(RunLazy, this) } filter(fn: (value: T) => boolean): ValueRune filter(fn: (value: T) => value is T2): ValueRune filter(fn: (value: T) => boolean): ValueRune { return ValueRune.new(RunFilter, this, fn) } final() { return ValueRune.new(RunFinal, this) } reduce(init: T2, fn: (last: T2, value: T) => T2 | Promise): ValueRune { return ValueRune.new(RunReduce, this, init, fn) } collect() { return this.reduce([], (arr, val) => { arr.push(val) return arr }).final() } dbg(...prefix: RunicArgs) { return Rune .tuple([this, Rune.tuple(prefix).lazy()]) .map(([value, prefix]) => { console.log(...prefix, value) return value }) } chain(fn: (result: ValueRune) => Rune): ValueRune { return ValueRune.new(RunChain, this, fn(this as never)) } match( this: ValueRune, fn: (match: Match) => ExhaustiveMatch, ): ValueRune { const match = new Match(this as any as ValueRune) if (fn(match) !== match as any) { throw new Error("The callback supplied to match must return the match passed in") } return ValueRune.new(RunMatch, this as any as ValueRune, match.conditions) } } type ExhaustiveMatch = Match class Match { conditions: [(x: M) => boolean, ValueRune][] = [] constructor(readonly value: ValueRune) {} when( guard: Guard, fn: (value: ValueRune) => ValueRune, ): Match, T | T2, U | U2> { this.conditions.push([guard, fn(this.value as any) as any]) return this as any } else( fn: (value: ValueRune) => ValueRune, ): ExhaustiveMatch { this.conditions.push([() => true, fn(this.value as any) as any]) return this as any } } class RunMatch extends Run { value conditions constructor( runner: Runner, child: Rune, conditions: [(x: M) => boolean, ValueRune][], ) { super(runner) this.value = this.use(child) this.conditions = conditions.map(([cond, val]) => [cond, this.use(val)] as const) } async _evaluate(time: number, receipt: Receipt) { const value = await this.value.evaluate(time, receipt) as M for (const [cond, val] of this.conditions) { if (cond(value)) { return await val.evaluate(time, receipt) } } throw new Error("Match was not exhaustive") } } class RunMap extends Run { child constructor( runner: Runner, child: Rune, readonly fn: (value: T1) => T2 | Promise, ) { super(runner) this.child = this.use(child) } lastValue: T2 = null! async _evaluate(time: number, receipt: Receipt) { // TODO: improve const source = await this.child.evaluate(time, receipt) if (!receipt.ready || !receipt.novel) return this.lastValue return this.lastValue = await this.fn(source) } } Rune.ValueRune = ValueRune class RunHandle extends Run | T3, U | U2> { child alt constructor( runner: Runner, child: Rune, readonly guard: Guard, alt: Rune, ) { super(runner) this.child = this.use(child) this.alt = this.use(alt) } async _evaluate(time: number, receipt: Receipt) { const value = await this.child.evaluate(time, receipt) as T if (this.guard(value)) { return await this.alt.evaluate(time, receipt) } else return value as Exclude } } class RunUnhandle extends Run { child constructor( runner: Runner, child: Rune, readonly guard: Guard, ) { super(runner) this.child = this.use(child) } async _evaluate(time: number, receipt: Receipt) { const value = (await this.child.evaluate(time, receipt)) as T if (this.guard(value)) throw new Unhandled(value, this.trace) return value } } class RunThrows extends Run { child constructor( runner: Runner, child: Rune, readonly guards: Array>, ) { super(runner) this.child = this.use(child) } async _evaluate(time: number, receipt: Receipt) { try { return await this.child.evaluate(time, receipt) } catch (e) { for (const guard of this.guards) { if (guard(e)) throw new Unhandled(e, this.trace) } throw e } } } class RunGetUnhandled extends Run | null, never> { child constructor(runner: Runner, child: Rune) { super(runner) this.child = this.use(child) } async _evaluate(time: number, receipt: Receipt) { try { await this.child.evaluate(time, receipt) return null } catch (e) { if (e instanceof Unhandled) { return e } throw e } } } class RunRehandle extends Run | U3> { child alt constructor( runner: Runner, child: Rune, readonly fn: (value: U1) => value is U2, alt: Rune, ) { super(runner) this.child = this.use(child) this.alt = this.use(alt) } async _evaluate(time: number, receipt: Receipt) { try { return await this.child.evaluate(time, receipt) } catch (e) { if (e instanceof Unhandled && this.fn(e.value)) { return await this.alt.evaluate(time, receipt) } throw e } } } class RunLazy extends Run { child constructor(runner: Runner, child: Rune) { super(runner) this.child = this.use(child) } async _evaluate(time: number, _receipt: Receipt): Promise { return await this.child.evaluate(time, new Receipt()) } } class RunFilter extends Run { child constructor(runner: Runner, child: Rune, readonly fn: (value: T) => boolean) { super(runner) this.child = this.use(child) } first = true lastValue: T = null! async _evaluate(time: number, receipt: Receipt): Promise { const _receipt = new Receipt() try { const value = await this.child.evaluate(time, _receipt) if (!_receipt.ready || !_receipt.novel || this.fn(value)) { receipt.setNovel(_receipt.novel) this.lastValue = value this.first = false return value } else { receipt.setReady(!this.first) receipt.novel = true return this.lastValue } } finally { receipt.setNextTimeFrom(_receipt) } } } class RunFinal extends Run { child constructor(runner: Runner, child: Rune) { super(runner) this.child = this.use(child) } async _evaluate(time: number, receipt: Receipt): Promise { const value = await this.child.evaluate(time, receipt) await receipt.finalized() receipt.setReady(receipt.nextTime === Infinity) return value } } class RunReduce extends Run { child constructor( runner: Runner, child: Rune, public lastValue: T2, readonly fn: (last: T2, value: T1) => T2 | Promise, ) { super(runner) this.child = this.use(child) } async _evaluate(time: number, receipt: Receipt) { // TODO: improve const source = await this.child.evaluate(time, receipt) if (!receipt.ready || !receipt.novel) return this.lastValue return this.lastValue = await this.fn(this.lastValue, source) } } class RunChain extends Run { first second constructor( runner: Runner, first: Rune, second: Rune, ) { super(runner) this.first = this.use(first) this.second = this.use(second) } lastValue: T2 = null! async _evaluate(time: number, receipt: Receipt) { // TODO: improve await this.first.evaluate(time, receipt) if (!receipt.ready || !receipt.novel) return this.lastValue return this.lastValue = await this.second.evaluate(time, receipt) } }