ember-legacy-class-transform
Version:
The default blueprint for ember-cli addons.
256 lines • 8.19 kB
JavaScript
import { clear, move as moveBounds } from '../bounds';
import { ElementStack } from '../builder';
import { Stack, LinkedList, dict, expect } from '@glimmer/util';
import { IteratorSynchronizer,
// Tags
combine, UpdatableTag, combineSlice, CONSTANT_TAG, INITIAL } from '@glimmer/reference';
import { UpdatingOpcode } from '../opcodes';
import VM, { EvaluationStack } from './append';
export default class UpdatingVM {
constructor(env, { alwaysRevalidate = false }) {
this.frameStack = new Stack();
this.env = env;
this.constants = env.program.constants;
this.dom = env.getDOM();
this.alwaysRevalidate = alwaysRevalidate;
}
execute(opcodes, handler) {
let { frameStack } = this;
this.try(opcodes, handler);
while (true) {
if (frameStack.isEmpty()) break;
let opcode = this.frame.nextStatement();
if (opcode === null) {
this.frameStack.pop();
continue;
}
opcode.evaluate(this);
}
}
get frame() {
return expect(this.frameStack.current, 'bug: expected a frame');
}
goto(op) {
this.frame.goto(op);
}
try(ops, handler) {
this.frameStack.push(new UpdatingVMFrame(this, ops, handler));
}
throw() {
this.frame.handleException();
this.frameStack.pop();
}
evaluateOpcode(opcode) {
opcode.evaluate(this);
}
}
export class BlockOpcode extends UpdatingOpcode {
constructor(start, state, bounds, children) {
super();
this.start = start;
this.type = "block";
this.next = null;
this.prev = null;
let { env, scope, dynamicScope, stack } = state;
this.children = children;
this.env = env;
this.scope = scope;
this.dynamicScope = dynamicScope;
this.stack = stack;
this.bounds = bounds;
}
parentElement() {
return this.bounds.parentElement();
}
firstNode() {
return this.bounds.firstNode();
}
lastNode() {
return this.bounds.lastNode();
}
evaluate(vm) {
vm.try(this.children, null);
}
destroy() {
this.bounds.destroy();
}
didDestroy() {
this.env.didDestroy(this.bounds);
}
toJSON() {
let details = dict();
details["guid"] = `${this._guid}`;
return {
guid: this._guid,
type: this.type,
details,
children: this.children.toArray().map(op => op.toJSON())
};
}
}
export class TryOpcode extends BlockOpcode {
constructor(start, state, bounds, children) {
super(start, state, bounds, children);
this.type = "try";
this.tag = this._tag = UpdatableTag.create(CONSTANT_TAG);
}
didInitializeChildren() {
this._tag.inner.update(combineSlice(this.children));
}
evaluate(vm) {
vm.try(this.children, this);
}
handleException() {
let { env, bounds, children, scope, dynamicScope, start, stack, prev, next } = this;
children.clear();
let elementStack = ElementStack.resume(env, bounds, bounds.reset(env));
let vm = new VM(env, scope, dynamicScope, elementStack);
let updating = new LinkedList();
vm.execute(start, vm => {
vm.stack = EvaluationStack.restore(stack);
vm.updatingOpcodeStack.push(updating);
vm.updateWith(this);
vm.updatingOpcodeStack.push(children);
});
this.prev = prev;
this.next = next;
}
toJSON() {
let json = super.toJSON();
let details = json["details"];
if (!details) {
details = json["details"] = {};
}
return super.toJSON();
}
}
class ListRevalidationDelegate {
constructor(opcode, marker) {
this.opcode = opcode;
this.marker = marker;
this.didInsert = false;
this.didDelete = false;
this.map = opcode.map;
this.updating = opcode['children'];
}
insert(key, item, memo, before) {
let { map, opcode, updating } = this;
let nextSibling = null;
let reference = null;
if (before) {
reference = map[before];
nextSibling = reference['bounds'].firstNode();
} else {
nextSibling = this.marker;
}
let vm = opcode.vmForInsertion(nextSibling);
let tryOpcode = null;
let { start } = opcode;
vm.execute(start, vm => {
map[key] = tryOpcode = vm.iterate(memo, item);
vm.updatingOpcodeStack.push(new LinkedList());
vm.updateWith(tryOpcode);
vm.updatingOpcodeStack.push(tryOpcode.children);
});
updating.insertBefore(tryOpcode, reference);
this.didInsert = true;
}
retain(_key, _item, _memo) {}
move(key, _item, _memo, before) {
let { map, updating } = this;
let entry = map[key];
let reference = map[before] || null;
if (before) {
moveBounds(entry, reference.firstNode());
} else {
moveBounds(entry, this.marker);
}
updating.remove(entry);
updating.insertBefore(entry, reference);
}
delete(key) {
let { map } = this;
let opcode = map[key];
opcode.didDestroy();
clear(opcode);
this.updating.remove(opcode);
delete map[key];
this.didDelete = true;
}
done() {
this.opcode.didInitializeChildren(this.didInsert || this.didDelete);
}
}
export class ListBlockOpcode extends BlockOpcode {
constructor(start, state, bounds, children, artifacts) {
super(start, state, bounds, children);
this.type = "list-block";
this.map = dict();
this.lastIterated = INITIAL;
this.artifacts = artifacts;
let _tag = this._tag = UpdatableTag.create(CONSTANT_TAG);
this.tag = combine([artifacts.tag, _tag]);
}
didInitializeChildren(listDidChange = true) {
this.lastIterated = this.artifacts.tag.value();
if (listDidChange) {
this._tag.inner.update(combineSlice(this.children));
}
}
evaluate(vm) {
let { artifacts, lastIterated } = this;
if (!artifacts.tag.validate(lastIterated)) {
let { bounds } = this;
let { dom } = vm;
let marker = dom.createComment('');
dom.insertAfter(bounds.parentElement(), marker, expect(bounds.lastNode(), "can't insert after an empty bounds"));
let target = new ListRevalidationDelegate(this, marker);
let synchronizer = new IteratorSynchronizer({ target, artifacts });
synchronizer.sync();
this.parentElement().removeChild(marker);
}
// Run now-updated updating opcodes
super.evaluate(vm);
}
vmForInsertion(nextSibling) {
let { env, scope, dynamicScope } = this;
let elementStack = ElementStack.forInitialRender(this.env, this.bounds.parentElement(), nextSibling);
return new VM(env, scope, dynamicScope, elementStack);
}
toJSON() {
let json = super.toJSON();
let map = this.map;
let inner = Object.keys(map).map(key => {
return `${JSON.stringify(key)}: ${map[key]._guid}`;
}).join(", ");
let details = json["details"];
if (!details) {
details = json["details"] = {};
}
details["map"] = `{${inner}}`;
return json;
}
}
class UpdatingVMFrame {
constructor(vm, ops, exceptionHandler) {
this.vm = vm;
this.ops = ops;
this.exceptionHandler = exceptionHandler;
this.vm = vm;
this.ops = ops;
this.current = ops.head();
}
goto(op) {
this.current = op;
}
nextStatement() {
let { current, ops } = this;
if (current) this.current = ops.nextNode(current);
return current;
}
handleException() {
if (this.exceptionHandler) {
this.exceptionHandler.handleException();
}
}
}