ember-app-scheduler
Version:
Ember addon to schedule work at different phases of app life cycle.
486 lines • 14.9 kB
JavaScript
import { dict, EMPTY_ARRAY, expect, fillNulls, Stack, typePos } from '@glimmer/util';
import { ComponentBuilder } from '../../compiler';
import { Register } from '../../opcodes';
import { expr, InvokeDynamicLayout } from '../../syntax/functions';
import RawInlineBlock from '../../syntax/raw-block';
import { IsComponentDefinitionReference } from '../opcodes/content';
import * as content from './content';
import * as vm from './vm';
class Labels {
constructor() {
this.labels = dict();
this.targets = [];
}
label(name, index) {
this.labels[name] = index;
}
target(at, Target, target) {
this.targets.push({ at, Target, target });
}
patch(program) {
let { targets, labels } = this;
for (let i = 0; i < targets.length; i++) {
let { at, target } = targets[i];
let goto = labels[target] - at;
program.heap.setbyaddr(at + 1, goto);
}
}
}
export class BasicOpcodeBuilder {
constructor(env, meta, program) {
this.env = env;
this.meta = meta;
this.program = program;
this.labelsStack = new Stack();
this.constants = program.constants;
this.heap = program.heap;
this.start = this.heap.malloc();
}
get pos() {
return typePos(this.heap.size());
}
get nextPos() {
return this.heap.size();
}
upvars(count) {
return fillNulls(count);
}
reserve(name) {
this.push(name, 0, 0, 0);
}
push(name, op1 = 0, op2 = 0, op3 = 0) {
this.heap.push(name);
this.heap.push(op1);
this.heap.push(op2);
this.heap.push(op3);
}
finalize() {
this.push(22 /* Return */);
this.heap.finishMalloc(this.start);
return this.start;
}
// args
pushArgs(synthetic) {
this.push(58 /* PushArgs */, synthetic === true ? 1 : 0);
}
// helpers
get labels() {
return expect(this.labelsStack.current, 'bug: not in a label stack');
}
startLabels() {
this.labelsStack.push(new Labels());
}
stopLabels() {
let label = expect(this.labelsStack.pop(), 'unbalanced push and pop labels');
label.patch(this.program);
}
// components
pushComponentManager(definition) {
this.push(56 /* PushComponentManager */, this.other(definition));
}
pushDynamicComponentManager() {
this.push(57 /* PushDynamicComponentManager */);
}
prepareArgs(state) {
this.push(59 /* PrepareArgs */, state);
}
createComponent(state, hasDefault, hasInverse) {
let flag = (hasDefault === true ? 1 : 0) | (hasInverse === true ? 1 : 0) << 1;
this.push(60 /* CreateComponent */, flag, state);
}
registerComponentDestructor(state) {
this.push(61 /* RegisterComponentDestructor */, state);
}
beginComponentTransaction() {
this.push(65 /* BeginComponentTransaction */);
}
commitComponentTransaction() {
this.push(66 /* CommitComponentTransaction */);
}
pushComponentOperations() {
this.push(62 /* PushComponentOperations */);
}
getComponentSelf(state) {
this.push(63 /* GetComponentSelf */, state);
}
getComponentLayout(state) {
this.push(64 /* GetComponentLayout */, state);
}
didCreateElement(state) {
this.push(67 /* DidCreateElement */, state);
}
didRenderLayout(state) {
this.push(68 /* DidRenderLayout */, state);
}
// partial
getPartialTemplate() {
this.push(69 /* GetPartialTemplate */);
}
resolveMaybeLocal(name) {
this.push(70 /* ResolveMaybeLocal */, this.string(name));
}
// debugger
debugger(symbols, evalInfo) {
this.push(71 /* Debugger */, this.constants.other(symbols), this.constants.array(evalInfo));
}
// content
dynamicContent(Opcode) {
this.push(26 /* DynamicContent */, this.other(Opcode));
}
cautiousAppend() {
this.dynamicContent(new content.OptimizedCautiousAppendOpcode());
}
trustingAppend() {
this.dynamicContent(new content.OptimizedTrustingAppendOpcode());
}
// dom
text(text) {
this.push(24 /* Text */, this.constants.string(text));
}
openPrimitiveElement(tag) {
this.push(27 /* OpenElement */, this.constants.string(tag));
}
openElementWithOperations(tag) {
this.push(28 /* OpenElementWithOperations */, this.constants.string(tag));
}
openDynamicElement() {
this.push(29 /* OpenDynamicElement */);
}
flushElement() {
this.push(33 /* FlushElement */);
}
closeElement() {
this.push(34 /* CloseElement */);
}
staticAttr(_name, _namespace, _value) {
let name = this.constants.string(_name);
let namespace = _namespace ? this.constants.string(_namespace) : 0;
let value = this.constants.string(_value);
this.push(30 /* StaticAttr */, name, value, namespace);
}
dynamicAttrNS(_name, _namespace, trusting) {
let name = this.constants.string(_name);
let namespace = this.constants.string(_namespace);
this.push(32 /* DynamicAttrNS */, name, namespace, trusting === true ? 1 : 0);
}
dynamicAttr(_name, trusting) {
let name = this.constants.string(_name);
this.push(31 /* DynamicAttr */, name, trusting === true ? 1 : 0);
}
comment(_comment) {
let comment = this.constants.string(_comment);
this.push(25 /* Comment */, comment);
}
modifier(_definition) {
this.push(35 /* Modifier */, this.other(_definition));
}
// lists
putIterator() {
this.push(54 /* PutIterator */);
}
enterList(start) {
this.reserve(52 /* EnterList */);
this.labels.target(this.pos, 52 /* EnterList */, start);
}
exitList() {
this.push(53 /* ExitList */);
}
iterate(breaks) {
this.reserve(55 /* Iterate */);
this.labels.target(this.pos, 55 /* Iterate */, breaks);
}
// expressions
setVariable(symbol) {
this.push(4 /* SetVariable */, symbol);
}
getVariable(symbol) {
this.push(5 /* GetVariable */, symbol);
}
getProperty(key) {
this.push(6 /* GetProperty */, this.string(key));
}
getBlock(symbol) {
this.push(8 /* GetBlock */, symbol);
}
hasBlock(symbol) {
this.push(9 /* HasBlock */, symbol);
}
hasBlockParams(symbol) {
this.push(10 /* HasBlockParams */, symbol);
}
concat(size) {
this.push(11 /* Concat */, size);
}
function(f) {
this.push(2 /* Function */, this.func(f));
}
load(register) {
this.push(17 /* Load */, register);
}
fetch(register) {
this.push(18 /* Fetch */, register);
}
dup(register = Register.sp, offset = 0) {
return this.push(15 /* Dup */, register, offset);
}
pop(count = 1) {
return this.push(16 /* Pop */, count);
}
// vm
pushRemoteElement() {
this.push(36 /* PushRemoteElement */);
}
popRemoteElement() {
this.push(37 /* PopRemoteElement */);
}
label(name) {
this.labels.label(name, this.nextPos);
}
pushRootScope(symbols, bindCallerScope) {
this.push(19 /* RootScope */, symbols, bindCallerScope ? 1 : 0);
}
pushChildScope() {
this.push(20 /* ChildScope */);
}
popScope() {
this.push(21 /* PopScope */);
}
returnTo(label) {
this.reserve(23 /* ReturnTo */);
this.labels.target(this.pos, 23 /* ReturnTo */, label);
}
pushDynamicScope() {
this.push(39 /* PushDynamicScope */);
}
popDynamicScope() {
this.push(40 /* PopDynamicScope */);
}
pushImmediate(value) {
this.push(13 /* Constant */, this.other(value));
}
primitive(_primitive) {
let flag = 0;
let primitive;
switch (typeof _primitive) {
case 'number':
if (_primitive % 1 === 0 && _primitive > 0) {
primitive = _primitive;
} else {
primitive = this.float(_primitive);
flag = 1;
}
break;
case 'string':
primitive = this.string(_primitive);
flag = 2;
break;
case 'boolean':
primitive = _primitive | 0;
flag = 3;
break;
case 'object':
// assume null
primitive = 2;
flag = 3;
break;
case 'undefined':
primitive = 3;
flag = 3;
break;
default:
throw new Error('Invalid primitive passed to pushPrimitive');
}
this.push(14 /* PrimitiveReference */, flag << 30 | primitive);
}
helper(func) {
this.push(1 /* Helper */, this.func(func));
}
pushBlock(block) {
this.push(7 /* PushBlock */, this.block(block));
}
bindDynamicScope(_names) {
this.push(38 /* BindDynamicScope */, this.names(_names));
}
enter(args) {
this.push(49 /* Enter */, args);
}
exit() {
this.push(50 /* Exit */);
}
return() {
this.push(22 /* Return */);
}
pushFrame() {
this.push(47 /* PushFrame */);
}
popFrame() {
this.push(48 /* PopFrame */);
}
compileDynamicBlock() {
this.push(41 /* CompileDynamicBlock */);
}
invokeDynamic(invoker) {
this.push(43 /* InvokeDynamic */, this.other(invoker));
}
invokeStatic(block, callerCount = 0) {
let { parameters } = block.symbolTable;
let calleeCount = parameters.length;
let count = Math.min(callerCount, calleeCount);
this.pushFrame();
if (count) {
this.pushChildScope();
for (let i = 0; i < count; i++) {
this.dup(Register.fp, callerCount - i);
this.setVariable(parameters[i]);
}
}
let _block = this.constants.block(block);
this.push(42 /* InvokeStatic */, _block);
if (count) {
this.popScope();
}
this.popFrame();
}
test(testFunc) {
let _func;
if (testFunc === 'const') {
_func = vm.ConstTest;
} else if (testFunc === 'simple') {
_func = vm.SimpleTest;
} else if (testFunc === 'environment') {
_func = vm.EnvironmentTest;
} else if (typeof testFunc === 'function') {
_func = testFunc;
} else {
throw new Error('unreachable');
}
let func = this.constants.function(_func);
this.push(51 /* Test */, func);
}
jump(target) {
this.reserve(44 /* Jump */);
this.labels.target(this.pos, 44 /* Jump */, target);
}
jumpIf(target) {
this.reserve(45 /* JumpIf */);
this.labels.target(this.pos, 45 /* JumpIf */, target);
}
jumpUnless(target) {
this.reserve(46 /* JumpUnless */);
this.labels.target(this.pos, 46 /* JumpUnless */, target);
}
string(_string) {
return this.constants.string(_string);
}
float(num) {
return this.constants.float(num);
}
names(_names) {
let names = [];
for (let i = 0; i < _names.length; i++) {
let n = _names[i];
names[i] = this.constants.string(n);
}
return this.constants.array(names);
}
symbols(symbols) {
return this.constants.array(symbols);
}
other(value) {
return this.constants.other(value);
}
block(block) {
return block ? this.constants.block(block) : 0;
}
func(func) {
return this.constants.function(func);
}
}
function isCompilableExpression(expr) {
return typeof expr === 'object' && expr !== null && typeof expr.compile === 'function';
}
export default class OpcodeBuilder extends BasicOpcodeBuilder {
constructor(env, meta, program = env.program) {
super(env, meta, program);
this.component = new ComponentBuilder(this);
}
compileArgs(params, hash, synthetic) {
let positional = 0;
if (params) {
for (let i = 0; i < params.length; i++) {
expr(params[i], this);
}
positional = params.length;
}
this.pushImmediate(positional);
let names = EMPTY_ARRAY;
if (hash) {
names = hash[0];
let val = hash[1];
for (let i = 0; i < val.length; i++) {
expr(val[i], this);
}
}
this.pushImmediate(names);
this.pushArgs(synthetic);
}
compile(expr) {
if (isCompilableExpression(expr)) {
return expr.compile(this);
} else {
return expr;
}
}
guardedAppend(expression, trusting) {
this.startLabels();
this.pushFrame();
this.returnTo('END');
expr(expression, this);
this.dup();
this.test(reference => {
return IsComponentDefinitionReference.create(reference);
});
this.enter(2);
this.jumpUnless('ELSE');
this.pushDynamicComponentManager();
this.invokeComponent(null, null, null, null, null);
this.exit();
this.return();
this.label('ELSE');
if (trusting) {
this.trustingAppend();
} else {
this.cautiousAppend();
}
this.exit();
this.return();
this.label('END');
this.popFrame();
this.stopLabels();
}
invokeComponent(attrs, params, hash, block, inverse = null) {
this.fetch(Register.s0);
this.dup(Register.sp, 1);
this.load(Register.s0);
this.pushBlock(block);
this.pushBlock(inverse);
this.compileArgs(params, hash, false);
this.prepareArgs(Register.s0);
this.beginComponentTransaction();
this.pushDynamicScope();
this.createComponent(Register.s0, block !== null, inverse !== null);
this.registerComponentDestructor(Register.s0);
this.getComponentSelf(Register.s0);
this.getComponentLayout(Register.s0);
this.invokeDynamic(new InvokeDynamicLayout(attrs && attrs.scan()));
this.popFrame();
this.popScope();
this.popDynamicScope();
this.commitComponentTransaction();
this.load(Register.s0);
}
template(block) {
if (!block) return null;
return new RawInlineBlock(this.meta, block.statements, block.parameters);
}
}