UNPKG

ember-material-icons

Version:

Google Material icons for your ember-cli app

470 lines (380 loc) 12.3 kB
import TemplateVisitor, { SymbolTable } from "./template-visitor"; import JavaScriptCompiler, { Template } from "./javascript-compiler"; import { getAttrNamespace } from "@glimmer/util"; import { assert } from "@glimmer/util"; import { TemplateMeta } from "@glimmer/wire-format"; export interface CompileOptions<T extends TemplateMeta> { meta?: T; } function isTrustedValue(value) { return value.escaped !== undefined && !value.escaped; } export default class TemplateCompiler<T extends TemplateMeta> { static compile<T>(options: CompileOptions<T>, ast): Template<T> { let templateVisitor = new TemplateVisitor(); templateVisitor.visit(ast); let compiler = new TemplateCompiler(options); let opcodes = compiler.process(templateVisitor.actions); return JavaScriptCompiler.process<T>(opcodes, options.meta); } private options: CompileOptions<T>; private templateId = 0; private templateIds: number[] = []; private symbols: SymbolTable = null; private opcodes: any[] = []; private includeMeta = false; constructor(options: CompileOptions<T>) { this.options = options || {}; } process(actions): any[] { actions.forEach(([name, ...args]) => { if (!this[name]) { throw new Error(`Unimplemented ${name} on TemplateCompiler`); } this[name](...args); }); return this.opcodes; } startProgram(program) { this.opcode('startProgram', program, program); } endProgram() { this.opcode('endProgram', null); } startBlock(program) { this.symbols = program[0].symbols; this.templateId++; this.opcode('startBlock', program, program); } endBlock() { this.symbols = null; this.templateIds.push(this.templateId - 1); this.opcode('endBlock', null); } text([action]) { this.opcode('text', action, action.chars); } comment([action]) { this.opcode('comment', action, action.value); } openElement([action]) { this.opcode('openElement', action, action.tag, action.blockParams); for (let i = 0; i < action.attributes.length; i++) { this.attribute([action.attributes[i]]); } for (let i = 0; i < action.modifiers.length; i++) { this.modifier([action.modifiers[i]]); } this.opcode('flushElement', null); } closeElement([action]) { this.opcode('closeElement', null, action.tag); } attribute([action]) { let { name, value } = action; let namespace = getAttrNamespace(name); let isStatic = this.prepareAttributeValue(value); if (name.charAt(0) === '@') { // Arguments if (isStatic) { this.opcode('staticArg', action, name); } else if (action.value.type === 'MustacheStatement') { this.opcode('dynamicArg', action, name); } else { this.opcode('dynamicArg', action, name); } } else { let isTrusting = isTrustedValue(value); if (isStatic) { this.opcode('staticAttr', action, name, namespace); } else if (isTrusting) { this.opcode('trustingAttr', action, name, namespace); } else if (action.value.type === 'MustacheStatement') { this.opcode('dynamicAttr', action, name); } else { this.opcode('dynamicAttr', action, name, namespace); } } } modifier([action]) { assertIsSimplePath(action, 'modifier'); let { path: { parts } } = action; this.prepareHelper(action); this.opcode('modifier', action, parts); } mustache([action]) { if (isYield(action)) { let to = assertValidYield(action); this.yield(to, action); } else if (isPartial(action)) { let params = assertValidPartial(action); this.partial(params, action); }else if (isDebugger(action)) { assertValidDebuggerUsage(action); this.debugger('debugger', action); } else { this.mustacheExpression(action); this.opcode('append', action, !action.escaped); } } block([action/*, index, count*/]) { this.prepareHelper(action); let templateId = this.templateIds.pop(); let inverseId = action.inverse === null ? null : this.templateIds.pop(); this.opcode('block', action, action.path.parts, templateId, inverseId); } /// Internal actions, not found in the original processed actions arg([path]) { let { parts } = path; this.opcode('arg', path, parts); } mustacheExpression(expr) { if (isBuiltInHelper(expr)) { this.builtInHelper(expr); } else if (isLiteral(expr)) { this.opcode('literal', expr, expr.path.value); } else if (isArg(expr)) { this.arg([expr.path]); } else if (isHelperInvocation(expr)) { this.prepareHelper(expr); this.opcode('helper', expr, expr.path.parts); } else if (!isSimplePath(expr) || isSelfGet(expr) || isLocalVariable(expr, this.symbols)) { this.opcode('get', expr, expr.path.parts); } else { this.opcode('unknown', expr, expr.path.parts); } } /// Internal Syntax yield(to: string, action) { this.prepareParams(action.params); this.opcode('yield', action, to); } debugger(name, action) { this.opcode('debugger', null); } hasBlock(name: string, action) { this.opcode('hasBlock', action, name); } hasBlockParams(name: string, action) { this.opcode('hasBlockParams', action, name); } partial(params, action) { this.prepareParams(action.params); this.opcode('partial', action); } builtInHelper(expr) { if (isHasBlock(expr)) { let name = assertValidHasBlockUsage(expr.path.original, expr); this.hasBlock(name, expr); } else if (isHasBlockParams(expr)) { let name = assertValidHasBlockUsage(expr.path.original, expr); this.hasBlockParams(name, expr); } } /// Expressions, invoked recursively from prepareParams and prepareHash SubExpression(expr) { if (isBuiltInHelper(expr)) { this.builtInHelper(expr); } else { this.prepareHelper(expr); this.opcode('helper', expr, expr.path.parts); } } PathExpression(expr) { if (expr.data) { this.arg([expr]); } else { this.opcode('get', expr, expr.parts); } } StringLiteral(action) { this.opcode('literal', null, action.value); } BooleanLiteral(action) { this.opcode('literal', null, action.value); } NumberLiteral(action) { this.opcode('literal', null, action.value); } NullLiteral(action) { this.opcode('literal', null, action.value); } UndefinedLiteral(action) { this.opcode('literal', null, action.value); } /// Utilities opcode(name, action, ...args) { let opcode = [name, ...args]; if (this.includeMeta && action) { opcode.push(this.meta(action)); } this.opcodes.push(opcode); } prepareHelper(expr) { assertIsSimplePath(expr, 'helper'); let { params, hash } = expr; this.prepareHash(hash); this.prepareParams(params); } preparePath(path) { this.opcode('literal', path, path.parts); } prepareParams(params) { if (!params.length) { this.opcode('literal', null, null); return; } for (let i = params.length - 1; i >= 0; i--) { let param = params[i]; assert(this[param.type], `Unimplemented ${param.type} on TemplateCompiler`); this[param.type](param); } this.opcode('prepareArray', null, params.length); } prepareHash(hash) { let pairs = hash.pairs; if (!pairs.length) { this.opcode('literal', null, null); return; } for (let i = pairs.length - 1; i >= 0; i--) { let { key, value } = pairs[i]; assert(this[value.type], `Unimplemented ${value.type} on TemplateCompiler`); this[value.type](value); this.opcode('literal', null, key); } this.opcode('prepareObject', null, pairs.length); } prepareAttributeValue(value) { // returns the static value if the value is static switch (value.type) { case 'TextNode': this.opcode('literal', value, value.chars); return true; case 'MustacheStatement': this.attributeMustache([value]); return false; case 'ConcatStatement': this.prepareConcatParts(value.parts); this.opcode('concat', value); return false; } } prepareConcatParts(parts) { for (let i = parts.length - 1; i >= 0; i--) { let part = parts[i]; if (part.type === 'MustacheStatement') { this.attributeMustache([part]); } else if (part.type === 'TextNode') { this.opcode('literal', null, part.chars); } } this.opcode('prepareArray', null, parts.length); } attributeMustache([action]) { this.mustacheExpression(action); } meta(node) { let loc = node.loc; if (!loc) { return []; } let { source, start, end } = loc; return [ 'loc', [source || null, [start.line, start.column], [end.line, end.column]] ]; } } function isHelperInvocation(mustache) { return (mustache.params && mustache.params.length > 0) || (mustache.hash && mustache.hash.pairs.length > 0); } function isSimplePath(mustache) { let { parts } = mustache.path; return parts.length === 1; } function isSelfGet(mustache) { let { parts } = mustache.path; return parts[0] === null; } function isLocalVariable(mustache, symbols) { let { parts } = mustache.path; return parts.length === 1 && symbols && symbols.hasLocalVariable(parts[0]); } function isYield({ path }) { return path.original === 'yield'; } function isPartial({ path }) { return path.original === 'partial'; } function isDebugger({ path }) { return path.original === 'debugger'; } function isArg({ path }) { return path.data; } function isLiteral({ path }) { return path.type === 'StringLiteral' || path.type === 'BooleanLiteral' || path.type === 'NumberLiteral' || path.type === 'NullLiteral' || path.type === 'UndefinedLiteral'; } function isHasBlock({ path }) { return path.original === 'has-block'; } function assertIsSimplePath(expr, context) { if (!isSimplePath(expr)) { let { path: { original }, loc: { start: { line } } } = expr; throw new Error(`\`${original}\` is not a valid name for a ${context} on line ${line}.`); } } function isHasBlockParams({ path }) { return path.original === 'has-block-params'; } function isBuiltInHelper(expr) { return isHasBlock(expr) || isHasBlockParams(expr); } function assertValidYield({ hash }): string { let pairs = hash.pairs; if ((pairs.length === 1 && pairs[0].key !== 'to') || pairs.length > 1) { throw new Error(`yield only takes a single named argument: 'to'`); } else if (pairs.length === 1 && pairs[0].value.type !== 'StringLiteral') { throw new Error(`you can only yield to a literal value`); } else if (pairs.length === 0) { return 'default'; } else { return pairs[0].value.value; } } function assertValidPartial({ params, hash, escaped, loc }) /* : expr */ { if (params && params.length !== 1) { throw new Error(`Partial found with no arguments. You must specify a template name. (on line ${loc.start.line})`); } else if (hash && hash.pairs.length > 0) { throw new Error(`partial does not take any named arguments (on line ${loc.start.line})`); } else if (!escaped) { throw new Error(`{{{partial ...}}} is not supported, please use {{partial ...}} instead (on line ${loc.start.line})`); } return params; } function assertValidHasBlockUsage(type, { params, hash, loc }): string { if (hash && hash.pairs.length > 0) { throw new Error(`${type} does not take any named arguments`); } if (params.length === 0) { return 'default'; } else if (params.length === 1) { if (params[0].type === 'StringLiteral') { return params[0].value; } else { throw new Error(`you can only yield to a literal value (on line ${loc.start.line})`); } } else { throw new Error(`${type} only takes a single positional argument (on line ${loc.start.line})`); } } function assertValidDebuggerUsage({ params, hash }) { if (hash && hash.pairs.length > 0) { throw new Error(`debugger does not take any named arguments`); } if (params.length === 0) { return 'default'; } else { throw new Error(`debugger does not take any positional arguments`); } }