UNPKG

ember-legacy-class-transform

Version:
280 lines 8.54 kB
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(); } }