ember-material-icons
Version:
Google Material icons for your ember-cli app
418 lines (325 loc) • 11.4 kB
text/typescript
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));
}
}
}