import { CompiledProgram, CompiledBlock } from './compiled/blocks'; import { builder } from './compiler'; import OpcodeBuilder from './compiled/opcodes/builder'; import Environment from './environment'; import { Option } from '@glimmer/util'; import { SerializedTemplateBlock, TemplateMeta, SerializedBlock, Statement as SerializedStatement } from '@glimmer/wire-format'; import * as WireFormat from '@glimmer/wire-format'; import { entryPoint as entryPointTable, layout as layoutTable, block as blockTable } from './symbol-table'; import { Opaque, SymbolTable, ProgramSymbolTable } from '@glimmer/interfaces'; import { STATEMENTS } from './syntax/functions'; import { SPECIALIZE } from './syntax/specialize'; export type DeserializedStatement = WireFormat.Statement | WireFormat.Statements.Attribute | WireFormat.Statements.Argument; export function compileStatement(statement: BaselineSyntax.AnyStatement, builder: OpcodeBuilder) { let refined = SPECIALIZE.specialize(statement, builder.symbolTable); STATEMENTS.compile(refined, builder); } export class Template { constructor(public statements: BaselineSyntax.AnyStatement[], public symbolTable: SymbolTable) {} } export class Layout extends Template { public symbolTable: ProgramSymbolTable; } export class EntryPoint extends Template { private compiled: Option = null; public symbolTable: ProgramSymbolTable; compile(env: Environment): CompiledProgram { let compiled = this.compiled; if (!compiled) { let table = this.symbolTable; let b = builder(env, table); for (let i = 0; i < this.statements.length; i++) { let statement = this.statements[i]; let refined = SPECIALIZE.specialize(statement, table); STATEMENTS.compile(refined, b); } compiled = this.compiled = new CompiledProgram(b.start, b.end, this.symbolTable.size); } return compiled; } } export class InlineBlock extends Template { private compiled: Option = null; splat(builder: OpcodeBuilder) { let table = builder.symbolTable; let locals = table.getSymbols().locals; if (locals) { builder.pushChildScope(); builder.bindPositionalArgsForLocals(locals); } for (let i = 0; i < this.statements.length; i++) { let statement = this.statements[i]; let refined = SPECIALIZE.specialize(statement, table); STATEMENTS.compile(refined, builder); } if (locals) { builder.popScope(); } } compile(env: Environment): CompiledBlock { let compiled = this.compiled; if (!compiled) { let table = this.symbolTable; let b = builder(env, table); this.splat(b); compiled = this.compiled = new CompiledBlock(b.start, b.end); } return compiled; } } export class PartialBlock extends Template { private compiled: Option = null; public symbolTable: ProgramSymbolTable; compile(env: Environment): CompiledProgram { let compiled = this.compiled; if (!compiled) { let table = this.symbolTable; let b = builder(env, table); for (let i = 0; i < this.statements.length; i++) { let statement = this.statements[i]; let refined = SPECIALIZE.specialize(statement, table); STATEMENTS.compile(refined, b); } compiled = this.compiled = new CompiledProgram(b.start, b.end, table.size); } return compiled; } } export default class Scanner { constructor(private block: SerializedTemplateBlock, private meta: TemplateMeta, private env: Environment) { } scanEntryPoint(): EntryPoint { let { block, meta } = this; let symbolTable = entryPointTable(meta); let child = scanBlock(block, symbolTable, this.env); return new EntryPoint(child.statements, symbolTable); } scanLayout(): Layout { let { block, meta } = this; let { named, yields, hasPartials } = block; let symbolTable = layoutTable(meta, named, yields, hasPartials); let child = scanBlock(block, symbolTable, this.env); return new Layout(child.statements, symbolTable); } scanPartial(symbolTable: SymbolTable): PartialBlock { let { block } = this; let child = scanBlock(block, symbolTable, this.env); return new PartialBlock(child.statements, symbolTable); } } export function scanBlock({ statements }: SerializedBlock, symbolTable: SymbolTable, env: Environment): InlineBlock { return new RawInlineBlock(env, symbolTable, statements).scan(); } import { PublicVM } from './vm'; import { PathReference } from '@glimmer/reference'; export namespace BaselineSyntax { import Core = WireFormat.Core; const { Ops } = WireFormat; // TODO: use symbols for sexp[0]? export type ScannedComponent = [number, string, RawInlineBlock, WireFormat.Core.Hash, Option]; export const isScannedComponent = WireFormat.is(Ops.ScannedComponent); import Params = WireFormat.Core.Params; import Hash = WireFormat.Core.Hash; export type Block = InlineBlock; export type OpenPrimitiveElement = [number, string, string[]]; export const isPrimitiveElement = WireFormat.is(Ops.OpenPrimitiveElement); export type OptimizedAppend = [number, WireFormat.Expression, boolean]; export const isOptimizedAppend = WireFormat.is(Ops.OptimizedAppend); export type UnoptimizedAppend = [number, WireFormat.Expression, boolean]; export const isUnoptimizedAppend = WireFormat.is(Ops.UnoptimizedAppend); export type AnyDynamicAttr = [number, string, WireFormat.Expression, Option, boolean]; export const isAnyAttr = WireFormat.is(Ops.AnyDynamicAttr); export type StaticPartial = [number, string]; export const isStaticPartial = WireFormat.is(Ops.StaticPartial); export type DynamicPartial = [number, WireFormat.Expression]; export const isDynamicPartial = WireFormat.is(Ops.DynamicPartial); export type FunctionExpressionCallback = (VM: PublicVM, symbolTable: SymbolTable) => PathReference; export type FunctionExpression = [number, FunctionExpressionCallback]; export const isFunctionExpression = WireFormat.is(Ops.Function); export type NestedBlock = [number, WireFormat.Core.Path, WireFormat.Core.Params, WireFormat.Core.Hash, Option, Option]; export const isNestedBlock = WireFormat.is(Ops.NestedBlock); export type ScannedBlock = [number, Core.Path, Core.Params, Core.Hash, Option, Option]; export const isScannedBlock = WireFormat.is(Ops.ScannedBlock); export type Debugger = [number]; export const isDebugger = WireFormat.is(Ops.Debugger); export type Args = [Params, Hash, Option, Option]; export namespace NestedBlock { export function defaultBlock(sexp: NestedBlock): Option { return sexp[4]; } export function inverseBlock(sexp: NestedBlock): Option { return sexp[5]; } export function params(sexp: NestedBlock): WireFormat.Core.Params { return sexp[2]; } export function hash(sexp: NestedBlock): WireFormat.Core.Hash { return sexp[3]; } } export type Statement = ScannedComponent | OpenPrimitiveElement | OptimizedAppend | UnoptimizedAppend | StaticPartial | DynamicPartial | AnyDynamicAttr | NestedBlock | ScannedBlock | Debugger ; export type AnyStatement = Statement | WireFormat.Statement; export type AnyExpression = FunctionExpression | WireFormat.Expression; export type Program = AnyStatement[]; } const { Ops } = WireFormat; export class RawInlineBlock { constructor(private env: Environment, private table: SymbolTable, private statements: SerializedStatement[]) {} scan(): InlineBlock { let buffer: BaselineSyntax.AnyStatement[] = []; this.specializeStatements(this.statements, buffer); return new InlineBlock(buffer, this.table); } private specializeStatements(statements: SerializedStatement[], buffer: BaselineSyntax.AnyStatement[]) { for(let i = 0; i < statements.length; i++) { let statement = statements[i]; this.specializeStatement(statement, buffer); } } private specializeStatement(statement: SerializedStatement, buffer: BaselineSyntax.AnyStatement[]) { if (WireFormat.Statements.isBlock(statement)) { buffer.push(this.specializeBlock(statement)); } else if (WireFormat.Statements.isComponent(statement)) { buffer.push(...this.specializeComponent(statement)); } else { buffer.push(statement); } } private specializeBlock(block: WireFormat.Statements.Block): BaselineSyntax.ScannedBlock { let [, path, params, hash, template, inverse] = block; return [Ops.ScannedBlock, path, params, hash, this.child(template), this.child(inverse)]; } private specializeComponent(sexp: WireFormat.Statements.Component): BaselineSyntax.AnyStatement[] { let [, tag, component] = sexp; if (this.env.hasComponentDefinition(tag, this.table)) { let child = this.child(component); let attrs = new RawInlineBlock(this.env, this.table, component.attrs); return [[Ops.ScannedComponent, tag, attrs, component.args, child]]; } else { let buff: BaselineSyntax.AnyStatement[] = []; buff.push([Ops.OpenElement, tag, []]); this.specializeStatements(component.attrs, buff); buff.push([Ops.FlushElement]); this.specializeStatements(component.statements, buff); buff.push([Ops.CloseElement]); return buff; } } child(block: Option): Option { if (!block) return null; let table = blockTable(this.table, block.locals); return new RawInlineBlock(this.env, table, block.statements); } }