import { Scope, Environment, Opcode } from '../environment'; import { Reference, PathReference, ReferenceIterator } from '@glimmer/reference'; import { Option, unwrap } from '@glimmer/util'; import { InlineBlock } from '../scanner'; import { EvaluatedArgs } from '../compiled/expressions/args'; import { Component, ComponentManager } from '../component/interfaces'; export class CapturedFrame { constructor( public operand: Option>, public args: Option, public condition: Option> ) {} } class Frame { ip: number; operand: Option> = null; immediate: any = null; args: Option = null; callerScope: Option = null; blocks: Option = null; condition: Option> = null; iterator: Option = null; key: Option = null; constructor( public start: number, public end: number, public component = null, public manager: Option> = null, public shadow: Option = null ) { this.ip = start; } capture(): CapturedFrame { return new CapturedFrame(this.operand, this.args, this.condition); } restore(frame: CapturedFrame) { this.operand = frame.operand; this.args = frame.args; this.condition = frame.condition; } } export interface Blocks { default: Option; inverse: Option; } export class FrameStack { private frames: Frame[] = []; private frame: number = -1; private get currentFrame(): Frame { return this.frames[this.frame]; } push(start: number, end: number, component = null, manager: Option> = null, shadow: Option = null) { let pos = ++this.frame; if (pos < this.frames.length) { let frame = this.frames[pos]; frame.start = frame.ip = start; frame.end = end; frame.component = component; frame.manager = manager; frame.shadow = shadow; frame.operand = null; frame.immediate = null; frame.args = null; frame.callerScope = null; frame.blocks = null; frame.condition = null; frame.iterator = null; frame.key = null; } else { this.frames[pos] = new Frame(start, end, component, manager, shadow); } } pop() { this.frame--; } capture(): CapturedFrame { return this.currentFrame.capture(); } restore(frame: CapturedFrame) { this.currentFrame.restore(frame); } getStart(): number { return this.currentFrame.start; } getEnd(): number { return this.currentFrame.end; } getCurrent(): number { return this.currentFrame.ip; } setCurrent(ip: number): number { return this.currentFrame.ip = ip; } getOperand(): PathReference { return unwrap(this.currentFrame.operand); } setOperand(operand: PathReference): PathReference { return this.currentFrame.operand = operand; } getImmediate(): T { return this.currentFrame.immediate; } setImmediate(value: T): T { return this.currentFrame.immediate = value; } // FIXME: These options are required in practice by the existing code, but // figure out why. getArgs(): Option { return this.currentFrame.args; } setArgs(args: EvaluatedArgs): EvaluatedArgs { return this.currentFrame.args = args; } getCondition(): Reference { return unwrap(this.currentFrame.condition); } setCondition(condition: Reference): Reference { return this.currentFrame.condition = condition; } getIterator(): ReferenceIterator { return unwrap(this.currentFrame.iterator); } setIterator(iterator: ReferenceIterator): ReferenceIterator { return this.currentFrame.iterator = iterator; } getKey(): Option { return this.currentFrame.key; } setKey(key: string): string { return this.currentFrame.key = key; } getBlocks(): Blocks { return unwrap(this.currentFrame.blocks); } setBlocks(blocks: Blocks): Blocks { return this.currentFrame.blocks = blocks; } getCallerScope(): Scope { return unwrap(this.currentFrame.callerScope); } setCallerScope(callerScope: Scope): Scope { return this.currentFrame.callerScope = callerScope; } getComponent(): Component { return unwrap(this.currentFrame.component); } getManager(): ComponentManager { return unwrap(this.currentFrame.manager); } getShadow(): Option { return this.currentFrame.shadow; } goto(ip: number) { this.setCurrent(ip); } nextStatement(env: Environment): Option { while (this.frame !== -1) { let frame = this.frames[this.frame]; let ip = frame.ip; let end = frame.end; if (ip < end) { let program = env.program; frame.ip += 4; return program.opcode(ip); } else { this.pop(); } } return null; } }