ember-legacy-class-transform
Version:
The default blueprint for ember-cli addons.
280 lines • 8.54 kB
JavaScript
import { assert } from "@glimmer/util";
import { Stack, DictSet, expect } from "@glimmer/util";
import { Statements, Ops } from '@glimmer/wire-format';
export class Block {
constructor() {
this.statements = [];
}
push(statement) {
this.statements.push(statement);
}
}
export class InlineBlock extends Block {
constructor(table) {
super();
this.table = table;
}
toJSON() {
return {
statements: this.statements,
parameters: this.table.slots
};
}
}
export class TemplateBlock extends Block {
constructor(symbolTable) {
super();
this.symbolTable = symbolTable;
this.type = "template";
this.yields = new DictSet();
this.named = new DictSet();
this.blocks = [];
this.hasEval = false;
}
push(statement) {
this.statements.push(statement);
}
toJSON() {
return {
symbols: this.symbolTable.symbols,
statements: this.statements,
hasEval: this.hasEval
};
}
}
export class ComponentBlock extends Block {
constructor(table) {
super();
this.table = table;
this.attributes = [];
this.arguments = [];
this.inParams = true;
this.positionals = [];
}
push(statement) {
if (this.inParams) {
if (Statements.isFlushElement(statement)) {
this.inParams = false;
} else if (Statements.isArgument(statement)) {
this.arguments.push(statement);
} else if (Statements.isAttribute(statement)) {
this.attributes.push(statement);
} else if (Statements.isModifier(statement)) {
throw new Error('Compile Error: Element modifiers are not allowed in components');
} else {
throw new Error('Compile Error: only parameters allowed before flush-element');
}
} else {
this.statements.push(statement);
}
}
toJSON() {
let args = this.arguments;
let keys = args.map(arg => arg[1]);
let values = args.map(arg => arg[2]);
return [this.attributes, [keys, values], {
statements: this.statements,
parameters: this.table.slots
}];
}
}
export class Template {
constructor(symbols, meta) {
this.meta = meta;
this.block = new TemplateBlock(symbols);
}
toJSON() {
return {
block: this.block.toJSON(),
meta: this.meta
};
}
}
export default class JavaScriptCompiler {
constructor(opcodes, symbols, meta) {
this.blocks = new Stack();
this.values = [];
this.opcodes = opcodes;
this.template = new Template(symbols, meta);
}
static process(opcodes, symbols, meta) {
let compiler = new JavaScriptCompiler(opcodes, symbols, meta);
return compiler.process();
}
get currentBlock() {
return expect(this.blocks.current, 'Expected a block on the stack');
}
process() {
this.opcodes.forEach(([opcode, ...args]) => {
if (!this[opcode]) {
throw new Error(`unimplemented ${opcode} on JavaScriptCompiler`);
}
this[opcode](...args);
});
return this.template;
}
/// Nesting
startBlock([program]) {
let block = new InlineBlock(program['symbols']);
this.blocks.push(block);
}
endBlock() {
let { template, blocks } = this;
let block = blocks.pop();
template.block.blocks.push(block.toJSON());
}
startProgram() {
this.blocks.push(this.template.block);
}
endProgram() {}
/// Statements
text(content) {
this.push([Ops.Text, content]);
}
append(trusted) {
this.push([Ops.Append, this.popValue(), trusted]);
}
comment(value) {
this.push([Ops.Comment, value]);
}
modifier(name) {
let params = this.popValue();
let hash = this.popValue();
this.push([Ops.Modifier, name, params, hash]);
}
block(name, template, inverse) {
let params = this.popValue();
let hash = this.popValue();
let blocks = this.template.block.blocks;
assert(typeof template !== 'number' || blocks[template] !== null, 'missing block in the compiler');
assert(typeof inverse !== 'number' || blocks[inverse] !== null, 'missing block in the compiler');
this.push([Ops.Block, name, params, hash, blocks[template], blocks[inverse]]);
}
openElement(element) {
let tag = element.tag;
if (tag.indexOf('-') !== -1) {
this.startComponent(element);
} else if (element.blockParams.length > 0) {
throw new Error(`Compile Error: <${element.tag}> is not a component and doesn't support block parameters`);
} else {
this.push([Ops.OpenElement, tag]);
}
}
flushElement() {
this.push([Ops.FlushElement]);
}
closeElement(element) {
let tag = element.tag;
if (tag.indexOf('-') !== -1) {
let [attrs, args, block] = this.endComponent();
this.push([Ops.Component, tag, attrs, args, block]);
} else {
this.push([Ops.CloseElement]);
}
}
staticAttr(name, namespace) {
let value = this.popValue();
this.push([Ops.StaticAttr, name, value, namespace]);
}
dynamicAttr(name, namespace) {
let value = this.popValue();
this.push([Ops.DynamicAttr, name, value, namespace]);
}
trustingAttr(name, namespace) {
let value = this.popValue();
this.push([Ops.TrustingAttr, name, value, namespace]);
}
staticArg(name) {
let value = this.popValue();
this.push([Ops.StaticArg, name, value]);
}
dynamicArg(name) {
let value = this.popValue();
this.push([Ops.DynamicArg, name, value]);
}
yield(to) {
let params = this.popValue();
this.push([Ops.Yield, to, params]);
}
debugger(evalInfo) {
this.push([Ops.Debugger, evalInfo]);
this.template.block.hasEval = true;
}
hasBlock(name) {
this.pushValue([Ops.HasBlock, name]);
}
hasBlockParams(name) {
this.pushValue([Ops.HasBlockParams, name]);
}
partial(evalInfo) {
let params = this.popValue();
this.push([Ops.Partial, params[0], evalInfo]);
this.template.block.hasEval = true;
}
/// Expressions
literal(value) {
if (value === undefined) {
this.pushValue([Ops.Undefined]);
} else {
this.pushValue(value);
}
}
unknown(name) {
this.pushValue([Ops.Unknown, name]);
}
get(head, path) {
this.pushValue([Ops.Get, head, path]);
}
maybeLocal(path) {
this.pushValue([Ops.MaybeLocal, path]);
}
concat() {
this.pushValue([Ops.Concat, this.popValue()]);
}
helper(name) {
let params = this.popValue();
let hash = this.popValue();
this.pushValue([Ops.Helper, name, params, hash]);
}
/// Stack Management Opcodes
startComponent(element) {
let component = new ComponentBlock(element['symbols']);
this.blocks.push(component);
}
endComponent() {
let component = this.blocks.pop();
assert(component instanceof ComponentBlock, "Compiler bug: endComponent() should end a component");
return component.toJSON();
}
prepareArray(size) {
let values = [];
for (let i = 0; i < size; i++) {
values.push(this.popValue());
}
this.pushValue(values);
}
prepareObject(size) {
assert(this.values.length >= size, `Expected ${size} values on the stack, found ${this.values.length}`);
let keys = new Array(size);
let values = new Array(size);
for (let i = 0; i < size; i++) {
keys[i] = this.popValue();
values[i] = this.popValue();
}
this.pushValue([keys, values]);
}
/// Utilities
push(args) {
while (args[args.length - 1] === null) {
args.pop();
}
this.currentBlock.push(args);
}
pushValue(val) {
this.values.push(val);
}
popValue() {
assert(this.values.length, "No expression found on stack");
return this.values.pop();
}
}