import type { Either, Right } from "../Either"; import { left, right } from "../Either/constructors"; import { map_ as mapEither_ } from "../Either/functor"; import type { Stack } from "../support/Stack"; import { stack } from "../support/Stack"; import { fail, succeed } from "./constructors"; import type { XPure } from "./model"; import { _XPI, XPureInstructionTag } from "./model"; import { giveAll_ } from "./reader"; export class FoldFrame { readonly _xptag = "FoldFrame"; constructor( readonly failure: (e: any) => XPure, readonly apply: (e: any) => XPure ) {} } export class ApplyFrame { readonly _xptag = "ApplyFrame"; constructor(readonly apply: (e: any) => XPure) {} } export type Frame = FoldFrame | ApplyFrame; /** * Runs this computation with the specified initial state, returning either a * failure or the updated state and the result */ export const runStateEither_ = (fa: XPure, s: S1): Either => { let frames: Stack | undefined = undefined; let state = s as any; let result = null; let environment = null; let failed = false; let current = fa as XPure | undefined; function popContinuation() { const nextInstr = frames; if (nextInstr) { frames = frames?.previous; } return nextInstr?.value; } function pushContinuation(cont: Frame) { frames = stack(cont, frames); } function findNextErrorHandler() { let unwinding = true; while (unwinding) { const next = popContinuation(); if (next == null) { unwinding = false; } else { if (next._xptag === "FoldFrame") { unwinding = false; pushContinuation(new ApplyFrame(next.failure)); } } } } while (current != null) { const I = current[_XPI]; switch (I._xptag) { case XPureInstructionTag.Chain: { const nested = I.ma[_XPI]; const continuation = I.f; switch (nested._xptag) { case XPureInstructionTag.Succeed: { current = continuation(nested.value); break; } case XPureInstructionTag.Total: { current = continuation(nested.thunk()); break; } case XPureInstructionTag.Partial: { try { current = succeed(nested.thunk()); } catch (e) { current = fail(nested.onThrow(e)); } break; } case XPureInstructionTag.Modify: { const updated = nested.run(state); state = updated[0]; result = updated[1]; current = continuation(result); break; } default: { current = nested; pushContinuation(new ApplyFrame(continuation)); } } break; } case XPureInstructionTag.Total: { result = I.thunk(); const nextInstruction = popContinuation(); if (nextInstruction) { current = nextInstruction.apply(result); } else { current = undefined; } break; } case XPureInstructionTag.Partial: { try { current = succeed(I.thunk()); } catch (e) { current = fail(I.onThrow(e)); } break; } case XPureInstructionTag.Suspend: { current = I.factory(); break; } case XPureInstructionTag.Succeed: { result = I.value; const nextInstr = popContinuation(); if (nextInstr) { current = nextInstr.apply(result); } else { current = undefined; } break; } case XPureInstructionTag.Fail: { findNextErrorHandler(); const nextInst = popContinuation(); if (nextInst) { current = nextInst.apply(I.e); } else { failed = true; result = I.e; current = undefined; } break; } case XPureInstructionTag.Fold: { current = I.fa; pushContinuation(new FoldFrame(I.onFailure, I.onSuccess)); break; } case XPureInstructionTag.Asks: { current = I.f(environment); break; } case XPureInstructionTag.Give: { environment = I.r; current = I.fa; break; } case XPureInstructionTag.Modify: { const updated = I.run(state); state = updated[0]; result = updated[1]; const nextInst = popContinuation(); if (nextInst) { current = nextInst.apply(result); } else { current = undefined; } break; } } } if (failed) { return left(result); } return right([state, result]); }; /** * Runs this computation with the specified initial state, returning either a * failure or the updated state and the result */ export const runStateEither = (s: S1) => ( fx: XPure ): Either => runStateEither_(fx, s); /** * Runs this computation with the specified initial state, returning both * the updated state and the result. */ export const run_ = (self: XPure, s: S1) => (runStateEither_(self, s) as Right).right; /** * Runs this computation with the specified initial state, returning both * updated state and the result */ export const run = (s: S1) => (self: XPure): readonly [S2, A] => run_(self, s); /** * Runs this computation, returning the result. */ export const runIO = (self: XPure) => run_(self, {})[1]; /** * Runs this computation with the specified initial state, returning the * updated state and discarding the result. */ export const runState_ = (self: XPure, s: S1) => (runStateEither_(self, s) as Right).right[0]; /** * Runs this computation with the specified initial state, returning the * updated state and discarding the result. */ export const runState = (s: S1) => (self: XPure) => runState_(self, s); /** * Runs this computation with the specified initial state, returning the * updated state and the result. */ export const runStateResult_ = (self: XPure, s: S1) => (runStateEither_(self, s) as Right).right; /** * Runs this computation with the specified initial state, returning the * updated state and the result. */ export const runStateResult = (s: S1) => (self: XPure) => runStateResult_(self, s); /** * Runs this computation with the specified initial state, returning the * result and discarding the updated state. */ export const runResult_ = (self: XPure, s: S1) => (runStateEither_(self, s) as Right).right[1]; /** * Runs this computation with the specified initial state, returning the * result and discarding the updated state. */ export const runResult = (s: S1) => (self: XPure) => runResult_(self, s); /** * Runs this computation returning either the result or error */ export const runEither = (self: XPure): Either => mapEither_(runStateEither_(self, {} as never), ([_, x]) => x); export const runEitherEnv_ = (self: XPure, env: R): Either => runEither(giveAll_(self, env)); export const runEitherEnv = (env: R) => (self: XPure): Either => runEitherEnv_(self, env);