ember-legacy-class-transform
Version:
The default blueprint for ember-cli addons.
699 lines • 23.8 kB
JavaScript
import { map } from '@glimmer/reference';
import { assert, dict, EMPTY_ARRAY, unwrap } from '@glimmer/util';
import * as WireFormat from '@glimmer/wire-format';
import OpcodeBuilder from '../compiled/opcodes/builder';
import { Register } from '../opcodes';
import * as ClientSide from '../syntax/client-side';
import RawInlineBlock from './raw-block';
var Ops = WireFormat.Ops;
export const ATTRS_BLOCK = '&attrs';
class Compilers {
constructor(offset = 0) {
this.offset = offset;
this.names = dict();
this.funcs = [];
}
add(name, func) {
this.funcs.push(func);
this.names[name] = this.funcs.length - 1;
}
compile(sexp, builder) {
let name = sexp[this.offset];
let index = this.names[name];
let func = this.funcs[index];
assert(!!func, `expected an implementation for ${this.offset === 0 ? Ops[sexp[0]] : ClientSide.Ops[sexp[1]]}`);
func(sexp, builder);
}
}
const STATEMENTS = new Compilers();
const CLIENT_SIDE = new Compilers(1);
STATEMENTS.add(Ops.Text, (sexp, builder) => {
builder.text(sexp[1]);
});
STATEMENTS.add(Ops.Comment, (sexp, builder) => {
builder.comment(sexp[1]);
});
STATEMENTS.add(Ops.CloseElement, (_sexp, builder) => {
builder.closeElement();
});
STATEMENTS.add(Ops.FlushElement, (_sexp, builder) => {
builder.flushElement();
});
STATEMENTS.add(Ops.Modifier, (sexp, builder) => {
let { env, meta } = builder;
let [, name, params, hash] = sexp;
if (env.hasModifier(name, meta.templateMeta)) {
builder.compileArgs(params, hash, true);
builder.modifier(env.lookupModifier(name, meta.templateMeta));
} else {
throw new Error(`Compile Error ${name} is not a modifier: Helpers may not be used in the element form.`);
}
});
STATEMENTS.add(Ops.StaticAttr, (sexp, builder) => {
let [, name, value, namespace] = sexp;
builder.staticAttr(name, namespace, value);
});
STATEMENTS.add(Ops.DynamicAttr, (sexp, builder) => {
dynamicAttr(sexp, false, builder);
});
STATEMENTS.add(Ops.TrustingAttr, (sexp, builder) => {
dynamicAttr(sexp, true, builder);
});
function dynamicAttr(sexp, trusting, builder) {
let [, name, value, namespace] = sexp;
expr(value, builder);
if (namespace) {
builder.dynamicAttrNS(name, namespace, trusting);
} else {
builder.dynamicAttr(name, trusting);
}
}
STATEMENTS.add(Ops.OpenElement, (sexp, builder) => {
builder.openPrimitiveElement(sexp[1]);
});
CLIENT_SIDE.add(ClientSide.Ops.OpenComponentElement, (sexp, builder) => {
builder.pushComponentOperations();
builder.openElementWithOperations(sexp[2]);
});
CLIENT_SIDE.add(ClientSide.Ops.DidCreateElement, (_sexp, builder) => {
builder.didCreateElement(Register.s0);
});
CLIENT_SIDE.add(ClientSide.Ops.DidRenderLayout, (_sexp, builder) => {
builder.didRenderLayout(Register.s0);
});
STATEMENTS.add(Ops.Append, (sexp, builder) => {
let [, value, trusting] = sexp;
let { inlines } = builder.env.macros();
let returned = inlines.compile(sexp, builder) || value;
if (returned === true) return;
let isGet = E.isGet(value);
let isMaybeLocal = E.isMaybeLocal(value);
if (trusting) {
builder.guardedAppend(value, true);
} else {
if (isGet || isMaybeLocal) {
builder.guardedAppend(value, false);
} else {
expr(value, builder);
builder.cautiousAppend();
}
}
});
STATEMENTS.add(Ops.Block, (sexp, builder) => {
let [, name, params, hash, _template, _inverse] = sexp;
let template = builder.template(_template);
let inverse = builder.template(_inverse);
let templateBlock = template && template.scan();
let inverseBlock = inverse && inverse.scan();
let { blocks } = builder.env.macros();
blocks.compile(name, params, hash, templateBlock, inverseBlock, builder);
});
export class InvokeDynamicLayout {
constructor(attrs) {
this.attrs = attrs;
}
invoke(vm, layout) {
let { symbols, hasEval } = layout.symbolTable;
let stack = vm.stack;
let scope = vm.pushRootScope(symbols.length + 1, true);
scope.bindSelf(stack.pop());
scope.bindBlock(symbols.indexOf(ATTRS_BLOCK) + 1, this.attrs);
let lookup = null;
let $eval = -1;
if (hasEval) {
$eval = symbols.indexOf('$eval') + 1;
lookup = dict();
}
let callerNames = stack.pop();
for (let i = callerNames.length - 1; i >= 0; i--) {
let symbol = symbols.indexOf(callerNames[i]);
let value = stack.pop();
if (symbol !== -1) scope.bindSymbol(symbol + 1, value);
if (hasEval) lookup[callerNames[i]] = value;
}
let numPositionalArgs = stack.pop();
assert(typeof numPositionalArgs === 'number', '[BUG] Incorrect value of positional argument count found during invoke-dynamic-layout.');
// Currently we don't support accessing positional args in templates, so just throw them away
stack.pop(numPositionalArgs);
let inverseSymbol = symbols.indexOf('&inverse');
let inverse = stack.pop();
if (inverseSymbol !== -1) {
scope.bindBlock(inverseSymbol + 1, inverse);
}
if (lookup) lookup['&inverse'] = inverse;
let defaultSymbol = symbols.indexOf('&default');
let defaultBlock = stack.pop();
if (defaultSymbol !== -1) {
scope.bindBlock(defaultSymbol + 1, defaultBlock);
}
if (lookup) lookup['&default'] = defaultBlock;
if (lookup) scope.bindEvalScope(lookup);
vm.pushFrame();
vm.call(layout.handle);
}
toJSON() {
return { GlimmerDebug: '<invoke-dynamic-layout>' };
}
}
STATEMENTS.add(Ops.Component, (sexp, builder) => {
let [, tag, attrs, args, block] = sexp;
if (builder.env.hasComponentDefinition(tag, builder.meta.templateMeta)) {
let child = builder.template(block);
let attrsBlock = new RawInlineBlock(builder.meta, attrs, EMPTY_ARRAY);
let definition = builder.env.getComponentDefinition(tag, builder.meta.templateMeta);
builder.pushComponentManager(definition);
builder.invokeComponent(attrsBlock, null, args, child && child.scan());
} else if (block && block.parameters.length) {
throw new Error(`Compile Error: Cannot find component ${tag}`);
} else {
builder.openPrimitiveElement(tag);
for (let i = 0; i < attrs.length; i++) {
STATEMENTS.compile(attrs[i], builder);
}
builder.flushElement();
if (block) {
let stmts = block.statements;
for (let i = 0; i < stmts.length; i++) {
STATEMENTS.compile(stmts[i], builder);
}
}
builder.closeElement();
}
});
export class PartialInvoker {
constructor(outerSymbols, evalInfo) {
this.outerSymbols = outerSymbols;
this.evalInfo = evalInfo;
}
invoke(vm, _partial) {
let partial = unwrap(_partial);
let partialSymbols = partial.symbolTable.symbols;
let outerScope = vm.scope();
let partialScope = vm.pushRootScope(partialSymbols.length, false);
partialScope.bindCallerScope(outerScope.getCallerScope());
partialScope.bindEvalScope(outerScope.getEvalScope());
partialScope.bindSelf(outerScope.getSelf());
let { evalInfo, outerSymbols } = this;
let locals = dict();
for (let i = 0; i < evalInfo.length; i++) {
let slot = evalInfo[i];
let name = outerSymbols[slot - 1];
let ref = outerScope.getSymbol(slot);
locals[name] = ref;
}
let evalScope = outerScope.getEvalScope();
for (let i = 0; i < partialSymbols.length; i++) {
let name = partialSymbols[i];
let symbol = i + 1;
let value = evalScope[name];
if (value !== undefined) partialScope.bind(symbol, value);
}
partialScope.bindPartialMap(locals);
vm.pushFrame();
vm.call(partial.handle);
}
}
STATEMENTS.add(Ops.Partial, (sexp, builder) => {
let [, name, evalInfo] = sexp;
let { templateMeta, symbols } = builder.meta;
function helper(vm, args) {
let { env } = vm;
let nameRef = args.positional.at(0);
return map(nameRef, n => {
if (typeof n === 'string' && n) {
if (!env.hasPartial(n, templateMeta)) {
throw new Error(`Could not find a partial named "${n}"`);
}
return env.lookupPartial(n, templateMeta);
} else if (n) {
throw new Error(`Could not find a partial named "${String(n)}"`);
} else {
return null;
}
});
}
builder.startLabels();
builder.pushFrame();
builder.returnTo('END');
expr(name, builder);
builder.pushImmediate(1);
builder.pushImmediate(EMPTY_ARRAY);
builder.pushArgs(true);
builder.helper(helper);
builder.dup();
builder.test('simple');
builder.enter(2);
builder.jumpUnless('ELSE');
builder.getPartialTemplate();
builder.compileDynamicBlock();
builder.invokeDynamic(new PartialInvoker(symbols, evalInfo));
builder.popScope();
builder.popFrame();
builder.label('ELSE');
builder.exit();
builder.return();
builder.label('END');
builder.popFrame();
builder.stopLabels();
});
class InvokeDynamicYield {
constructor(callerCount) {
this.callerCount = callerCount;
}
invoke(vm, block) {
let { callerCount } = this;
let stack = vm.stack;
if (!block) {
// To balance the pop{Frame,Scope}
vm.pushFrame();
vm.pushCallerScope();
return;
}
let table = block.symbolTable;
let locals = table.parameters; // always present in inline blocks
let calleeCount = locals ? locals.length : 0;
let count = Math.min(callerCount, calleeCount);
vm.pushFrame();
vm.pushCallerScope(calleeCount > 0);
let scope = vm.scope();
for (let i = 0; i < count; i++) {
scope.bindSymbol(locals[i], stack.fromBase(callerCount - i));
}
vm.call(block.handle);
}
toJSON() {
return { GlimmerDebug: `<invoke-dynamic-yield caller-count=${this.callerCount}>` };
}
}
STATEMENTS.add(Ops.Yield, (sexp, builder) => {
let [, to, params] = sexp;
let count = compileList(params, builder);
builder.getBlock(to);
builder.compileDynamicBlock();
builder.invokeDynamic(new InvokeDynamicYield(count));
builder.popScope();
builder.popFrame();
if (count) {
builder.pop(count);
}
});
STATEMENTS.add(Ops.Debugger, (sexp, builder) => {
let [, evalInfo] = sexp;
builder.debugger(builder.meta.symbols, evalInfo);
});
STATEMENTS.add(Ops.ClientSideStatement, (sexp, builder) => {
CLIENT_SIDE.compile(sexp, builder);
});
const EXPRESSIONS = new Compilers();
const CLIENT_SIDE_EXPRS = new Compilers(1);
var E = WireFormat.Expressions;
export function expr(expression, builder) {
if (Array.isArray(expression)) {
EXPRESSIONS.compile(expression, builder);
} else {
builder.primitive(expression);
}
}
EXPRESSIONS.add(Ops.Unknown, (sexp, builder) => {
let name = sexp[1];
if (builder.env.hasHelper(name, builder.meta.templateMeta)) {
EXPRESSIONS.compile([Ops.Helper, name, EMPTY_ARRAY, null], builder);
} else if (builder.meta.asPartial) {
builder.resolveMaybeLocal(name);
} else {
builder.getVariable(0);
builder.getProperty(name);
}
});
EXPRESSIONS.add(Ops.Concat, (sexp, builder) => {
let parts = sexp[1];
for (let i = 0; i < parts.length; i++) {
expr(parts[i], builder);
}
builder.concat(parts.length);
});
CLIENT_SIDE_EXPRS.add(ClientSide.Ops.FunctionExpression, (sexp, builder) => {
builder.function(sexp[2]);
});
EXPRESSIONS.add(Ops.Helper, (sexp, builder) => {
let { env, meta } = builder;
let [, name, params, hash] = sexp;
if (env.hasHelper(name, meta.templateMeta)) {
builder.compileArgs(params, hash, true);
builder.helper(env.lookupHelper(name, meta.templateMeta));
} else {
throw new Error(`Compile Error: ${name} is not a helper`);
}
});
EXPRESSIONS.add(Ops.Get, (sexp, builder) => {
let [, head, path] = sexp;
builder.getVariable(head);
for (let i = 0; i < path.length; i++) {
builder.getProperty(path[i]);
}
});
EXPRESSIONS.add(Ops.MaybeLocal, (sexp, builder) => {
let [, path] = sexp;
if (builder.meta.asPartial) {
let head = path[0];
path = path.slice(1);
builder.resolveMaybeLocal(head);
} else {
builder.getVariable(0);
}
for (let i = 0; i < path.length; i++) {
builder.getProperty(path[i]);
}
});
EXPRESSIONS.add(Ops.Undefined, (_sexp, builder) => {
return builder.primitive(undefined);
});
EXPRESSIONS.add(Ops.HasBlock, (sexp, builder) => {
builder.hasBlock(sexp[1]);
});
EXPRESSIONS.add(Ops.HasBlockParams, (sexp, builder) => {
builder.hasBlockParams(sexp[1]);
});
EXPRESSIONS.add(Ops.ClientSideExpression, (sexp, builder) => {
CLIENT_SIDE_EXPRS.compile(sexp, builder);
});
export function compileList(params, builder) {
if (!params) return 0;
for (let i = 0; i < params.length; i++) {
expr(params[i], builder);
}
return params.length;
}
export class Blocks {
constructor() {
this.names = dict();
this.funcs = [];
}
add(name, func) {
this.funcs.push(func);
this.names[name] = this.funcs.length - 1;
}
addMissing(func) {
this.missing = func;
}
compile(name, params, hash, template, inverse, builder) {
let index = this.names[name];
if (index === undefined) {
assert(!!this.missing, `${name} not found, and no catch-all block handler was registered`);
let func = this.missing;
let handled = func(name, params, hash, template, inverse, builder);
assert(!!handled, `${name} not found, and the catch-all block handler didn't handle it`);
} else {
let func = this.funcs[index];
func(params, hash, template, inverse, builder);
}
}
}
export const BLOCKS = new Blocks();
export class Inlines {
constructor() {
this.names = dict();
this.funcs = [];
}
add(name, func) {
this.funcs.push(func);
this.names[name] = this.funcs.length - 1;
}
addMissing(func) {
this.missing = func;
}
compile(sexp, builder) {
let value = sexp[1];
// TODO: Fix this so that expression macros can return
// things like components, so that {{component foo}}
// is the same as {{(component foo)}}
if (!Array.isArray(value)) return ['expr', value];
let name;
let params;
let hash;
if (value[0] === Ops.Helper) {
name = value[1];
params = value[2];
hash = value[3];
} else if (value[0] === Ops.Unknown) {
name = value[1];
params = hash = null;
} else {
return ['expr', value];
}
let index = this.names[name];
if (index === undefined && this.missing) {
let func = this.missing;
let returned = func(name, params, hash, builder);
return returned === false ? ['expr', value] : returned;
} else if (index !== undefined) {
let func = this.funcs[index];
let returned = func(name, params, hash, builder);
return returned === false ? ['expr', value] : returned;
} else {
return ['expr', value];
}
}
}
export const INLINES = new Inlines();
populateBuiltins(BLOCKS, INLINES);
export function populateBuiltins(blocks = new Blocks(), inlines = new Inlines()) {
blocks.add('if', (params, _hash, template, inverse, builder) => {
// PutArgs
// Test(Environment)
// Enter(BEGIN, END)
// BEGIN: Noop
// JumpUnless(ELSE)
// Evaluate(default)
// Jump(END)
// ELSE: Noop
// Evalulate(inverse)
// END: Noop
// Exit
if (!params || params.length !== 1) {
throw new Error(`SYNTAX ERROR: #if requires a single argument`);
}
builder.startLabels();
builder.pushFrame();
builder.returnTo('END');
expr(params[0], builder);
builder.test('environment');
builder.enter(1);
builder.jumpUnless('ELSE');
builder.invokeStatic(unwrap(template));
if (inverse) {
builder.jump('EXIT');
builder.label('ELSE');
builder.invokeStatic(inverse);
builder.label('EXIT');
builder.exit();
builder.return();
} else {
builder.label('ELSE');
builder.exit();
builder.return();
}
builder.label('END');
builder.popFrame();
builder.stopLabels();
});
blocks.add('unless', (params, _hash, template, inverse, builder) => {
// PutArgs
// Test(Environment)
// Enter(BEGIN, END)
// BEGIN: Noop
// JumpUnless(ELSE)
// Evaluate(default)
// Jump(END)
// ELSE: Noop
// Evalulate(inverse)
// END: Noop
// Exit
if (!params || params.length !== 1) {
throw new Error(`SYNTAX ERROR: #unless requires a single argument`);
}
builder.startLabels();
builder.pushFrame();
builder.returnTo('END');
expr(params[0], builder);
builder.test('environment');
builder.enter(1);
builder.jumpIf('ELSE');
builder.invokeStatic(unwrap(template));
if (inverse) {
builder.jump('EXIT');
builder.label('ELSE');
builder.invokeStatic(inverse);
builder.label('EXIT');
builder.exit();
builder.return();
} else {
builder.label('ELSE');
builder.exit();
builder.return();
}
builder.label('END');
builder.popFrame();
builder.stopLabels();
});
blocks.add('with', (params, _hash, template, inverse, builder) => {
// PutArgs
// Test(Environment)
// Enter(BEGIN, END)
// BEGIN: Noop
// JumpUnless(ELSE)
// Evaluate(default)
// Jump(END)
// ELSE: Noop
// Evalulate(inverse)
// END: Noop
// Exit
if (!params || params.length !== 1) {
throw new Error(`SYNTAX ERROR: #with requires a single argument`);
}
builder.startLabels();
builder.pushFrame();
builder.returnTo('END');
expr(params[0], builder);
builder.dup();
builder.test('environment');
builder.enter(2);
builder.jumpUnless('ELSE');
builder.invokeStatic(unwrap(template), 1);
if (inverse) {
builder.jump('EXIT');
builder.label('ELSE');
builder.invokeStatic(inverse);
builder.label('EXIT');
builder.exit();
builder.return();
} else {
builder.label('ELSE');
builder.exit();
builder.return();
}
builder.label('END');
builder.popFrame();
builder.stopLabels();
});
blocks.add('each', (params, hash, template, inverse, builder) => {
// Enter(BEGIN, END)
// BEGIN: Noop
// PutArgs
// PutIterable
// JumpUnless(ELSE)
// EnterList(BEGIN2, END2)
// ITER: Noop
// NextIter(BREAK)
// BEGIN2: Noop
// PushChildScope
// Evaluate(default)
// PopScope
// END2: Noop
// Exit
// Jump(ITER)
// BREAK: Noop
// ExitList
// Jump(END)
// ELSE: Noop
// Evalulate(inverse)
// END: Noop
// Exit
builder.startLabels();
builder.pushFrame();
builder.returnTo('END');
if (hash && hash[0][0] === 'key') {
expr(hash[1][0], builder);
} else {
builder.primitive(null);
}
expr(params[0], builder);
builder.enter(2);
builder.putIterator();
builder.jumpUnless('ELSE');
builder.pushFrame();
builder.returnTo('ITER');
builder.dup(Register.fp, 1);
builder.enterList('BODY');
builder.label('ITER');
builder.iterate('BREAK');
builder.label('BODY');
builder.invokeStatic(unwrap(template), 2);
builder.pop(2);
builder.exit();
builder.return();
builder.label('BREAK');
builder.exitList();
builder.popFrame();
if (inverse) {
builder.jump('EXIT');
builder.label('ELSE');
builder.invokeStatic(inverse);
builder.label('EXIT');
builder.exit();
builder.return();
} else {
builder.label('ELSE');
builder.exit();
builder.return();
}
builder.label('END');
builder.popFrame();
builder.stopLabels();
});
blocks.add('-in-element', (params, hash, template, _inverse, builder) => {
if (!params || params.length !== 1) {
throw new Error(`SYNTAX ERROR: #-in-element requires a single argument`);
}
builder.startLabels();
builder.pushFrame();
builder.returnTo('END');
if (hash && hash[0].length) {
let [keys, values] = hash;
if (keys.length === 1 && keys[0] === 'nextSibling') {
expr(values[0], builder);
} else {
throw new Error(`SYNTAX ERROR: #-in-element does not take a \`${keys[0]}\` option`);
}
} else {
expr(null, builder);
}
expr(params[0], builder);
builder.dup();
builder.test('simple');
builder.enter(3);
builder.jumpUnless('ELSE');
builder.pushRemoteElement();
builder.invokeStatic(unwrap(template));
builder.popRemoteElement();
builder.label('ELSE');
builder.exit();
builder.return();
builder.label('END');
builder.popFrame();
builder.stopLabels();
});
blocks.add('-with-dynamic-vars', (_params, hash, template, _inverse, builder) => {
if (hash) {
let [names, expressions] = hash;
compileList(expressions, builder);
builder.pushDynamicScope();
builder.bindDynamicScope(names);
builder.invokeStatic(unwrap(template));
builder.popDynamicScope();
} else {
builder.invokeStatic(unwrap(template));
}
});
return { blocks, inlines };
}
export function compileStatement(statement, builder) {
STATEMENTS.compile(statement, builder);
}
export function compileStatements(statements, meta, env) {
let b = new OpcodeBuilder(env, meta);
for (let i = 0; i < statements.length; i++) {
compileStatement(statements[i], b);
}
return b;
}