UNPKG

ember-material-icons

Version:

Google Material icons for your ember-cli app

547 lines (425 loc) 14.5 kB
import * as content from './content'; import * as vm from './vm'; import { Insertion } from '../../upsert'; import { CompiledGetBlock, CompiledGetBlockBySymbol, CompiledInPartialGetBlock } from '../../compiled/expressions/has-block'; import { Option, Stack, Dict, Opaque, dict, expect } from '@glimmer/util'; import { expr } from '../../syntax/functions'; import { Constants } from '../../opcodes'; import { CompiledArgs } from '../expressions/args'; import { CompiledExpression } from '../expressions'; import { ComponentDefinition } from '../../component/interfaces'; import { PartialDefinition } from '../../partial'; import Environment, { Program } from '../../environment'; import { SymbolTable } from '@glimmer/interfaces'; import { ComponentBuilder as IComponentBuilder } from '../../opcode-builder'; import { ComponentBuilder } from '../../compiler'; import { BaselineSyntax, InlineBlock, Template } from '../../scanner'; import { OpcodeName as Op, ConstantArray, ConstantOther, ConstantExpression, ConstantBlock } from '../../opcodes'; export interface CompilesInto<E> { compile(builder: OpcodeBuilder): E; } export type RepresentsExpression = BaselineSyntax.AnyExpression | CompiledExpression<Opaque>; export type Represents<E> = CompilesInto<E> | E; export type Label = string; export interface SymbolLookup { symbolTable: SymbolTable; } type TargetOpcode = Op.Jump | Op.JumpIf | Op.JumpUnless | Op.NextIter; type RangeOpcode = Op.Enter | Op.EnterList | Op.EnterWithKey; class Labels { labels = dict<number>(); jumps: { at: number, target: string, Target: TargetOpcode }[] = []; ranges: { at: number, start: string, end: string, Range: RangeOpcode }[] = []; label(name: string, index: number) { this.labels[name] = index; } jump(at: number, Target: TargetOpcode, target: string) { this.jumps.push({ at, target, Target }); } range(at: number, Range: RangeOpcode, start: string, end: string) { this.ranges.push({ at, start, end, Range }); } patch(opcodes: Program): void { for (let i = 0; i < this.jumps.length; i++) { let { at, target, Target } = this.jumps[i]; opcodes.set(at, Target, this.labels[target]); } for (let i = 0; i < this.ranges.length; i++) { let { at, start, end, Range } = this.ranges[i]; opcodes.set(at, Range, this.labels[start], this.labels[end] - 1); } } } export abstract class BasicOpcodeBuilder implements SymbolLookup { private labelsStack = new Stack<Labels>(); public constants: Constants; public start: number; constructor(public symbolTable: SymbolTable, public env: Environment, public program: Program) { this.constants = env.constants; this.start = program.next; } abstract compile<E>(expr: Represents<E>): E; abstract compileExpression(expr: RepresentsExpression): CompiledExpression<Opaque>; public get end(): number { return this.program.next; } private get pos() { return this.program.current; } private get nextPos() { return this.program.next; } protected opcode(name: Op, op1?: number, op2?: number, op3?: number) { this.push(name, op1, op2, op3); } push(type: number, op1 = 0, op2 = 0, op3 = 0) { this.program.push(type, op1, op2, op3); } // helpers private get labels(): 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); } // partials putPartialDefinition(_definition: PartialDefinition<Opaque>) { let definition = this.constants.other(_definition); this.opcode(Op.PutPartial, definition); } putDynamicPartialDefinition() { this.opcode(Op.PutDynamicPartial, this.constants.other(this.symbolTable)); } evaluatePartial() { this.opcode(Op.EvaluatePartial, this.constants.other(this.symbolTable), this.constants.other(dict())); } // components putComponentDefinition(definition: ComponentDefinition<Opaque>) { this.opcode(Op.PutComponent, this.other(definition)); } putDynamicComponentDefinition() { this.opcode(Op.PutDynamicComponent); } openComponent(args: Represents<CompiledArgs>, shadow?: InlineBlock) { this.opcode(Op.OpenComponent, this.args(args), shadow ? this.block(shadow) : 0); } didCreateElement() { this.opcode(Op.DidCreateElement); } shadowAttributes() { this.opcode(Op.ShadowAttributes); this.opcode(Op.CloseBlock); } didRenderLayout() { this.opcode(Op.DidRenderLayout); } closeComponent() { this.opcode(Op.CloseComponent); } // content dynamicContent(Opcode: content.AppendDynamicOpcode<Insertion>) { this.opcode(Op.DynamicContent, this.other(Opcode)); } cautiousAppend() { this.dynamicContent(new content.OptimizedCautiousAppendOpcode()); } trustingAppend() { this.dynamicContent(new content.OptimizedTrustingAppendOpcode()); } guardedCautiousAppend(expression: RepresentsExpression) { this.dynamicContent(new content.GuardedCautiousAppendOpcode(this.compileExpression(expression), this.symbolTable)); } guardedTrustingAppend(expression: RepresentsExpression) { this.dynamicContent(new content.GuardedTrustingAppendOpcode(this.compileExpression(expression), this.symbolTable)); } // dom text(text: string) { this.opcode(Op.Text, this.constants.string(text)); } openPrimitiveElement(tag: string) { this.opcode(Op.OpenElement, this.constants.string(tag)); } openComponentElement(tag: string) { this.opcode(Op.OpenComponentElement, this.constants.string(tag)); } openDynamicPrimitiveElement() { this.opcode(Op.OpenDynamicElement); } flushElement() { this.opcode(Op.FlushElement); } closeElement() { this.opcode(Op.CloseElement); } staticAttr(_name: string, _namespace: Option<string>, _value: string) { let name = this.constants.string(_name); let namespace = _namespace ? this.constants.string(_namespace) : 0; let value = this.constants.string(_value); this.opcode(Op.StaticAttr, name, value, namespace); } dynamicAttrNS(_name: string, _namespace: string, trusting: boolean) { let name = this.constants.string(_name); let namespace = this.constants.string(_namespace); this.opcode(Op.DynamicAttrNS, name, namespace, (trusting as any)|0); } dynamicAttr(_name: string, trusting: boolean) { let name = this.constants.string(_name); this.opcode(Op.DynamicAttr, name, (trusting as any)|0); } comment(_comment: string) { let comment = this.constants.string(_comment); this.opcode(Op.Comment, comment); } modifier(_name: string, _args: Represents<CompiledArgs>) { let args = this.constants.expression(this.compile(_args)); let _modifierManager = this.env.lookupModifier(_name, this.symbolTable); let modifierManager = this.constants.other(_modifierManager); let name = this.constants.string(_name); this.opcode(Op.Modifier, name, modifierManager, args); } // lists putIterator() { this.opcode(Op.PutIterator); } enterList(start: string, end: string) { this.push(Op.EnterList); this.labels.range(this.pos, Op.EnterList, start, end); } exitList() { this.opcode(Op.ExitList); } enterWithKey(start: string, end: string) { this.push(Op.EnterWithKey); this.labels.range(this.pos, Op.EnterWithKey, start, end); } nextIter(end: string) { this.push(Op.NextIter); this.labels.jump(this.pos, Op.NextIter, end); } // vm openBlock(_args: Represents<CompiledArgs>, _inner: CompiledGetBlock) { let args = this.constants.expression(this.compile(_args)); let inner = this.constants.other(_inner); this.opcode(Op.OpenBlock, inner, args); } closeBlock() { this.opcode(Op.CloseBlock); } pushRemoteElement() { this.opcode(Op.PushRemoteElement); } popRemoteElement() { this.opcode(Op.PopRemoteElement); } popElement() { this.opcode(Op.PopElement); } label(name: string) { this.labels.label(name, this.nextPos); } pushChildScope() { this.opcode(Op.PushChildScope); } popScope() { this.opcode(Op.PopScope); } pushDynamicScope() { this.opcode(Op.PushDynamicScope); } popDynamicScope() { this.opcode(Op.PopDynamicScope); } putNull() { this.opcode(Op.Put, this.constants.NULL_REFERENCE); } putValue(_expression: RepresentsExpression) { let expr = this.constants.expression(this.compileExpression(_expression)); this.opcode(Op.EvaluatePut, expr); } putArgs(_args: Represents<CompiledArgs>) { let args = this.constants.expression(this.compile(_args)); this.opcode(Op.PutArgs, args); } bindDynamicScope(_names: string[]) { this.opcode(Op.BindDynamicScope, this.names(_names)); } bindPositionalArgs(_names: string[], _symbols: number[]) { this.opcode(Op.BindPositionalArgs, this.names(_names), this.symbols(_symbols)); } bindNamedArgs(_names: string[], _symbols: number[]) { this.opcode(Op.BindNamedArgs, this.names(_names), this.symbols(_symbols)); } bindBlocks(_names: string[], _symbols: number[]) { this.opcode(Op.BindBlocks, this.names(_names), this.symbols(_symbols)); } enter(enter: string, exit: string) { this.push(Op.Enter); this.labels.range(this.pos, Op.Enter, enter, exit); } exit() { this.opcode(Op.Exit); } evaluate(_block: InlineBlock) { let block = this.constants.block(_block); this.opcode(Op.Evaluate, block); } test(testFunc: 'const' | 'simple' | 'environment' | vm.TestFunction) { let _func: vm.TestFunction; 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.opcode(Op.Test, func); } jump(target: string) { this.push(Op.Jump); this.labels.jump(this.pos, Op.Jump, target); } jumpIf(target: string) { this.push(Op.JumpIf); this.labels.jump(this.pos, Op.JumpIf, target); } jumpUnless(target: string) { this.push(Op.JumpUnless); this.labels.jump(this.pos, Op.JumpUnless, target); } protected names(_names: string[]): ConstantArray { let names = _names.map(n => this.constants.string(n)); return this.constants.array(names); } protected symbols(symbols: number[]): ConstantArray { return this.constants.array(symbols); } protected other(value: Opaque): ConstantOther { return this.constants.other(value); } protected args(args: Represents<CompiledArgs>): ConstantExpression { return this.constants.expression(this.compile(args)); } protected block(block: InlineBlock): ConstantBlock { return this.constants.block(block); } } function isCompilableExpression<E>(expr: Represents<E>): expr is CompilesInto<E> { return expr && typeof expr['compile'] === 'function'; } export default class OpcodeBuilder extends BasicOpcodeBuilder { public component: IComponentBuilder; constructor(symbolTable: SymbolTable, env: Environment, program: Program = env.program) { super(symbolTable, env, program); this.component = new ComponentBuilder(this); } compile<E>(expr: Represents<E>): E { if (isCompilableExpression(expr)) { return expr.compile(this); } else { return expr; } } compileExpression(expression: RepresentsExpression): CompiledExpression<Opaque> { if (expression instanceof CompiledExpression) { return expression; } else { return expr(expression, this); } } bindPositionalArgsForLocals(locals: Dict<number>) { let names = Object.keys(locals); let symbols: number[] = new Array(names.length); //Object.keys(locals).map(name => locals[name]); for (let i = 0; i < names.length; i++) { symbols[i] = locals[names[i]]; } this.opcode(Op.BindPositionalArgs, this.symbols(symbols)); } preludeForLayout(layout: Template) { let symbols = layout.symbolTable.getSymbols(); if (symbols.named) { let named = symbols.named; let namedNames = Object.keys(named); let namedSymbols = namedNames.map(n => named[n]); this.opcode(Op.BindNamedArgs, this.names(namedNames), this.symbols(namedSymbols)); } this.opcode(Op.BindCallerScope); if (symbols.yields) { let yields = symbols.yields; let yieldNames = Object.keys(yields); let yieldSymbols = yieldNames.map(n => yields[n]); this.opcode(Op.BindBlocks, this.names(yieldNames), this.symbols(yieldSymbols)); } if (symbols.partialArgs) { this.opcode(Op.BindPartialArgs, symbols.partialArgs); } } yield(args: Represents<CompiledArgs>, to: string) { let yields: Option<number>, partial: Option<number>; let inner: CompiledGetBlock; if (yields = this.symbolTable.getSymbol('yields', to)) { inner = new CompiledGetBlockBySymbol(yields, to); } else if (partial = this.symbolTable.getPartialArgs()) { inner = new CompiledInPartialGetBlock(partial, to); } else { throw new Error('[BUG] ${to} is not a valid block name.'); } this.openBlock(args, inner); this.closeBlock(); } // TODO // come back to this labelled(args: Option<Represents<CompiledArgs>>, callback: BlockCallback) { if (args) this.putArgs(args); this.startLabels(); this.enter('BEGIN', 'END'); this.label('BEGIN'); callback(this, 'BEGIN', 'END'); this.label('END'); this.exit(); this.stopLabels(); } // TODO // come back to this iter(callback: BlockCallback) { this.startLabels(); this.enterList('BEGIN', 'END'); this.label('ITER'); this.nextIter('BREAK'); this.enterWithKey('BEGIN', 'END'); this.label('BEGIN'); callback(this, 'BEGIN', 'END'); this.label('END'); this.exit(); this.jump('ITER'); this.label('BREAK'); this.exitList(); this.stopLabels(); } // TODO // come back to this unit(callback: (builder: OpcodeBuilder) => void) { this.startLabels(); callback(this); this.stopLabels(); } } export type BlockCallback = (dsl: OpcodeBuilder, BEGIN: Label, END: Label) => void;