import { mustFixError } from "#Source/exception/error/must-fix.ts" import type { AnyEither, GetLeft, GetRight } from "./either.ts" import { Either } from "./either.ts" export type EitherSyncGenerator = Generator< Yield, Return, unknown > export type EitherAsyncGenerator< Yield extends AnyEither, Return extends AnyEither, > = AsyncGenerator export type GetEitherGeneratorResult = Either< GetLeft | GetLeft, GetRight > export const runSyncGenerator = ( generator: EitherSyncGenerator, ): GetEitherGeneratorResult => { let iteratorResult: IteratorResult try { iteratorResult = generator.next() } catch (exception) { throw mustFixError("Generator threw an exception", { cause: exception }) } if (Either.isEither(iteratorResult.value) === false) { throw mustFixError("Generator yielded a non-Either value", { cause: iteratorResult.value }) } if (iteratorResult.done === false) { try { generator.return?.(undefined as never) } catch (exception) { throw mustFixError("Generator threw an exception during cleanup", { cause: exception }) } } return iteratorResult.value } export const runAsyncGenerator = async ( generator: EitherAsyncGenerator, ): Promise> => { let iteratorResult: IteratorResult try { iteratorResult = await generator.next() } catch (exception) { throw mustFixError("Generator threw an exception", { cause: exception }) } if (Either.isEither(iteratorResult.value) === false) { throw mustFixError("Generator yielded a non-Either value", { cause: iteratorResult.value }) } if (iteratorResult.done === false) { try { await generator.return?.(undefined as never) } catch (exception) { throw mustFixError("Generator threw an exception during cleanup", { cause: exception }) } } return iteratorResult.value } export interface GenSync { ( generatorFunction: () => EitherSyncGenerator, ): GetEitherGeneratorResult ( generatorFunction: (this: This) => EitherSyncGenerator, thisArg: This, ): GetEitherGeneratorResult } export const genSync: GenSync = ( generatorFunction: | (() => EitherSyncGenerator) | ((this: This) => EitherSyncGenerator), thisArg?: This | undefined, ): GetEitherGeneratorResult => { let generator: EitherSyncGenerator if (thisArg !== undefined) { generator = (generatorFunction as (this: This) => EitherSyncGenerator).call( thisArg, ) } else { generator = (generatorFunction as () => EitherSyncGenerator)() } const result = runSyncGenerator(generator) return result } export interface GenAsync { ( generatorFunction: () => EitherAsyncGenerator, ): Promise> ( generatorFunction: (this: This) => EitherAsyncGenerator, thisArg: This, ): Promise> } export const genAsync: GenAsync = async ( generatorFunction: | (() => EitherAsyncGenerator) | ((this: This) => EitherAsyncGenerator), thisArg?: This | undefined, ): Promise> => { let generator: EitherAsyncGenerator if (thisArg !== undefined) { generator = (generatorFunction as (this: This) => EitherAsyncGenerator).call( thisArg, ) } else { generator = (generatorFunction as () => EitherAsyncGenerator)() } const result = await runAsyncGenerator(generator) return result } export interface Gen extends GenSync, GenAsync {} export const gen = (( generatorFunction: | (() => EitherSyncGenerator) | ((thisArg: This) => EitherSyncGenerator) | (() => EitherAsyncGenerator) | ((thisArg: This) => EitherAsyncGenerator), thisArg?: This | undefined, ): GetEitherGeneratorResult | Promise> => { let generator: EitherSyncGenerator | EitherAsyncGenerator if (thisArg !== undefined) { generator = ( generatorFunction as ( this: This, ) => EitherSyncGenerator | EitherAsyncGenerator ).call(thisArg) } else { generator = ( generatorFunction as () => | EitherSyncGenerator | EitherAsyncGenerator )() } if (Symbol.asyncIterator in generator) { return runAsyncGenerator(generator) } if (Symbol.iterator in generator) { return runSyncGenerator(generator) } throw new TypeError("Invalid generator function") }) as Gen export const genAwait = async function* ( promise: Promise>, ): AsyncGenerator, Right, unknown> { const either = await promise const result = yield* either return result }