diff --git a/src/vm/vm.ts b/src/vm/vm.ts index 532acec..2521a38 100644 --- a/src/vm/vm.ts +++ b/src/vm/vm.ts @@ -53,6 +53,12 @@ export class Scope { makeChild(): Scope { return new Scope(this) } + + clone(): Scope { + const s = new Scope + s.entries = {...this.entries} + return s + } } /** @@ -81,11 +87,31 @@ export type TermOperator = { } export class ExecutionContext { + private history: [StrRVal, Scope][] = [] + constructor( private subject: StrRVal, private scope: Scope, ) {} + public pushHistory() { + this.history.push([this.subject, this.scope.clone()]) + } + + public restoreHistory() { + const [subject, scope] = this.popHistory() + this.subject = subject + this.scope = scope + } + + public popHistory(): [StrRVal, Scope] { + const state = this.history.pop() + if ( !state ) { + throw new Error('No history to undo') + } + return state + } + async replaceSubject(operator: (sub: StrRVal) => Awaitable) { this.subject = await operator(this.subject) } @@ -201,7 +227,14 @@ export class StrVM { ) {} public async runInPlace(operator: (ctx: ExecutionContext) => Awaitable): Promise { - return operator(this.context) + this.context.pushHistory() + try { + return await operator(this.context) + } catch (e: unknown) { + // If we got an error, return to the previous state: + this.context.restoreHistory() + throw e + } } public async tapInPlace(operator: (ctx: ExecutionContext) => Awaitable): Promise {