import type { EffectBackend } from "../../../../src/core/effects/backend"; import type { State } from "../../../../src/core/eval/machine"; import type { OpCall, Resumption } from "../../../../src/core/effects/opcall"; import type { Val } from "../../../../src/core/eval/values"; import type { BudgetTracker } from "../../../../src/package-bridge/governance"; type AmbAlternative = { thunk: Val; resumption: Resumption; }; export type AmbStrategy = "DFS" | "FAIR"; export class AmbBackend implements EffectBackend { readonly ops = ["amb.choose", "amb.fail"] as const; private alternatives: AmbAlternative[] = []; private strategy: AmbStrategy = "DFS"; constructor(private budget?: BudgetTracker) {} setStrategy(strategy: AmbStrategy): void { this.strategy = strategy; } reset(): void { this.alternatives = []; } hasAlternatives(): boolean { return this.alternatives.length > 0; } setBudget(budget: BudgetTracker): void { this.budget = budget; } async handle(state: State, opcall: OpCall): Promise { if (opcall.op === "amb.choose") { return this.handleChoose(state, opcall); } if (opcall.op === "amb.fail") { return this.handleFail(state, opcall); } throw new Error(`AmbBackend: unhandled op ${opcall.op}`); } private handleChoose(state: State, opcall: OpCall): State { const thunks = opcall.args[0]; if (!thunks || thunks.tag !== "Vector") { throw new Error("amb.choose: expected vector of thunks"); } function consToArray(v: Val): Val[] { const result: Val[] = []; let cur = v; while (cur.tag === "Vector" && cur.items.length === 2) { result.push(cur.items[0]); cur = cur.items[1]; } if (cur.tag === "Vector" && cur.items.length > 2) { return cur.items; } return result; } const items = consToArray(thunks); if (items.length === 0) { return this.handleFail(state, { op: "amb.fail", args: [{ tag: "Str", s: "amb: no alternatives" } as Val], resumption: opcall.resumption, ctxDigest: opcall.ctxDigest, }); } for (let i = 1; i < items.length; i++) { this.alternatives.push({ thunk: items[i], resumption: opcall.resumption, }); } this.budget?.consumeAmbAttempt?.(); const firstThunk = items[0]; if (firstThunk.tag !== "Closure") { throw new Error("amb.choose: thunk must be a closure"); } const baseState: State | "Uncaught" | "OutOfBudget" = opcall.resumption.invoke({ tag: "Unit" } as Val); if (baseState === ("Uncaught" as any) || baseState === ("OutOfBudget" as any)) { return baseState as any; } return { ...baseState, control: { tag: "Expr", e: firstThunk.body }, env: firstThunk.env, } as State; } private handleFail(_state: State, _opcall: OpCall): State { if (this.alternatives.length === 0) { throw new Error("amb: all alternatives exhausted"); } this.budget?.consumeAmbAttempt?.(); const alt = this.alternatives.shift()!; const thunk = alt.thunk; const resumption = alt.resumption; const baseState: State | "Uncaught" | "OutOfBudget" = resumption.invoke({ tag: "Unit" } as Val); if (baseState === ("Uncaught" as any) || baseState === ("OutOfBudget" as any)) { return baseState as any; } if (thunk.tag === "Closure") { return { ...baseState, control: { tag: "Expr", e: thunk.body }, env: thunk.env, } as State; } if (thunk.tag === "Native") { throw new Error("amb: Native thunks not yet supported"); } throw new Error(`amb: invalid thunk type ${thunk.tag}`); } }