UNPKG

ember-material-icons

Version:

Google Material icons for your ember-cli app

418 lines (325 loc) 11.4 kB
import { Scope, DynamicScope, Environment, Opcode } from '../environment'; import { ElementStack } from '../builder'; import { Option, Destroyable, Stack, LinkedList, ListSlice, Opaque, assert, expect } from '@glimmer/util'; import { PathReference, combineSlice } from '@glimmer/reference'; import { CompiledBlock, CompiledProgram } from '../compiled/blocks'; import { InlineBlock, PartialBlock } from '../scanner'; import { CompiledExpression } from '../compiled/expressions'; import { CompiledArgs, EvaluatedArgs } from '../compiled/expressions/args'; import { LabelOpcode, JumpIfNotModifiedOpcode, DidModifyOpcode } from '../compiled/opcodes/vm'; import { Component, ComponentManager } from '../component/interfaces'; import { VMState, ListBlockOpcode, TryOpcode, BlockOpcode } from './update'; import RenderResult from './render-result'; import { CapturedFrame, FrameStack } from './frame'; import { APPEND_OPCODES, UpdatingOpcode, Constants, ConstantString, } from '../opcodes'; export interface PublicVM { env: Environment; getArgs(): Option<EvaluatedArgs>; dynamicScope(): DynamicScope; getSelf(): PathReference<Opaque>; newDestroyable(d: Destroyable): void; } export interface IteratorResult<T> { value: T | null; done: boolean; } export default class VM implements PublicVM { private dynamicScopeStack = new Stack<DynamicScope>(); private scopeStack = new Stack<Scope>(); public updatingOpcodeStack = new Stack<LinkedList<UpdatingOpcode>>(); public cacheGroups = new Stack<Option<UpdatingOpcode>>(); public listBlockStack = new Stack<ListBlockOpcode>(); public frame = new FrameStack(); public constants: Constants; static initial( env: Environment, self: PathReference<Opaque>, dynamicScope: DynamicScope, elementStack: ElementStack, compiledProgram: CompiledProgram ) { let { symbols: size, start, end } = compiledProgram; let scope = Scope.root(self, size); let vm = new VM(env, scope, dynamicScope, elementStack); vm.prepare(start, end); return vm; } constructor( public env: Environment, scope: Scope, dynamicScope: DynamicScope, private elementStack: ElementStack, ) { this.env = env; this.constants = env.constants; this.elementStack = elementStack; this.scopeStack.push(scope); this.dynamicScopeStack.push(dynamicScope); } capture(): VMState { return { env: this.env, scope: this.scope(), dynamicScope: this.dynamicScope(), frame: this.frame.capture() }; } goto(ip: number) { this.frame.goto(ip); } beginCacheGroup() { this.cacheGroups.push(this.updating().tail()); } commitCacheGroup() { // JumpIfNotModified(END) // (head) // (....) // (tail) // DidModify // END: Noop let END = new LabelOpcode("END"); let opcodes = this.updating(); let marker = this.cacheGroups.pop(); let head = marker ? opcodes.nextNode(marker) : opcodes.head(); let tail = opcodes.tail(); let tag = combineSlice(new ListSlice(head, tail)); let guard = new JumpIfNotModifiedOpcode(tag, END); opcodes.insertBefore(guard, head); opcodes.append(new DidModifyOpcode(guard)); opcodes.append(END); } enter(start: number, end: number) { let updating = new LinkedList<UpdatingOpcode>(); let tracker = this.stack().pushUpdatableBlock(); let state = this.capture(); let tryOpcode = new TryOpcode(start, end, state, tracker, updating); this.didEnter(tryOpcode, updating); } enterWithKey(key: string, start: number, end: number) { let updating = new LinkedList<UpdatingOpcode>(); let tracker = this.stack().pushUpdatableBlock(); let state = this.capture(); let tryOpcode = new TryOpcode(start, end, state, tracker, updating); this.listBlock().map[key] = tryOpcode; this.didEnter(tryOpcode, updating); } enterList(start: number, end: number) { let updating = new LinkedList<BlockOpcode>(); let tracker = this.stack().pushBlockList(updating); let state = this.capture(); let artifacts = this.frame.getIterator().artifacts; let opcode = new ListBlockOpcode(start, end, state, tracker, updating, artifacts); this.listBlockStack.push(opcode); this.didEnter(opcode, updating); } private didEnter(opcode: BlockOpcode, updating: LinkedList<UpdatingOpcode>) { this.updateWith(opcode); this.updatingOpcodeStack.push(updating); } exit() { this.stack().popBlock(); this.updatingOpcodeStack.pop(); let parent = this.updating().tail() as BlockOpcode; parent.didInitializeChildren(); } exitList() { this.exit(); this.listBlockStack.pop(); } updateWith(opcode: UpdatingOpcode) { this.updating().append(opcode); } listBlock(): ListBlockOpcode { return expect(this.listBlockStack.current, 'expected a list block'); } updating(): LinkedList<UpdatingOpcode> { return expect(this.updatingOpcodeStack.current, 'expected updating opcode on the updating opcode stack'); } stack(): ElementStack { return this.elementStack; } scope(): Scope { return expect(this.scopeStack.current, 'expected scope on the scope stack'); } dynamicScope(): DynamicScope { return expect(this.dynamicScopeStack.current, 'expected dynamic scope on the dynamic scope stack'); } pushFrame( block: CompiledBlock, args?: Option<EvaluatedArgs>, callerScope?: Scope ) { this.frame.push(block.start, block.end); if (args) this.frame.setArgs(args); if (args && args.blocks) this.frame.setBlocks(args.blocks); if (callerScope) this.frame.setCallerScope(callerScope); } pushComponentFrame( layout: CompiledBlock, args: EvaluatedArgs, callerScope: Scope, component: Component, manager: ComponentManager<Component>, shadow: Option<InlineBlock> ) { this.frame.push(layout.start, layout.end, component, manager, shadow); if (args) this.frame.setArgs(args); if (args && args.blocks) this.frame.setBlocks(args.blocks); if (callerScope) this.frame.setCallerScope(callerScope); } pushEvalFrame(start: number, end: number) { this.frame.push(start, end); } pushChildScope() { this.scopeStack.push(this.scope().child()); } pushCallerScope() { this.scopeStack.push(expect(this.scope().getCallerScope(), 'pushCallerScope is called when a caller scope is present')); } pushDynamicScope(): DynamicScope { let child = this.dynamicScope().child(); this.dynamicScopeStack.push(child); return child; } pushRootScope(self: PathReference<any>, size: number): Scope { let scope = Scope.root(self, size); this.scopeStack.push(scope); return scope; } popScope() { this.scopeStack.pop(); } popDynamicScope() { this.dynamicScopeStack.pop(); } newDestroyable(d: Destroyable) { this.stack().newDestroyable(d); } /// SCOPE HELPERS getSelf(): PathReference<any> { return this.scope().getSelf(); } referenceForSymbol(symbol: number): PathReference<any> { return this.scope().getSymbol(symbol); } getArgs(): Option<EvaluatedArgs> { return this.frame.getArgs(); } /// EXECUTION resume(start: number, end: number, frame: CapturedFrame): RenderResult { return this.execute(start, end, vm => vm.frame.restore(frame)); } execute(start: number, end: number, initialize?: (vm: VM) => void): RenderResult { this.prepare(start, end, initialize); let result: IteratorResult<RenderResult>; while (true) { result = this.next(); if (result.done) break; } return result.value as RenderResult; } private prepare(start: number, end: number, initialize?: (vm: VM) => void): void { let { elementStack, frame, updatingOpcodeStack } = this; elementStack.pushSimpleBlock(); updatingOpcodeStack.push(new LinkedList<UpdatingOpcode>()); frame.push(start, end); if (initialize) initialize(this); } next(): IteratorResult<RenderResult> { let { frame, env, updatingOpcodeStack, elementStack } = this; let opcode: Option<Opcode>; if (opcode = frame.nextStatement(env)) { APPEND_OPCODES.evaluate(this, opcode); return { done: false, value: null }; } return { done: true, value: new RenderResult( env, expect(updatingOpcodeStack.pop(), 'there should be a final updating opcode stack'), elementStack.popBlock() ) }; } evaluateOpcode(opcode: Opcode) { APPEND_OPCODES.evaluate(this, opcode); } // Make sure you have opcodes that push and pop a scope around this opcode // if you need to change the scope. invokeBlock(block: InlineBlock, args: Option<EvaluatedArgs>) { let compiled = block.compile(this.env); this.pushFrame(compiled, args); } invokePartial(block: PartialBlock) { let compiled = block.compile(this.env); this.pushFrame(compiled); } invokeLayout( args: EvaluatedArgs, layout: CompiledBlock, callerScope: Scope, component: Component, manager: ComponentManager<Component>, shadow: Option<InlineBlock> ) { this.pushComponentFrame(layout, args, callerScope, component, manager, shadow); } evaluateOperand(expr: CompiledExpression<any>) { this.frame.setOperand(expr.evaluate(this)); } evaluateArgs(args: CompiledArgs) { let evaledArgs = this.frame.setArgs(args.evaluate(this)); this.frame.setOperand(evaledArgs.positional.at(0)); } bindPositionalArgs(symbols: number[]) { let args = expect(this.frame.getArgs(), 'bindPositionalArgs assumes a previous setArgs'); let { positional } = args; let scope = this.scope(); for(let i=0; i < symbols.length; i++) { scope.bindSymbol(symbols[i], positional.at(i)); } } bindNamedArgs(names: ConstantString[], symbols: number[]) { let args = expect(this.frame.getArgs(), 'bindNamedArgs assumes a previous setArgs'); let scope = this.scope(); let { named } = args; for(let i=0; i < names.length; i++) { let name = this.constants.getString(names[i]); scope.bindSymbol(symbols[i], named.get(name)); } } bindBlocks(names: ConstantString[], symbols: number[]) { let blocks = this.frame.getBlocks(); let scope = this.scope(); for(let i=0; i < names.length; i++) { let name = this.constants.getString(names[i]); scope.bindBlock(symbols[i], (blocks && blocks[name]) || null); } } bindPartialArgs(symbol: number) { let args = expect(this.frame.getArgs(), 'bindPartialArgs assumes a previous setArgs'); let scope = this.scope(); assert(args, "Cannot bind named args"); scope.bindPartialArgs(symbol, args); } bindCallerScope() { let callerScope = this.frame.getCallerScope(); let scope = this.scope(); assert(callerScope, "Cannot bind caller scope"); scope.bindCallerScope(callerScope); } bindDynamicScope(names: ConstantString[]) { let args = expect(this.frame.getArgs(), 'bindDynamicScope assumes a previous setArgs'); let scope = this.dynamicScope(); assert(args, "Cannot bind dynamic scope"); for(let i=0; i < names.length; i++) { let name = this.constants.getString(names[i]); scope.set(name, args.named.get(name)); } } }