UNPKG

ember-material-icons

Version:

Google Material icons for your ember-cli app

732 lines (595 loc) 23.1 kB
import * as WireFormat from '@glimmer/wire-format'; import OpcodeBuilder from '../compiled/opcodes/builder'; import { CompiledExpression } from '../compiled/expressions'; import CompiledValue from '../compiled/expressions/value'; import CompiledHasBlock, { CompiledHasBlockParams } from '../compiled/expressions/has-block'; import { BaselineSyntax } from '../scanner'; import { LOGGER, Opaque, Option, dict, assert, unwrap, unreachable } from '@glimmer/util'; import CompiledLookup, { CompiledSelf, CompiledSymbol, CompiledInPartialName } from '../compiled/expressions/lookups'; import CompiledHelper from '../compiled/expressions/helper'; import CompiledConcat from '../compiled/expressions/concat'; import { COMPILED_EMPTY_POSITIONAL_ARGS, COMPILED_EMPTY_NAMED_ARGS, EMPTY_BLOCKS, CompiledArgs, CompiledPositionalArgs, CompiledNamedArgs, Blocks as BlocksSyntax } from '../compiled/expressions/args'; import { CompiledGetBlockBySymbol, CompiledInPartialGetBlock } from '../compiled/expressions/has-block'; import { PublicVM as VM } from '../vm'; import AppendVM from '../vm/append'; import { CompiledFunctionExpression } from '../compiled/expressions/function'; const { defaultBlock, params, hash } = BaselineSyntax.NestedBlock; export type SexpExpression = BaselineSyntax.AnyExpression & { 0: number }; export type Syntax = SexpExpression | BaselineSyntax.AnyStatement; export type CompilerFunction<T extends Syntax, U> = ((sexp: T, builder: OpcodeBuilder) => U); export type Name = BaselineSyntax.AnyStatement[0]; export type debugGet = ((path: string) => any); export interface DebugContext { context: Opaque; get: debugGet; } export type debugCallback = ((context: Opaque, get: debugGet) => DebugContext); function debugCallback(context: Opaque, get: debugGet) { console.info('Use `context`, and `get(<path>)` to debug this template.'); /* tslint:disable */ debugger; /* tslint:enable */ return { context, get }; } function getter(vm: VM, builder: OpcodeBuilder) { return (path: string) => { let parts = path.split('.') as any; if (parts[0] === 'this') { parts[0] = null; } return compileRef(parts, builder).evaluate(vm as AppendVM); }; } let callback = debugCallback; // For testing purposes export function setDebuggerCallback(cb: debugCallback) { callback = cb; } export function resetDebuggerCallback() { callback = debugCallback; } export class Compilers<T extends Syntax, CompileTo> { private names = dict<number>(); private funcs: CompilerFunction<T, CompileTo>[] = []; add(name: number, func: CompilerFunction<T, CompileTo>): void { this.funcs.push(func); this.names[name] = this.funcs.length - 1; } compile(sexp: T, builder: OpcodeBuilder): CompileTo { let name: number = sexp[0]; let index = this.names[name]; let func = this.funcs[index]; assert(!!func, `expected an implementation for ${sexp[0]}`); return func(sexp, builder); } } import S = WireFormat.Statements; let { Ops } = WireFormat; export const STATEMENTS = new Compilers<BaselineSyntax.AnyStatement, void>(); STATEMENTS.add(Ops.Text, (sexp: S.Text, builder: OpcodeBuilder) => { builder.text(sexp[1]); }); STATEMENTS.add(Ops.Comment, (sexp: S.Comment, builder: OpcodeBuilder) => { builder.comment(sexp[1]); }); STATEMENTS.add(Ops.CloseElement, (_sexp, builder: OpcodeBuilder) => { LOGGER.trace('close-element statement'); builder.closeElement(); }); STATEMENTS.add(Ops.FlushElement, (_sexp, builder: OpcodeBuilder) => { builder.flushElement(); }); STATEMENTS.add(Ops.Modifier, (sexp: S.Modifier, builder: OpcodeBuilder) => { let [, path, params, hash] = sexp; let args = compileArgs(params, hash, builder); if (builder.env.hasModifier(path[0], builder.symbolTable)) { builder.modifier(path[0], args); } else { throw new Error(`Compile Error ${path.join('.')} is not a modifier: Helpers may not be used in the element form.`); } }); STATEMENTS.add(Ops.StaticAttr, (sexp: S.StaticAttr, builder: OpcodeBuilder) => { let [, name, value, namespace] = sexp; builder.staticAttr(name, namespace, value as string); }); STATEMENTS.add(Ops.AnyDynamicAttr, (sexp: BaselineSyntax.AnyDynamicAttr, builder: OpcodeBuilder) => { let [, name, value, namespace, trusting] = sexp; builder.putValue(value); if (namespace) { builder.dynamicAttrNS(name, namespace, trusting); } else { builder.dynamicAttr(name, trusting); } }); STATEMENTS.add(Ops.OpenElement, (sexp: BaselineSyntax.OpenPrimitiveElement, builder: OpcodeBuilder) => { LOGGER.trace('open-element statement'); builder.openPrimitiveElement(sexp[1]); }); STATEMENTS.add(Ops.OptimizedAppend, (sexp: BaselineSyntax.OptimizedAppend, builder: OpcodeBuilder) => { let [, value, trustingMorph] = sexp; let { inlines } = builder.env.macros(); let returned = inlines.compile(sexp, builder) || value; if (returned === true) return; builder.putValue(returned[1]); if (trustingMorph) { builder.trustingAppend(); } else { builder.cautiousAppend(); } }); STATEMENTS.add(Ops.UnoptimizedAppend, (sexp: BaselineSyntax.UnoptimizedAppend, builder) => { let [, value, trustingMorph] = sexp; let { inlines } = builder.env.macros(); let returned = inlines.compile(sexp, builder) || value; if (returned === true) return; if (trustingMorph) { builder.guardedTrustingAppend(returned[1]); } else { builder.guardedCautiousAppend(returned[1]); } }); STATEMENTS.add(Ops.NestedBlock, (sexp: BaselineSyntax.NestedBlock, builder: OpcodeBuilder) => { let { blocks } = builder.env.macros(); blocks.compile(sexp, builder); }); STATEMENTS.add(Ops.ScannedBlock, (sexp: BaselineSyntax.ScannedBlock, builder) => { let [, path, params, hash, template, inverse] = sexp; let templateBlock = template && template.scan(); let inverseBlock = inverse && inverse.scan(); let { blocks } = builder.env.macros(); blocks.compile([Ops.NestedBlock, path, params, hash, templateBlock, inverseBlock], builder); }); // this fixes an issue with Ember versions using glimmer-vm@0.22 when attempting // to use nested web components. This is obviously not correct for angle bracket components // but since no consumers are currently using them with glimmer@0.22.x we are hard coding // support to just use the fallback case. STATEMENTS.add(Ops.Component, (sexp: WireFormat.Statements.Component, builder) => { let [, tag, component ] = sexp; let { attrs, statements } = component; builder.openPrimitiveElement(tag); for (let i = 0; i < attrs.length; i++) { STATEMENTS.compile(attrs[i], builder); } builder.flushElement(); for (let i = 0; i < statements.length; i++) { STATEMENTS.compile(statements[i], builder); } builder.closeElement(); }); STATEMENTS.add(Ops.ScannedComponent, (sexp: BaselineSyntax.ScannedComponent, builder) => { let [, tag, attrs, rawArgs, rawBlock] = sexp; let block = rawBlock && rawBlock.scan(); let args = compileBlockArgs(null, rawArgs, { default: block, inverse: null }, builder); let definition = builder.env.getComponentDefinition(tag, builder.symbolTable); builder.putComponentDefinition(definition); builder.openComponent(args, attrs.scan()); builder.closeComponent(); }); STATEMENTS.add(Ops.StaticPartial, (sexp: BaselineSyntax.StaticPartial, builder) => { let [, name] = sexp; if (!builder.env.hasPartial(name, builder.symbolTable)) { throw new Error(`Compile Error: Could not find a partial named "${name}"`); } let definition = builder.env.lookupPartial(name, builder.symbolTable); builder.putPartialDefinition(definition); builder.evaluatePartial(); }); STATEMENTS.add(Ops.DynamicPartial, (sexp: BaselineSyntax.DynamicPartial, builder) => { let [, name] = sexp; builder.startLabels(); builder.putValue(name); builder.test('simple'); builder.enter('BEGIN', 'END'); builder.label('BEGIN'); builder.jumpUnless('END'); builder.putDynamicPartialDefinition(); builder.evaluatePartial(); builder.label('END'); builder.exit(); builder.stopLabels(); }); STATEMENTS.add(Ops.Yield, function(this: undefined, sexp: WireFormat.Statements.Yield, builder) { let [, to, params] = sexp; let args = compileArgs(params, null, builder); builder.yield(args, to); }); STATEMENTS.add(Ops.Debugger, (sexp: BaselineSyntax.Debugger, builder: OpcodeBuilder) => { builder.putValue([Ops.Function, (vm: VM) => { let context = vm.getSelf().value(); let get = (path: string) => { return getter(vm, builder)(path).value(); }; callback(context, get); }]); return sexp; }); let EXPRESSIONS = new Compilers<SexpExpression, CompiledExpression<Opaque>>(); import E = WireFormat.Expressions; import C = WireFormat.Core; export function expr(expression: BaselineSyntax.AnyExpression, builder: OpcodeBuilder): CompiledExpression<Opaque> { if (Array.isArray(expression)) { return EXPRESSIONS.compile(expression, builder); } else { return new CompiledValue(expression); } } EXPRESSIONS.add(Ops.Unknown, (sexp: E.Unknown, builder: OpcodeBuilder) => { let path = sexp[1]; let name = path[0]; if (builder.env.hasHelper(name, builder.symbolTable)) { return new CompiledHelper(name, builder.env.lookupHelper(name, builder.symbolTable), CompiledArgs.empty(), builder.symbolTable); } else { return compileRef(path, builder); } }); EXPRESSIONS.add(Ops.Concat, ((sexp: E.Concat, builder: OpcodeBuilder) => { let params = sexp[1].map(p => expr(p, builder)); return new CompiledConcat(params); }) as any); EXPRESSIONS.add(Ops.Function, (sexp: BaselineSyntax.FunctionExpression, builder: OpcodeBuilder) => { return new CompiledFunctionExpression(sexp[1], builder.symbolTable); }); EXPRESSIONS.add(Ops.Helper, (sexp: E.Helper, builder: OpcodeBuilder) => { let { env, symbolTable } = builder; let [, [name], params, hash] = sexp; if (env.hasHelper(name, symbolTable)) { let args = compileArgs(params, hash, builder); return new CompiledHelper(name, env.lookupHelper(name, symbolTable), args, symbolTable); } else { throw new Error(`Compile Error: ${name} is not a helper`); } }); EXPRESSIONS.add(Ops.Get, (sexp: E.Get, builder: OpcodeBuilder) => { return compileRef(sexp[1], builder); }); EXPRESSIONS.add(Ops.Undefined, (_sexp, _builder) => { return new CompiledValue(undefined); }); EXPRESSIONS.add(Ops.Arg, (sexp: E.Arg, builder: OpcodeBuilder) => { let [, parts] = sexp; let head = parts[0]; let named: Option<number>, partial: Option<number>; if (named = builder.symbolTable.getSymbol('named', head)) { let path = parts.slice(1); let inner = new CompiledSymbol(named, head); return CompiledLookup.create(inner, path); } else if (partial = builder.symbolTable.getPartialArgs()) { let path = parts.slice(1); let inner = new CompiledInPartialName(partial, head); return CompiledLookup.create(inner, path); } else { throw new Error(`[BUG] @${parts.join('.')} is not a valid lookup path.`); } }); EXPRESSIONS.add(Ops.HasBlock, (sexp: E.HasBlock, builder) => { let blockName = sexp[1]; let yields: Option<number>, partial: Option<number>; if (yields = builder.symbolTable.getSymbol('yields', blockName)) { let inner = new CompiledGetBlockBySymbol(yields, blockName); return new CompiledHasBlock(inner); } else if (partial = builder.symbolTable.getPartialArgs()) { let inner = new CompiledInPartialGetBlock(partial, blockName); return new CompiledHasBlock(inner); } else { throw new Error('[BUG] ${blockName} is not a valid block name.'); } }); EXPRESSIONS.add(Ops.HasBlockParams, (sexp: E.HasBlockParams, builder) => { let blockName = sexp[1]; let yields: Option<number>, partial: Option<number>; if (yields = builder.symbolTable.getSymbol('yields', blockName)) { let inner = new CompiledGetBlockBySymbol(yields, blockName); return new CompiledHasBlockParams(inner); } else if (partial = builder.symbolTable.getPartialArgs()) { let inner = new CompiledInPartialGetBlock(partial, blockName); return new CompiledHasBlockParams(inner); } else { throw new Error('[BUG] ${blockName} is not a valid block name.'); } }); export function compileArgs(params: Option<WireFormat.Core.Params>, hash: Option<WireFormat.Core.Hash>, builder: OpcodeBuilder): CompiledArgs { let compiledParams = compileParams(params, builder); let compiledHash = compileHash(hash, builder); return CompiledArgs.create(compiledParams, compiledHash, EMPTY_BLOCKS); } export function compileBlockArgs(params: Option<WireFormat.Core.Params>, hash: Option<WireFormat.Core.Hash>, blocks: BlocksSyntax, builder: OpcodeBuilder): CompiledArgs { let compiledParams = compileParams(params, builder); let compiledHash = compileHash(hash, builder); return CompiledArgs.create(compiledParams, compiledHash, blocks); } export function compileBaselineArgs(args: BaselineSyntax.Args, builder: OpcodeBuilder): CompiledArgs { let [params, hash, _default, inverse] = args; return CompiledArgs.create(compileParams(params, builder), compileHash(hash, builder), { default: _default, inverse }); } function compileParams(params: Option<WireFormat.Core.Params>, builder: OpcodeBuilder): CompiledPositionalArgs { if (!params || params.length === 0) return COMPILED_EMPTY_POSITIONAL_ARGS; let compiled = new Array(params.length); for (let i = 0; i < params.length; i++) { compiled[i] = expr(params[i], builder); } return CompiledPositionalArgs.create(compiled); } function compileHash(hash: Option<WireFormat.Core.Hash>, builder: OpcodeBuilder): CompiledNamedArgs { if (!hash) return COMPILED_EMPTY_NAMED_ARGS; let [keys, values] = hash; if (keys.length === 0) return COMPILED_EMPTY_NAMED_ARGS; let compiled = new Array(values.length); for (let i = 0; i < values.length; i++) { compiled[i] = expr(values[i], builder); } return new CompiledNamedArgs(keys, compiled); } function compileRef(parts: string[], builder: OpcodeBuilder) { let head = parts[0]; let local: Option<number>; if (head === null) { // {{this.foo}} let inner = new CompiledSelf(); let path = parts.slice(1) as string[]; return CompiledLookup.create(inner, path); } else if (local = builder.symbolTable.getSymbol('local', head)) { let path = parts.slice(1) as string[]; let inner = new CompiledSymbol(local, head); return CompiledLookup.create(inner, path); } else { let inner = new CompiledSelf(); return CompiledLookup.create(inner, parts as string[]); } } export type NestedBlockSyntax = BaselineSyntax.NestedBlock; export type CompileBlockMacro = (sexp: NestedBlockSyntax, builder: OpcodeBuilder) => void; export class Blocks { private names = dict<number>(); private funcs: CompileBlockMacro[] = []; private missing: CompileBlockMacro; add(name: string, func: CompileBlockMacro) { this.funcs.push(func); this.names[name] = this.funcs.length - 1; } addMissing(func: CompileBlockMacro) { this.missing = func; } compile(sexp: BaselineSyntax.NestedBlock, builder: OpcodeBuilder): void { // assert(sexp[1].length === 1, 'paths in blocks are not supported'); let name: string = sexp[1][0]; 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(sexp, builder); assert(!!handled, `${name} not found, and the catch-all block handler didn't handle it`); } else { let func = this.funcs[index]; func(sexp, builder); } } } export const BLOCKS = new Blocks(); export type AppendSyntax = BaselineSyntax.OptimizedAppend | BaselineSyntax.UnoptimizedAppend; export type AppendMacro = (path: C.Path, params: Option<C.Params>, hash: Option<C.Hash>, builder: OpcodeBuilder) => ['expr', BaselineSyntax.AnyExpression] | true | false; export class Inlines { private names = dict<number>(); private funcs: AppendMacro[] = []; private missing: AppendMacro; add(name: string, func: AppendMacro) { this.funcs.push(func); this.names[name] = this.funcs.length - 1; } addMissing(func: AppendMacro) { this.missing = func; } compile(sexp: AppendSyntax, builder: OpcodeBuilder): ['expr', BaselineSyntax.AnyExpression] | true { 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 path: C.Path; let params: Option<C.Params>; let hash: Option<C.Hash>; if (value[0] === Ops.Helper) { path = value[1]; params = value[2]; hash = value[3]; } else if (value[0] === Ops.Unknown) { path = value[1]; params = hash = null; } else { return ['expr', value]; } if (path.length > 1 && !params && !hash) { return ['expr', value]; } let name = path[0]; let index = this.names[name]; if (index === undefined && this.missing) { let func = this.missing; let returned = func(path, params, hash, builder); return returned === false ? ['expr', value] : returned; } else if (index !== undefined) { let func = this.funcs[index]; let returned = func(path, 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: Blocks = new Blocks(), inlines: Inlines = new Inlines()): { blocks: Blocks, inlines: Inlines } { blocks.add('if', (sexp: BaselineSyntax.NestedBlock, builder: OpcodeBuilder) => { // PutArgs // Test(Environment) // Enter(BEGIN, END) // BEGIN: Noop // JumpUnless(ELSE) // Evaluate(default) // Jump(END) // ELSE: Noop // Evalulate(inverse) // END: Noop // Exit let [,, params, hash, _default, inverse] = sexp; let args = compileArgs(params, hash, builder); builder.putArgs(args); builder.test('environment'); builder.labelled(null, b => { if (_default && inverse) { b.jumpUnless('ELSE'); b.evaluate(_default); b.jump('END'); b.label('ELSE'); b.evaluate(inverse); } else if (_default) { b.jumpUnless('END'); b.evaluate(_default); } else { throw unreachable(); } }); }); blocks.add('-in-element', (sexp: BaselineSyntax.NestedBlock, builder: OpcodeBuilder) => { let block = defaultBlock(sexp); let args = compileArgs(params(sexp), null, builder); builder.putArgs(args); builder.test('simple'); builder.labelled(null, b => { b.jumpUnless('END'); b.pushRemoteElement(); b.evaluate(unwrap(block)); b.popRemoteElement(); }); }); blocks.add('-with-dynamic-vars', (sexp, builder) => { let block = defaultBlock(sexp); let args = compileArgs(params(sexp), hash(sexp), builder); builder.unit(b => { b.putArgs(args); b.pushDynamicScope(); b.bindDynamicScope(args.named.keys as string[]); b.evaluate(unwrap(block)); b.popDynamicScope(); }); }); blocks.add('unless', (sexp: BaselineSyntax.NestedBlock, builder: OpcodeBuilder) => { // PutArgs // Test(Environment) // Enter(BEGIN, END) // BEGIN: Noop // JumpUnless(ELSE) // Evaluate(default) // Jump(END) // ELSE: Noop // Evalulate(inverse) // END: Noop // Exit let [,, params, hash, _default, inverse] = sexp; let args = compileArgs(params, hash, builder); builder.putArgs(args); builder.test('environment'); builder.labelled(null, b => { if (_default && inverse) { b.jumpIf('ELSE'); b.evaluate(_default); b.jump('END'); b.label('ELSE'); b.evaluate( inverse); } else if (_default) { b.jumpIf('END'); b.evaluate(_default); } else { throw unreachable(); } }); }); blocks.add('with', (sexp: BaselineSyntax.NestedBlock, builder: OpcodeBuilder) => { // PutArgs // Test(Environment) // Enter(BEGIN, END) // BEGIN: Noop // JumpUnless(ELSE) // Evaluate(default) // Jump(END) // ELSE: Noop // Evalulate(inverse) // END: Noop // Exit let [,, params, hash, _default, inverse] = sexp; let args = compileArgs(params, hash, builder); builder.putArgs(args); builder.test('environment'); builder.labelled(null, b => { if (_default && inverse) { b.jumpUnless('ELSE'); b.evaluate(_default); b.jump('END'); b.label('ELSE'); b.evaluate(inverse); } else if (_default) { b.jumpUnless('END'); b.evaluate(_default); } else { throw unreachable(); } }); }); blocks.add('each', (sexp: BaselineSyntax.NestedBlock, builder: OpcodeBuilder) => { // Enter(BEGIN, END) // BEGIN: Noop // PutArgs // PutIterable // JumpUnless(ELSE) // EnterList(BEGIN2, END2) // ITER: Noop // NextIter(BREAK) // EnterWithKey(BEGIN2, END2) // BEGIN2: Noop // PushChildScope // Evaluate(default) // PopScope // END2: Noop // Exit // Jump(ITER) // BREAK: Noop // ExitList // Jump(END) // ELSE: Noop // Evalulate(inverse) // END: Noop // Exit let [,, params, hash, _default, inverse] = sexp; let args = compileArgs(params, hash, builder); builder.labelled(args, b => { b.putIterator(); if (inverse) { b.jumpUnless('ELSE'); } else { b.jumpUnless('END'); } b.iter(b => { b.evaluate(unwrap(_default)); }); if (inverse) { b.jump('END'); b.label('ELSE'); b.evaluate(inverse); } }); }); return { blocks, inlines }; }