import "../_dnt.polyfills.js"; import { deferred } from "../deps/std/async.js" import { getOrInit } from "../util/state.js" import { EventSource, Receipt, Timeline } from "./Timeline.js" import { Trace } from "./Trace.js" // Hack to work around circularity issues import * as _ from "./_empty.js" export abstract class Runner { abstract order: number abstract timeline: Timeline _currentTrace?: Trace protected abstract _prime(rune: Rune): Run memo = new Map<(runner: Runner) => Run, Run>() prime(rune: Rune): Run { const run = getOrInit(this.memo, rune._prime, () => { const old = this._currentTrace this._currentTrace = rune._trace const run = this._prime(rune) this._currentTrace = old run._sources.push(rune._prime) return run }) return run } getPrimed(rune: Rune): Run | undefined { return this.memo.get(rune._prime) } onCleanup(run: Run) { for (const source of run._sources) { this.memo.delete(source) } } } class RootRunner extends Runner { order = 0 timeline = new Timeline() protected _prime(rune: _.Rune): _.Run { return rune._prime(this) } } const globalRunner = new RootRunner() declare const _T: unique symbol declare const _U: unique symbol export declare namespace Rune { export type T = R extends { [_T]: infer T } ? T : R export type U = R extends { [_U]: infer U } ? U : never export import ValueRune = _.ValueRune export import ArrayRune = _.ArrayRune export import FnRune = _.FnRune } export interface Rune { [_T]: T [_U]: U } export class Rune { declare private _ _trace: Trace constructor(readonly _prime: (runner: Runner) => Run) { this._trace = new Trace(`execution of the ${new.target.name} instantiated`) } static new( ctor: new(runner: Runner, ...args: A) => Run, ...args: A ) { return new Rune((runner) => new ctor(runner, ...args)) } async run(runner: Runner = globalRunner): Promise { for await (const value of this.iter(runner)) { return value } throw new Error("Rune did not yield any values") } async *iter(runner: Runner = globalRunner) { let time = runner.timeline.current const primed = runner.prime(this) primed.reference() try { while (time !== Infinity) { const receipt = new Receipt() const value = await primed.evaluate(time, receipt) if (receipt.ready && receipt.novel) { yield value } await receipt.finalized() time = receipt.nextTime } } finally { primed.dereference() } } static constant(value: T) { return Rune.ValueRune.new(RunConstant, value) } static resolve(value: V): Rune.ValueRune, Rune.U> { return value instanceof Rune ? value.into(Rune.ValueRune) : Rune.constant(value) } static str(strings: TemplateStringsArray, ..._values: RunicArgs) { const values = RunicArgs.resolve(_values) return Rune .tuple(values) .map((values) => strings .map((templateString, i) => { return i < values.length ? `${templateString}${values[i]}` : templateString }) .join("") ) } static tuple( runes: [...R], ): Rune.ValueRune<{ [K in keyof R]: Rune.T }, Rune.U> static tuple( runes: [...R], ): Rune.ValueRune[], Rune.U> { return Rune.ValueRune.new(RunLs, runes.map(Rune.resolve)) } static array(runes: RunicArgs) { return Rune.ValueRune.new(RunLs, RunicArgs.resolve(runes)).into(Rune.ArrayRune) } static fn(...[fn]: RunicArgs T]>) { return Rune.resolve(fn).into(Rune.FnRune) } static object( runes: R, ): Rune.ValueRune<{ [K in keyof R]: Rune.T }, Rune.U> { const keys = Object.keys(runes) const values = Object.values(runes) return Rune.tuple(values).map((values) => { return Object.fromEntries(values.map((v, i) => [keys[i], v])) as any }) } static captureUnhandled( sources: [...R], fn: ( ...runes: { [K in keyof R]: Rune.ValueRune, never> } ) => Rune, ): Rune.ValueRune | U2> static captureUnhandled( sources: any[], fn: (...runes: Rune.ValueRune, never>[]) => Rune, ): Rune.ValueRune | U2> { const symbol = Symbol() return Rune.ValueRune.new( RunCaptureUnhandled>, fn( ...sources.map((source) => Rune.ValueRune.new(RunBubbleUnhandled, Rune.resolve(source), symbol) ), ), symbol, ) } static asyncIter(fn: () => AsyncIterable): Rune.ValueRune { return Rune.ValueRune.new(RunAsyncIter, fn) } static _placeholder() { return Rune.new(RunPlaceholder) } into>( ctor: new(_prime: (runner: Runner) => Run>, ...args: A) => C, ...args: A ): C { const rune = new ctor(this._prime, ...args) rune._trace = this._trace return rune } as(this: R, _ctor: new(_prime: (runner: Runner) => Run, ...args: any) => R): R { return this } unsafeAs(): Rune { return this as never } pipe>(fn: (rune: this) => R): R { return fn(this) } static pin(rune: Rune, pinned: Rune): Rune { return new Rune((runner) => { const run = runner.prime(rune) run.use(pinned) return run }) } } export abstract class Run { declare "": [T, U] trace: Trace order: number timeline constructor(readonly runner: Runner) { this.trace = runner._currentTrace ?? new Trace(`execution of the ${new.target.name} instantiated`) this.order = runner.order this.timeline = runner.timeline } dependencies: Run[] = [] use(rune: Rune): Run { const run = this.runner.prime(rune) this.useRun(run) return run } useRun(run: Run) { run.reference() this.dependencies.push(run) } referenceCount = 0 alive = true reference() { if (!this.alive) throw new Error("cannot reference a dead rune") this.referenceCount++ } _sources: Array<(runner: Runner) => Run> = [] dereference(cleanupBatches?: Run[][]) { if (!--this.referenceCount) { this.alive = false this.cleanup() this.runner.onCleanup(this) if (cleanupBatches) { cleanupBatches.push(this.dependencies) } else { const cleanupBatches = [this.dependencies] while (cleanupBatches.length) { const batch = cleanupBatches.pop()! for (const run of batch) { run.dereference(cleanupBatches) } } } } } cleanup() {} _currentTime = -1 _currentPromise: Promise = null! _currentReceipt = new Receipt() async evaluate(time: number, receipt: Receipt) { if (this._currentTime > time) throw new Error("cannot regress") if (this._currentTime < time) { this._currentReceipt = new Receipt() if (this._currentTime === -1) { this._currentReceipt.novel = true } this._currentTime = time this._currentPromise = this.trace.runAsync(() => this._evaluate(time, this._currentReceipt)) } const _receipt = this._currentReceipt try { return await this._currentPromise } finally { receipt.setFrom(_receipt) } } abstract _evaluate(time: number, receipt: Receipt): Promise } class RunConstant extends Run { constructor(runner: Runner, readonly value: T) { super(runner) } _evaluate() { return Promise.resolve(this.value) } } class RunLs extends Run { children constructor(runner: Runner, children: Rune[]) { super(runner) this.children = children.map((child) => this.use(child)) } _evaluate(time: number, receipt: Receipt) { return Promise.all(this.children.map((child) => child.evaluate(time, receipt))) } } export abstract class RunStream extends Run { initPromise = deferred() valueQueue: [number, T][] = [] eventSource = new EventSource(this.timeline) first = true done = false curIter = new AbortController() lastValue: T = null! constructor(runner: Runner) { super(runner) } async _evaluate(time: number, receipt: Receipt): Promise { if (this.first) { await this.initPromise } while (time >= this.valueQueue[0]?.[0]!) { // NaN comparisons are false receipt.novel = true this.lastValue = this.valueQueue.shift()![1] } const value = this.lastValue if (this.valueQueue.length) { receipt.setNextTime(this.valueQueue[0]![0]) } else if (!this.done) { receipt.bind(this.eventSource) } return value } push(value: T) { if (this.first) { this.first = false this.valueQueue.push([0, value]) this.initPromise.resolve() } else { this.valueQueue.push([this.eventSource.push(), value]) } } finish() { this.done = true this.eventSource.finish() } } class RunAsyncIter extends RunStream { constructor(runner: Runner, fn: () => AsyncIterable) { super(runner) ;(async () => { for await (const value of fn()) { if (!this.alive) break this.push(value) } this.finish() })() } } class RunPlaceholder extends Run { _evaluate(): Promise { return Promise.reject(new Error("PlaceholderRune should not be evaluated")) } } class RunBubbleUnhandled extends Run { child constructor(runner: Runner, child: Rune, readonly symbol: symbol) { super(runner) this.child = this.use(child) } async _evaluate(time: number, receipt: Receipt) { try { return await this.child.evaluate(time, receipt) } catch (e) { if (e instanceof Unhandled) { throw new BubbleUnhandled(this.symbol, e.value, e.trace) } throw e } } } class RunCaptureUnhandled extends Run { child constructor(runner: Runner, child: Rune, readonly symbol: symbol) { super(runner) this.child = this.use(child) } async _evaluate(time: number, receipt: Receipt) { try { return await this.child.evaluate(time, receipt) } catch (e) { if (e instanceof BubbleUnhandled && e.symbol === this.symbol) { throw new Unhandled(e.value, e.trace) } throw e } } } class BubbleUnhandled { constructor(readonly symbol: symbol, readonly value: U, readonly trace: Trace) {} } export class Unhandled { declare private _ constructor(readonly value: U, readonly trace: Trace) {} } export type RunicArgs = | (never extends X ? never : X extends A ? X : never) | { [K in keyof A]: A[K] | Rune> } export namespace RunicArgs { export type U = X extends unknown[] ? Rune.U : Rune.U export function resolve( args: RunicArgs, ): { [K in keyof A]: Rune.ValueRune> } export function resolve(args: any): any { return args instanceof Array ? args.map(Rune.resolve) : Object.fromEntries(Object.entries(args).map(([k, v]) => [k, Rune.resolve(v)])) } }