From 2c52930070b9a3ac3575c8730281223c99ff13fa Mon Sep 17 00:00:00 2001 From: Yehuda Katz Date: Wed, 2 Aug 2017 14:28:51 -0700 Subject: [PATCH 01/42] WIP --- packages/@glimmer/bundle-compiler/index.ts | 0 .../@glimmer/bundle-compiler/lib/compiler.ts | 27 +++++++++++++++ .../@glimmer/bundle-compiler/lib/templates.ts | 34 +++++++++++++++++++ .../@glimmer/bundle-compiler/package.json | 16 +++++++++ packages/@glimmer/compiler/index.ts | 4 +++ packages/@glimmer/compiler/lib/compiler.ts | 2 +- 6 files changed, 82 insertions(+), 1 deletion(-) create mode 100644 packages/@glimmer/bundle-compiler/index.ts create mode 100644 packages/@glimmer/bundle-compiler/lib/compiler.ts create mode 100644 packages/@glimmer/bundle-compiler/lib/templates.ts create mode 100644 packages/@glimmer/bundle-compiler/package.json diff --git a/packages/@glimmer/bundle-compiler/index.ts b/packages/@glimmer/bundle-compiler/index.ts new file mode 100644 index 0000000000..e69de29bb2 diff --git a/packages/@glimmer/bundle-compiler/lib/compiler.ts b/packages/@glimmer/bundle-compiler/lib/compiler.ts new file mode 100644 index 0000000000..271a997f3e --- /dev/null +++ b/packages/@glimmer/bundle-compiler/lib/compiler.ts @@ -0,0 +1,27 @@ +import { CompilationOptions, Program } from '@glimmer/runtime'; +import { Resolver } from "@glimmer/interfaces"; +import { TemplateMeta } from "@glimmer/wire-format"; +import { preprocess, ASTPluginBuilder } from "@glimmer/syntax"; +import { PrecompileOptions, TemplateCompiler } from "@glimmer/compiler"; + +export interface EagerCompilerOptions { + meta: T; + plugins: ASTPluginBuilder[]; + id: string; +} + +export class EagerCompiler> { + private program: Program; + private resolver: R; + + constructor(private options: CompilationOptions) { + this.program = options.program; + this.resolver = options.resolver; + } + + compile(input: string, options: EagerCompilerOptions) { + let ast = preprocess(input, options); + let { block, meta } = TemplateCompiler.compile(options, ast); + let { id } = options; + } +} \ No newline at end of file diff --git a/packages/@glimmer/bundle-compiler/lib/templates.ts b/packages/@glimmer/bundle-compiler/lib/templates.ts new file mode 100644 index 0000000000..543b62e8da --- /dev/null +++ b/packages/@glimmer/bundle-compiler/lib/templates.ts @@ -0,0 +1,34 @@ +import { ASTPluginBuilder, preprocess } from "@glimmer/syntax"; +import { TemplateMeta, SerializedTemplateBlock } from "@glimmer/wire-format"; +import { TemplateCompiler } from "@glimmer/compiler"; +import { Option, Dict } from "@glimmer/interfaces"; +import { dict } from "@glimmer/util"; + +export interface EagerCompilerOptions { + meta: T; + plugins: ASTPluginBuilder[]; + id: string; + lookup: string; +} + +interface TemplateJSON { + id: Option; + block: SerializedTemplateBlock; + meta: T; +} + +export class Templates { + private templates: TemplateJSON[]; + private dict = dict>(); + + compile(input: string, options: EagerCompilerOptions) { + let ast = preprocess(input, options); + let template = TemplateCompiler.compile(options, ast); + let { id, lookup } = options; + let { block, meta } = template.toJSON(); + + let value = Object.freeze({ id, block, meta }); + this.templates.push(value); + this.dict[lookup] = value; + } +} diff --git a/packages/@glimmer/bundle-compiler/package.json b/packages/@glimmer/bundle-compiler/package.json new file mode 100644 index 0000000000..a9be747569 --- /dev/null +++ b/packages/@glimmer/bundle-compiler/package.json @@ -0,0 +1,16 @@ +{ + "name": "@glimmer/bundle-compiler", + "version": "0.26.2", + "repository": "https://github.com/glimmerjs/glimmer-vm/tree/master/packages/@glimmer/bundle-compiler", + "dependencies": { + "@glimmer/syntax": "^0.26.2", + "@glimmer/util": "^0.26.2", + "@glimmer/runtime": "^0.26.2", + "@glimmer/wire-format": "^0.26.2", + "@glimmer/interfaces": "^0.26.2", + "simple-html-tokenizer": "^0.3.0" + }, + "devDependencies": { + "typescript": "^2.2.0" + } +} \ No newline at end of file diff --git a/packages/@glimmer/compiler/index.ts b/packages/@glimmer/compiler/index.ts index 92735e429b..70d12ae0d6 100644 --- a/packages/@glimmer/compiler/index.ts +++ b/packages/@glimmer/compiler/index.ts @@ -2,5 +2,9 @@ export { precompile, PrecompileOptions } from "./lib/compiler"; +export { + default as TemplateCompiler +} from "./lib/template-compiler"; + // exported only for tests export { default as TemplateVisitor } from './lib/template-visitor'; diff --git a/packages/@glimmer/compiler/lib/compiler.ts b/packages/@glimmer/compiler/lib/compiler.ts index 7b8413434f..a97087eb3d 100644 --- a/packages/@glimmer/compiler/lib/compiler.ts +++ b/packages/@glimmer/compiler/lib/compiler.ts @@ -1,6 +1,6 @@ import { preprocess } from "@glimmer/syntax"; import TemplateCompiler, { CompileOptions } from "./template-compiler"; -import { SerializedTemplateWithLazyBlock, TemplateJavascript, TemplateMeta } from "@glimmer/wire-format"; +import { SerializedTemplateWithLazyBlock, SerializedTemplate, TemplateJavascript, TemplateMeta } from "@glimmer/wire-format"; import { Option } from "@glimmer/interfaces"; import { PreprocessOptions } from "@glimmer/syntax"; From ca32e07622ecd99cc6976831ba0f5f5876f6c863 Mon Sep 17 00:00:00 2001 From: Yehuda Katz Date: Wed, 2 Aug 2017 15:58:38 -0700 Subject: [PATCH 02/42] [WIP] Initial extraction of the opcode compiler into its own package. The bulk of this work is trimming down incidental dependencies that the opcode compiler has on other runtime types. For the most part, they are either completely unnecessary or can be easily dependency injected. The next step is to get `runtime` to use this package. The step after that is to get `bundle-compiler` to use this package to produce a fully precompiled output. The most notable change in the interfaces in this commit is that the `compilation options`, which are now expected to be used in the eager bundler as well, no longer include the full resolver. Instead, they include a `getCapabilities` method that requires only the capabilities for a given component, and not access to arbitrary runtime objects. --- packages/@glimmer/bundle-compiler/index.ts | 3 + .../@glimmer/bundle-compiler/lib/compiler.ts | 3 +- .../@glimmer/bundle-compiler/lib/templates.ts | 31 +- packages/@glimmer/compiler/index.ts | 2 +- packages/@glimmer/compiler/lib/compiler.ts | 4 +- packages/@glimmer/opcode-compiler/index.ts | 1 + .../opcode-compiler/lib/interfaces.ts | 104 +++ .../opcode-compiler/lib/opcode-builder.ts | 857 ++++++++++++++++++ .../@glimmer/opcode-compiler/lib/syntax.ts | 790 ++++++++++++++++ .../@glimmer/opcode-compiler/package.json | 15 + .../runtime/lib/compiled/opcodes/builder.ts | 11 +- packages/@glimmer/runtime/lib/compiler.ts | 8 +- .../runtime/lib/component/interfaces.ts | 10 +- .../runtime/lib/syntax/compilable-template.ts | 12 +- .../@glimmer/runtime/lib/syntax/functions.ts | 6 +- packages/@glimmer/runtime/package.json | 5 +- .../@glimmer/runtime/test/template-test.ts | 27 +- 17 files changed, 1835 insertions(+), 54 deletions(-) create mode 100644 packages/@glimmer/opcode-compiler/index.ts create mode 100644 packages/@glimmer/opcode-compiler/lib/interfaces.ts create mode 100644 packages/@glimmer/opcode-compiler/lib/opcode-builder.ts create mode 100644 packages/@glimmer/opcode-compiler/lib/syntax.ts create mode 100644 packages/@glimmer/opcode-compiler/package.json diff --git a/packages/@glimmer/bundle-compiler/index.ts b/packages/@glimmer/bundle-compiler/index.ts index e69de29bb2..fb006fd192 100644 --- a/packages/@glimmer/bundle-compiler/index.ts +++ b/packages/@glimmer/bundle-compiler/index.ts @@ -0,0 +1,3 @@ +export { + Templates +} from './lib/templates'; diff --git a/packages/@glimmer/bundle-compiler/lib/compiler.ts b/packages/@glimmer/bundle-compiler/lib/compiler.ts index 271a997f3e..d962181b53 100644 --- a/packages/@glimmer/bundle-compiler/lib/compiler.ts +++ b/packages/@glimmer/bundle-compiler/lib/compiler.ts @@ -2,7 +2,6 @@ import { CompilationOptions, Program } from '@glimmer/runtime'; import { Resolver } from "@glimmer/interfaces"; import { TemplateMeta } from "@glimmer/wire-format"; import { preprocess, ASTPluginBuilder } from "@glimmer/syntax"; -import { PrecompileOptions, TemplateCompiler } from "@glimmer/compiler"; export interface EagerCompilerOptions { meta: T; @@ -24,4 +23,4 @@ export class EagerCompiler { meta: T; plugins: ASTPluginBuilder[]; - id: string; lookup: string; } -interface TemplateJSON { - id: Option; +export interface TemplateJSON { + lookup: string; block: SerializedTemplateBlock; meta: T; } export class Templates { - private templates: TemplateJSON[]; + private templates: TemplateJSON[] = []; private dict = dict>(); compile(input: string, options: EagerCompilerOptions) { - let ast = preprocess(input, options); + let ast = preprocess(input, { plugins: { ast: options.plugins } }); let template = TemplateCompiler.compile(options, ast); - let { id, lookup } = options; + let { lookup } = options; let { block, meta } = template.toJSON(); - let value = Object.freeze({ id, block, meta }); + let value = Object.freeze({ lookup, block, meta }); this.templates.push(value); this.dict[lookup] = value; } + + eachTemplate(callback: (template: TemplateJavascript) => void) { + let templates = this.templates; + for (let i=0; i = { id, block, meta }; + callback(JSON.stringify(templateJSONObject)); + } + } } diff --git a/packages/@glimmer/compiler/index.ts b/packages/@glimmer/compiler/index.ts index 70d12ae0d6..687b738b7a 100644 --- a/packages/@glimmer/compiler/index.ts +++ b/packages/@glimmer/compiler/index.ts @@ -1,5 +1,5 @@ export { - precompile, PrecompileOptions + defaultId, precompile, PrecompileOptions } from "./lib/compiler"; export { diff --git a/packages/@glimmer/compiler/lib/compiler.ts b/packages/@glimmer/compiler/lib/compiler.ts index a97087eb3d..b92684dfb0 100644 --- a/packages/@glimmer/compiler/lib/compiler.ts +++ b/packages/@glimmer/compiler/lib/compiler.ts @@ -1,6 +1,6 @@ import { preprocess } from "@glimmer/syntax"; import TemplateCompiler, { CompileOptions } from "./template-compiler"; -import { SerializedTemplateWithLazyBlock, SerializedTemplate, TemplateJavascript, TemplateMeta } from "@glimmer/wire-format"; +import { SerializedTemplateWithLazyBlock, TemplateJavascript, TemplateMeta } from "@glimmer/wire-format"; import { Option } from "@glimmer/interfaces"; import { PreprocessOptions } from "@glimmer/syntax"; @@ -14,7 +14,7 @@ export interface PrecompileOptions extends CompileOption declare function require(id: string): any; -const defaultId: TemplateIdFn = (() => { +export const defaultId: TemplateIdFn = (() => { if (typeof require === 'function') { try { /* tslint:disable:no-require-imports */ diff --git a/packages/@glimmer/opcode-compiler/index.ts b/packages/@glimmer/opcode-compiler/index.ts new file mode 100644 index 0000000000..20b4812f8a --- /dev/null +++ b/packages/@glimmer/opcode-compiler/index.ts @@ -0,0 +1 @@ +export * from './lib/interfaces'; diff --git a/packages/@glimmer/opcode-compiler/lib/interfaces.ts b/packages/@glimmer/opcode-compiler/lib/interfaces.ts new file mode 100644 index 0000000000..442c820a82 --- /dev/null +++ b/packages/@glimmer/opcode-compiler/lib/interfaces.ts @@ -0,0 +1,104 @@ +import { Unique, Opaque, SymbolTable, Option, BlockSymbolTable } from "@glimmer/interfaces"; +import { VersionedPathReference } from "@glimmer/reference"; +import { Core as C, Statements as S, Expression, Core } from "@glimmer/wire-format"; +import { OpcodeBuilder } from './opcode-builder'; + +export type Handle = Unique<"Handle">; + +export interface Heap { + push(name: /* TODO: Op */ number, op1?: number, op2?: number, op3?: number): void; + malloc(): Handle; + finishMalloc(handle: Handle): void; +} + +export interface ComponentCapabilities { + dynamicLayout: boolean; + dynamicTag: boolean; + prepareArgs: boolean; + createArgs: boolean; + attributeHook: boolean; + elementHook: boolean; +} + +export interface EagerResolver { + getCapabilities(specifier: Specifier): ComponentCapabilities; +} + +export interface EagerCompilationOptions> { + resolver: R; + program: Program; + macros: Macros; +} + +export interface RawInlineBlock { + scan(): BlockSyntax; +} + +export interface CompilableTemplate { + symbolTable: S; + compile(): Handle; +} + +export interface Macros { + blocks: Blocks; + inlines: Inlines; +} + +export type BlockSyntax = CompilableTemplate; +export type BlockMacro = (params: C.Params, hash: C.Hash, template: Option, inverse: Option, builder: OpcodeBuilder) => void; +export type MissingBlockMacro = (name: string, params: C.Params, hash: C.Hash, template: Option, inverse: Option, builder: OpcodeBuilder) => void; + +export interface Blocks { + add(name: string, func: BlockMacro): void; + addMissing(func: MissingBlockMacro): void; + compile(name: string, params: C.Params, hash: C.Hash, template: Option, inverse: Option, builder: OpcodeBuilder): void; +} + +export type AppendSyntax = S.Append; +export type AppendMacro = (name: string, params: Option, hash: Option, builder: OpcodeBuilder) => ['expr', Expression] | true | false; + +export interface Inlines { + add(name: string, func: AppendMacro): void; + addMissing(func: AppendMacro): void; + compile(sexp: AppendSyntax, builder: OpcodeBuilder): ['expr', Expression] | true; +} + +export interface Program { + [key: number]: never; + + constants: Constants; + heap: Heap; + + opcode(offset: number): Opcode; +} + +export interface Opcode { + offset: number; + type: number; + op1: number; + op2: number; + op3: number; +} + +export type Primitive = undefined | null | boolean | number | string; + +export interface Constants { + reference(value: VersionedPathReference): number; + string(value: string): number; + stringArray(strings: string[]): number; + array(values: number[]): number; + table(t: SymbolTable): number; + specifier(specifier: Opaque): number; + serializable(value: Opaque): number; +} + +export interface LazyConstants extends Constants { + other(value: Opaque): number; +} + +export type ComponentArgs = [Core.Params, Core.Hash, Option, Option]; +export type Specifier = Opaque; + +export interface ComponentBuilder { + static(definition: Specifier, args: ComponentArgs): void; +} diff --git a/packages/@glimmer/opcode-compiler/lib/opcode-builder.ts b/packages/@glimmer/opcode-compiler/lib/opcode-builder.ts new file mode 100644 index 0000000000..f7fb2ca9b8 --- /dev/null +++ b/packages/@glimmer/opcode-compiler/lib/opcode-builder.ts @@ -0,0 +1,857 @@ +import { CompilationMeta, Opaque, Option, ProgramSymbolTable, SymbolTable } from '@glimmer/interfaces'; +import { dict, EMPTY_ARRAY, expect, fillNulls, Stack, typePos, unreachable } from '@glimmer/util'; +import { Op, Register } from '@glimmer/vm'; +import * as WireFormat from '@glimmer/wire-format'; +import { TemplateMeta } from "@glimmer/wire-format"; + +import { + Handle, + Heap, + LazyConstants, + Primitive, + BlockSyntax, + CompilableTemplate, + Constants, + RawInlineBlock, + Specifier, + Program, + ComponentBuilder, + ComponentCapabilities +} from './interfaces'; + +import { + ATTRS_BLOCK, + expr +} from './syntax'; + +export interface CompilesInto { + compile(builder: OpcodeBuilder): E; +} + +export type Label = string; + +type TargetOpcode = Op.Jump | Op.JumpIf | Op.JumpUnless | Op.EnterList | Op.Iterate | Op.ReturnTo; + +class Labels { + labels = dict(); + targets: Array<{ at: number, Target: TargetOpcode, target: string }> = []; + + label(name: string, index: number) { + this.labels[name] = index; + } + + target(at: number, Target: TargetOpcode, target: string) { + this.targets.push({ at, Target, target }); + } + + patch(buffer: number[]): void { + let { targets, labels } = this; + for (let i = 0; i < targets.length; i++) { + let { at, target } = targets[i]; + let address = labels[target] - at; + buffer[at + 1] = address; + } + } +} + +export interface AbstractTemplate { + symbolTable: S; +} + +export abstract class OpcodeBuilder = AbstractTemplate> { + public constants: Constants; + + private buffer: number[] = []; + private labelsStack = new Stack(); + private isComponentAttrs = false; + + constructor(public program: Program, public meta: CompilationMeta, public component: ComponentBuilder) { + this.constants = program.constants; + } + + private get pos(): number { + return typePos(this.buffer.length); + } + + private get nextPos(): number { + return this.buffer.length; + } + + upvars(count: number): T { + return fillNulls(count) as T; + } + + reserve(name: Op) { + this.push(name, 0, 0, 0); + } + + push(name: Op, op1 = 0, op2 = 0, op3 = 0) { + let { buffer } = this; + buffer.push(name); + buffer.push(op1); + buffer.push(op2); + buffer.push(op3); + } + + commit(heap: Heap): Handle { + this.push(Op.Return); + + let { buffer } = this; + + // TODO: change the whole malloc API and do something more efficient + let handle = heap.malloc(); + + for (let i = 0; i < buffer.length; i++) { + heap.push(buffer[i]); + } + + heap.finishMalloc(handle); + + return handle; + } + + setComponentAttrs(enabled: boolean): void { + this.isComponentAttrs = enabled; + } + + // args + + pushArgs(names: string[], positionalCount: number, synthetic: boolean) { + let serialized = this.constants.stringArray(names); + this.push(Op.PushArgs, serialized, positionalCount, synthetic === true ? 1 : 0); + } + + // 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.buffer); + } + + // components + + pushComponentManager(specifier: Specifier) { + this.push(Op.PushComponentManager, this.constants.specifier(specifier)); + } + + pushDynamicComponentManager(meta: TemplateMeta) { + this.push(Op.PushDynamicComponentManager, this.constants.serializable(meta)); + } + + prepareArgs(state: Register) { + this.push(Op.PrepareArgs, state); + } + + createComponent(state: Register, hasDefault: boolean, hasInverse: boolean) { + let flag = (hasDefault === true ? 1 : 0) | ((hasInverse === true ? 1 : 0) << 1); + this.push(Op.CreateComponent, flag, state); + } + + registerComponentDestructor(state: Register) { + this.push(Op.RegisterComponentDestructor, state); + } + + beginComponentTransaction() { + this.push(Op.BeginComponentTransaction); + } + + commitComponentTransaction() { + this.push(Op.CommitComponentTransaction); + } + + putComponentOperations() { + this.push(Op.PutComponentOperations); + } + + getComponentSelf(state: Register) { + this.push(Op.GetComponentSelf, state); + } + + getComponentTagName(state: Register) { + this.push(Op.GetComponentTagName, state); + } + + getComponentLayout(state: Register) { + this.push(Op.GetComponentLayout, state); + } + + invokeComponentLayout() { + this.push(Op.InvokeComponentLayout); + } + + didCreateElement(state: Register) { + this.push(Op.DidCreateElement, state); + } + + didRenderLayout(state: Register) { + this.push(Op.DidRenderLayout, state); + } + + // partial + + invokePartial(meta: TemplateMeta, symbols: string[], evalInfo: number[]) { + let _meta = this.constants.serializable(meta); + let _symbols = this.constants.stringArray(symbols); + let _evalInfo = this.constants.array(evalInfo); + + this.push(Op.InvokePartial, _meta, _symbols, _evalInfo); + } + + resolveMaybeLocal(name: string) { + this.push(Op.ResolveMaybeLocal, this.string(name)); + } + + // debugger + + debugger(symbols: string[], evalInfo: number[]) { + this.push(Op.Debugger, this.constants.stringArray(symbols), this.constants.array(evalInfo)); + } + + // content + + dynamicContent(isTrusting: boolean) { + this.push(Op.DynamicContent, isTrusting ? 1 : 0); + } + + // dom + + text(text: string) { + this.push(Op.Text, this.constants.string(text)); + } + + openPrimitiveElement(tag: string) { + this.push(Op.OpenElement, this.constants.string(tag)); + } + + openElementWithOperations(tag: string) { + this.push(Op.OpenElementWithOperations, this.constants.string(tag)); + } + + openDynamicElement() { + this.push(Op.OpenDynamicElement); + } + + flushElement() { + this.push(Op.FlushElement); + } + + closeElement() { + this.push(Op.CloseElement); + } + + staticAttr(_name: string, _namespace: Option, _value: string) { + let name = this.constants.string(_name); + let namespace = _namespace ? this.constants.string(_namespace) : 0; + + if (this.isComponentAttrs) { + this.pushPrimitiveReference(_value); + this.push(Op.ComponentAttr, name, 1, namespace); + } else { + let value = this.constants.string(_value); + this.push(Op.StaticAttr, name, value, namespace); + } + } + + dynamicAttr(_name: string, _namespace: Option, trusting: boolean) { + let name = this.constants.string(_name); + let namespace = _namespace ? this.constants.string(_namespace) : 0; + + if (this.isComponentAttrs) { + this.push(Op.ComponentAttr, name, (trusting === true ? 1 : 0), namespace); + } else { + this.push(Op.DynamicAttr, name, (trusting === true ? 1 : 0), namespace); + } + } + + comment(_comment: string) { + let comment = this.constants.string(_comment); + this.push(Op.Comment, comment); + } + + modifier(specifier: Specifier) { + this.push(Op.Modifier, this.constants.specifier(specifier)); + } + + // lists + + putIterator() { + this.push(Op.PutIterator); + } + + enterList(start: string) { + this.reserve(Op.EnterList); + this.labels.target(this.pos, Op.EnterList, start); + } + + exitList() { + this.push(Op.ExitList); + } + + iterate(breaks: string) { + this.reserve(Op.Iterate); + this.labels.target(this.pos, Op.Iterate, breaks); + } + + // expressions + + setVariable(symbol: number) { + this.push(Op.SetVariable, symbol); + } + + setBlock(symbol: number) { + this.push(Op.SetBlock, symbol); + } + + getVariable(symbol: number) { + this.push(Op.GetVariable, symbol); + } + + getProperty(key: string) { + this.push(Op.GetProperty, this.string(key)); + } + + getBlock(symbol: number) { + this.push(Op.GetBlock, symbol); + } + + hasBlock(symbol: number) { + this.push(Op.HasBlock, symbol); + } + + hasBlockParams(symbol: number) { + this.push(Op.HasBlockParams, symbol); + } + + concat(size: number) { + this.push(Op.Concat, size); + } + + load(register: Register) { + this.push(Op.Load, register); + } + + fetch(register: Register) { + this.push(Op.Fetch, register); + } + + dup(register = Register.sp, offset = 0) { + return this.push(Op.Dup, register, offset); + } + + pop(count = 1) { + return this.push(Op.Pop, count); + } + + // vm + + pushRemoteElement() { + this.push(Op.PushRemoteElement); + } + + popRemoteElement() { + this.push(Op.PopRemoteElement); + } + + label(name: string) { + this.labels.label(name, this.nextPos); + } + + pushRootScope(symbols: number, bindCallerScope: boolean) { + this.push(Op.RootScope, symbols, (bindCallerScope ? 1 : 0)); + } + + pushChildScope() { + this.push(Op.ChildScope); + } + + popScope() { + this.push(Op.PopScope); + } + + returnTo(label: string) { + this.reserve(Op.ReturnTo); + this.labels.target(this.pos, Op.ReturnTo, label); + } + + pushDynamicScope() { + this.push(Op.PushDynamicScope); + } + + popDynamicScope() { + this.push(Op.PopDynamicScope); + } + + primitive(_primitive: Primitive) { + let flag: 0 | 1 | 2 = 0; + let primitive: number; + switch (typeof _primitive) { + case 'number': + primitive = _primitive as number; + break; + case 'string': + primitive = this.string(_primitive as string); + flag = 1; + break; + case 'boolean': + primitive = (_primitive as any) | 0; + flag = 2; + break; + case 'object': + // assume null + primitive = 2; + flag = 2; + break; + case 'undefined': + primitive = 3; + flag = 2; + break; + default: + throw new Error('Invalid primitive passed to pushPrimitive'); + } + + this.push(Op.Primitive, (flag << 30) | primitive); + } + + pushPrimitiveReference(primitive: Primitive) { + this.primitive(primitive); + this.primitiveReference(); + } + + primitiveReference() { + this.push(Op.PrimitiveReference); + } + + helper(helper: Specifier) { + this.push(Op.Helper, this.constants.specifier(helper)); + } + + bindDynamicScope(_names: string[]) { + this.push(Op.BindDynamicScope, this.names(_names)); + } + + enter(args: number) { + this.push(Op.Enter, args); + } + + exit() { + this.push(Op.Exit); + } + + return() { + this.push(Op.Return); + } + + pushFrame() { + this.push(Op.PushFrame); + } + + popFrame() { + this.push(Op.PopFrame); + } + + invokeStatic(): void { + this.push(Op.InvokeStatic); + } + + invokeYield(): void { + this.push(Op.InvokeYield); + } + + toBoolean() { + this.push(Op.ToBoolean); + } + + jump(target: string) { + this.reserve(Op.Jump); + this.labels.target(this.pos, Op.Jump, target); + } + + jumpIf(target: string) { + this.reserve(Op.JumpIf); + this.labels.target(this.pos, Op.JumpIf, target); + } + + jumpUnless(target: string) { + this.reserve(Op.JumpUnless); + this.labels.target(this.pos, Op.JumpUnless, target); + } + + // internal helpers + + string(_string: string): number { + return this.constants.string(_string); + } + + protected names(_names: string[]): number { + let names: number[] = []; + + for (let i = 0; i < _names.length; i++) { + let n = _names[i]; + names[i]= this.constants.string(n); + } + + return this.constants.array(names); + } + + protected symbols(symbols: number[]): number { + return this.constants.array(symbols); + } + + // convenience methods + + compileParams(params: Option) { + if (!params) return 0; + + for (let i = 0; i < params.length; i++) { + expr(params[i], this); + } + + return params.length; + } + + compileArgs(params: Option, hash: Option, synthetic: boolean) { + let count = this.compileParams(params); + + let names: string[] = EMPTY_ARRAY; + + if (hash) { + names = hash[0]; + let val = hash[1]; + for (let i = 0; i < val.length; i++) { + expr(val[i], this); + } + } + + this.pushArgs(names, count, synthetic); + } + + invokeStaticBlock(block: BlockSyntax, callerCount = 0): void { + let { parameters } = block.symbolTable; + let calleeCount = parameters.length; + let count = Math.min(callerCount, calleeCount); + + this.pushFrame(); + + if (count) { + this.pushChildScope(); + + for (let i = 0; i < count; i++) { + this.dup(Register.fp, callerCount - i); + this.setVariable(parameters[i]); + } + } + + this.pushBlock(block); + this.resolveBlock(); + this.invokeStatic(); + + if (count) { + this.popScope(); + } + + this.popFrame(); + } + + guardedAppend(expression: WireFormat.Expression, trusting: boolean) { + this.startLabels(); + + this.pushFrame(); + + this.returnTo('END'); + + expr(expression, this); + + this.dup(); + this.isComponent(); + + this.enter(2); + + this.jumpUnless('ELSE'); + + this.pushDynamicComponentManager(this.meta.templateMeta); + this.invokeComponent(null, null, null, false, null, null); + + this.exit(); + + this.return(); + + this.label('ELSE'); + + this.dynamicContent(trusting); + + this.exit(); + + this.return(); + + this.label('END'); + + this.popFrame(); + + this.stopLabels(); + } + + yield(to: number, params: Option) { + this.compileArgs(params, null, false); + this.getBlock(to); + this.resolveBlock(); + this.invokeYield(); + this.popScope(); + this.popFrame(); + } + + invokeComponent(attrs: Option, params: Option, hash: WireFormat.Core.Hash, synthetic: boolean, block: Option, inverse: Option = null) { + this.fetch(Register.s0); + this.dup(Register.sp, 1); + this.load(Register.s0); + + this.pushYieldableBlock(block); + this.pushYieldableBlock(inverse); + this.pushYieldableBlock(attrs && attrs.scan()); + + this.compileArgs(params, hash, synthetic); + this.prepareArgs(Register.s0); + + this.beginComponentTransaction(); + this.pushDynamicScope(); + this.createComponent(Register.s0, block !== null, inverse !== null); + this.registerComponentDestructor(Register.s0); + + this.getComponentSelf(Register.s0); + + this.getComponentLayout(Register.s0); + this.resolveLayout(); + this.invokeComponentLayout(); + this.didRenderLayout(Register.s0); + this.popFrame(); + + this.popScope(); + this.popDynamicScope(); + this.commitComponentTransaction(); + + this.load(Register.s0); + } + + invokeStaticComponent(capabilities: ComponentCapabilities, layout: Layout, attrs: Option, params: Option, hash: WireFormat.Core.Hash, synthetic: boolean, block: Option, inverse: Option = null) { + let { symbolTable } = layout; + + let bailOut = + symbolTable.hasEval || + capabilities.prepareArgs || + capabilities.createArgs; + + if (bailOut) { + this.invokeComponent(attrs, params, hash, synthetic, block, inverse); + return; + } + + this.fetch(Register.s0); + this.dup(Register.sp, 1); + this.load(Register.s0); + + let { symbols } = symbolTable; + + this.beginComponentTransaction(); + this.pushDynamicScope(); + this.createComponent(Register.s0, block !== null, inverse !== null); + this.registerComponentDestructor(Register.s0); + + let bindings: { symbol: number, isBlock: boolean }[] = []; + + this.getComponentSelf(Register.s0); + bindings.push({ symbol: 0, isBlock: false }); + + for (let i = 0; i < symbols.length; i++) { + let symbol = symbols[i]; + + switch (symbol.charAt(0)) { + case '&': + let callerBlock: Option = null; + + if (symbol === '&default') { + callerBlock = block; + } else if (symbol === '&inverse') { + callerBlock = inverse; + } else if (symbol === ATTRS_BLOCK) { + callerBlock = attrs && attrs.scan(); + } else { + throw unreachable(); + } + + if (callerBlock) { + this.pushYieldableBlock(callerBlock); + bindings.push({ symbol: i + 1, isBlock: true }); + } else { + this.primitive(null); + bindings.push({ symbol: i + 1, isBlock: false }); + } + + break; + + case '@': + if (!hash) { + break; + } + + let [keys, values] = hash; + let lookupName = symbol; + + if (synthetic) { + lookupName = symbol.slice(1); + } + + let index = keys.indexOf(lookupName); + + if (index !== -1) { + expr(values[index], this); + bindings.push({ symbol: i + 1, isBlock: false }); + } + + break; + } + } + + this.pushRootScope(symbols.length + 1, !!(block || inverse || attrs)); + + for (let i = bindings.length - 1; i >= 0; i--) { + let { symbol, isBlock } = bindings[i]; + + if (isBlock) { + this.setBlock(symbol); + } else { + this.setVariable(symbol); + } + } + + this.pushFrame(); + + this.pushSymbolTable(layout.symbolTable); + this.pushLayout(layout); + this.resolveLayout(); + this.invokeStatic(); + this.didRenderLayout(Register.s0); + this.popFrame(); + + this.popScope(); + this.popDynamicScope(); + this.commitComponentTransaction(); + + this.load(Register.s0); + } + + dynamicComponent(definition: WireFormat.Expression, /* TODO: attrs: Option, */ params: Option, hash: WireFormat.Core.Hash, synthetic: boolean, block: Option, inverse: Option = null) { + this.startLabels(); + + this.pushFrame(); + + this.returnTo('END'); + + expr(definition, this); + + this.dup(); + + this.enter(2); + + this.jumpUnless('ELSE'); + + this.pushDynamicComponentManager(this.meta.templateMeta); + this.invokeComponent(null, params, hash, synthetic, block, inverse); + + this.label('ELSE'); + this.exit(); + this.return(); + + this.label('END'); + this.popFrame(); + + this.stopLabels(); + } + + isComponent() { + this.push(Op.IsComponent); + } + + curryComponent(definition: WireFormat.Expression, /* TODO: attrs: Option, */ params: Option, hash: WireFormat.Core.Hash, synthetic: boolean) { + let meta = this.meta.templateMeta; + + expr(definition, this); + this.compileArgs(params, hash, synthetic); + this.push(Op.CurryComponent, this.constants.serializable(meta)); + } + + abstract pushBlock(block: Option): void; + abstract resolveBlock(): void; + abstract pushLayout(layout: Option): void; + abstract resolveLayout(): void; + abstract pushSymbolTable(block: Option): void; + + pushYieldableBlock(block: Option): void { + this.pushSymbolTable(block && block.symbolTable); + this.pushBlock(block); + } + + template(block: Option): Option { + if (!block) return null; + + // TODO: DI the Concrete RawInlineBlock + return new RawInlineBlock(block.statements, block.parameters, this.meta, this.program); + } +} + +export default OpcodeBuilder; + +export class LazyOpcodeBuilder extends OpcodeBuilder> { + public constants: LazyConstants; + + pushSymbolTable(symbolTable: Option) { + if (symbolTable) { + this.pushOther(symbolTable); + } else { + this.primitive(null); + } + } + + pushBlock(block: Option): void { + if (block) { + this.pushOther(block); + } else { + this.primitive(null); + } + } + + resolveBlock(): void { + this.push(Op.CompileBlock); + } + + pushLayout(layout: Option>) { + if (layout) { + this.pushOther(layout); + } else { + this.primitive(null); + } + } + + resolveLayout(): void { + this.push(Op.CompileBlock); + } + + protected pushOther(value: T) { + this.push(Op.Constant, this.other(value)); + } + + protected other(value: Opaque): number { + return this.constants.other(value); + } +} + +// export class EagerOpcodeBuilder extends OpcodeBuilder { +// } + +export type BlockCallback = (dsl: OpcodeBuilder, BEGIN: Label, END: Label) => void; diff --git a/packages/@glimmer/opcode-compiler/lib/syntax.ts b/packages/@glimmer/opcode-compiler/lib/syntax.ts new file mode 100644 index 0000000000..db76c55b2b --- /dev/null +++ b/packages/@glimmer/opcode-compiler/lib/syntax.ts @@ -0,0 +1,790 @@ +import { CompilationMeta, Option, ProgramSymbolTable } from '@glimmer/interfaces'; +import { assert, dict, EMPTY_ARRAY, unwrap } from '@glimmer/util'; +import { Register } from '@glimmer/vm'; +import * as WireFormat from '@glimmer/wire-format'; +import OpcodeBuilder, { LazyOpcodeBuilder } from '../compiled/opcodes/builder'; +import { Handle, Heap } from './interfaces'; +import { ComponentDefinition } from '../internal-interfaces'; +import * as ClientSide from './client-side'; +import { BlockSyntax, CompilationOptions } from './interfaces'; +import RawInlineBlock from './raw-block'; +import Ops = WireFormat.Ops; + +export type TupleSyntax = WireFormat.Statement | WireFormat.TupleExpression; +export type CompilerFunction = ((sexp: T, builder: OpcodeBuilder) => void); + +export const ATTRS_BLOCK = '&attrs'; + +class Compilers { + private names = dict(); + private funcs: CompilerFunction[] = []; + + constructor(private offset = 0) {} + + add(name: number, func: CompilerFunction): void { + this.funcs.push(func); + this.names[name] = this.funcs.length - 1; + } + + compile(sexp: T, builder: OpcodeBuilder): void { + let name: number = sexp[this.offset]; + let index = this.names[name]; + let func = this.funcs[index]; + assert(!!func, `expected an implementation for ${this.offset === 0 ? Ops[sexp[0]] : ClientSide.Ops[sexp[1]]}`); + func(sexp, builder); + } +} + +import S = WireFormat.Statements; + +const STATEMENTS = new Compilers(); +const CLIENT_SIDE = new Compilers(1); + +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: S.CloseElement, builder: OpcodeBuilder) => { + builder.closeElement(); +}); + +STATEMENTS.add(Ops.FlushElement, (_sexp: S.FlushElement, builder: OpcodeBuilder) => { + builder.flushElement(); +}); + +STATEMENTS.add(Ops.Modifier, (sexp: S.Modifier, builder: OpcodeBuilder) => { + let { options: { resolver }, meta } = builder; + let [, name, params, hash] = sexp; + + let specifier = resolver.lookupModifier(name, meta.templateMeta); + + if (specifier) { + builder.compileArgs(params, hash, true); + builder.modifier(specifier); + } else { + throw new Error(`Compile Error ${name} 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.DynamicAttr, (sexp: S.DynamicAttr, builder: OpcodeBuilder) => { + dynamicAttr(sexp, false, builder); +}); + +STATEMENTS.add(Ops.TrustingAttr, (sexp: S.DynamicAttr, builder: OpcodeBuilder) => { + dynamicAttr(sexp, true, builder); +}); + +function dynamicAttr(sexp: S.DynamicAttr | S.TrustingAttr, trusting: boolean, builder: OpcodeBuilder) { + let [, name, value, namespace] = sexp; + + expr(value, builder); + + if (namespace) { + builder.dynamicAttr(name, namespace, trusting); + } else { + builder.dynamicAttr(name, null, trusting); + } +} + +STATEMENTS.add(Ops.OpenElement, (sexp: S.OpenElement, builder: OpcodeBuilder) => { + builder.openPrimitiveElement(sexp[1]); +}); + +STATEMENTS.add(Ops.OpenSplattedElement, (sexp: S.SplatElement, builder) => { + builder.setComponentAttrs(true); + builder.putComponentOperations(); + builder.openElementWithOperations(sexp[1]); +}); + +CLIENT_SIDE.add(ClientSide.Ops.OpenComponentElement, (sexp: ClientSide.OpenComponentElement, builder: OpcodeBuilder) => { + builder.putComponentOperations(); + builder.openElementWithOperations(sexp[2]); +}); + +CLIENT_SIDE.add(ClientSide.Ops.DidCreateElement, (_sexp: ClientSide.DidCreateElement, builder: OpcodeBuilder) => { + builder.didCreateElement(Register.s0); +}); + +CLIENT_SIDE.add(ClientSide.Ops.SetComponentAttrs, (sexp: ClientSide.SetComponentAttrs, builder) => { + builder.setComponentAttrs(sexp[2]); +}); + +CLIENT_SIDE.add(ClientSide.Ops.Debugger, () => { + // tslint:disable-next-line:no-debugger + debugger; +}); + +CLIENT_SIDE.add(ClientSide.Ops.DidRenderLayout, (_sexp: ClientSide.DidRenderLayout, builder: OpcodeBuilder) => { + builder.didRenderLayout(Register.s0); +}); + +STATEMENTS.add(Ops.Append, (sexp: S.Append, builder: OpcodeBuilder) => { + let [, value, trusting] = sexp; + + let { inlines } = builder.options.macros; + let returned = inlines.compile(sexp, builder) || value; + + if (returned === true) return; + + let isGet = E.isGet(value); + let isMaybeLocal = E.isMaybeLocal(value); + + if (trusting) { + builder.guardedAppend(value, true); + } else { + if (isGet || isMaybeLocal) { + builder.guardedAppend(value, false); + } else { + expr(value, builder); + builder.dynamicContent(false); + } + } +}); + +STATEMENTS.add(Ops.Block, (sexp: S.Block, builder: OpcodeBuilder) => { + let [, name, params, hash, _template, _inverse] = sexp; + let template = builder.template(_template); + let inverse = builder.template(_inverse); + + let templateBlock = template && template.scan(); + let inverseBlock = inverse && inverse.scan(); + + let { blocks } = builder.options.macros; + blocks.compile(name, params, hash, templateBlock, inverseBlock, builder); +}); + +STATEMENTS.add(Ops.Component, (sexp: S.Component, builder: OpcodeBuilder) => { + let [, tag, _attrs, args, block] = sexp; + + let options = builder.options; + let resolver = options.resolver; + let specifier = resolver.lookupComponent(tag, builder.meta.templateMeta); + + if (specifier) { + let definition = resolver.resolve(specifier); + let manager = definition.manager; + + let attrs: WireFormat.Statement[] = [ + [Ops.ClientSideStatement, ClientSide.Ops.SetComponentAttrs, true], + ..._attrs, + [Ops.ClientSideStatement, ClientSide.Ops.SetComponentAttrs, false] + ]; + let attrsBlock = new RawInlineBlock(attrs, EMPTY_ARRAY, builder.meta, builder.options); + let child = builder.template(block); + + if (hasStaticLayout(definition, manager)) { + let layoutSpecifier = manager.getLayout(definition, resolver); + let layout = resolver.resolve<{ symbolTable: ProgramSymbolTable, template: Handle }>(layoutSpecifier); + + builder.pushComponentManager(specifier); + builder.invokeStaticComponent(definition, layout, attrsBlock, null, args, false, child && child.scan()); + } else { + builder.pushComponentManager(specifier); + builder.invokeComponent(attrsBlock, null, args, false, child && child.scan()); + } + } else { + throw new Error(`Compile Error: Cannot find component ${tag}`); + } +}); + +STATEMENTS.add(Ops.Partial, (sexp: S.Partial, builder: OpcodeBuilder) => { + let [, name, evalInfo] = sexp; + + let { meta: { templateMeta, symbols } } = builder; + + builder.startLabels(); + + builder.pushFrame(); + + builder.returnTo('END'); + + expr(name, builder); + + builder.dup(); + + builder.enter(2); + + builder.jumpUnless('ELSE'); + + builder.invokePartial(templateMeta, symbols, evalInfo); + builder.popScope(); + builder.popFrame(); + + builder.label('ELSE'); + builder.exit(); + builder.return(); + + builder.label('END'); + builder.popFrame(); + + builder.stopLabels(); +}); + +STATEMENTS.add(Ops.Yield, (sexp: WireFormat.Statements.Yield, builder: OpcodeBuilder) => { + let [, to, params] = sexp; + + builder.yield(to, params); +}); + +STATEMENTS.add(Ops.AttrSplat, (sexp: WireFormat.Statements.AttrSplat, builder: OpcodeBuilder) => { + let [, to] = sexp; + + builder.yield(to, []); + builder.didCreateElement(Register.s0); + builder.setComponentAttrs(false); +}); + +STATEMENTS.add(Ops.Debugger, (sexp: WireFormat.Statements.Debugger, builder: OpcodeBuilder) => { + let [, evalInfo] = sexp; + + builder.debugger(builder.meta.symbols, evalInfo); +}); + +STATEMENTS.add(Ops.ClientSideStatement, (sexp: WireFormat.Statements.ClientSide, builder: OpcodeBuilder) => { + CLIENT_SIDE.compile(sexp as ClientSide.ClientSideStatement, builder); +}); + +const EXPRESSIONS = new Compilers(); + +import E = WireFormat.Expressions; +import C = WireFormat.Core; + +export function expr(expression: WireFormat.Expression, builder: OpcodeBuilder): void { + if (Array.isArray(expression)) { + EXPRESSIONS.compile(expression, builder); + } else { + builder.pushPrimitiveReference(expression); + } +} + +EXPRESSIONS.add(Ops.Unknown, (sexp: E.Unknown, builder: OpcodeBuilder) => { + let { options: { resolver }, meta } = builder; + let name = sexp[1]; + + let specifier = resolver.lookupHelper(name, meta.templateMeta); + + if (specifier) { + builder.compileArgs(null, null, true); + builder.helper(specifier); + } else if (meta.asPartial) { + builder.resolveMaybeLocal(name); + } else { + builder.getVariable(0); + builder.getProperty(name); + } +}); + +EXPRESSIONS.add(Ops.Concat, ((sexp: E.Concat, builder: OpcodeBuilder) => { + let parts = sexp[1]; + for (let i = 0; i < parts.length; i++) { + expr(parts[i], builder); + } + builder.concat(parts.length); +}) as any); + +EXPRESSIONS.add(Ops.Helper, (sexp: E.Helper, builder: OpcodeBuilder) => { + let { options: { resolver }, meta } = builder; + let [, name, params, hash] = sexp; + + // TODO: triage this in the WF compiler + if (name === 'component') { + assert(params.length, 'SYNTAX ERROR: component helper requires at least one argument'); + + let [definition, ...restArgs] = params; + builder.curryComponent(definition, restArgs, hash, true); + return; + } + + let specifier = resolver.lookupHelper(name, meta.templateMeta); + + if (specifier) { + builder.compileArgs(params, hash, true); + builder.helper(specifier); + } else { + throw new Error(`Compile Error: ${name} is not a helper`); + } +}); + +EXPRESSIONS.add(Ops.Get, (sexp: E.Get, builder: OpcodeBuilder) => { + let [, head, path] = sexp; + builder.getVariable(head); + for (let i = 0; i < path.length; i++) { + builder.getProperty(path[i]); + } +}); + +EXPRESSIONS.add(Ops.MaybeLocal, (sexp: E.MaybeLocal, builder: OpcodeBuilder) => { + let [, path] = sexp; + + if (builder.meta.asPartial) { + let head = path[0]; + path = path.slice(1); + + builder.resolveMaybeLocal(head); + } else { + builder.getVariable(0); + } + + for(let i = 0; i < path.length; i++) { + builder.getProperty(path[i]); + } +}); + +EXPRESSIONS.add(Ops.Undefined, (_sexp, builder) => { + return builder.pushPrimitiveReference(undefined); +}); + +EXPRESSIONS.add(Ops.HasBlock, (sexp: E.HasBlock, builder: OpcodeBuilder) => { + builder.hasBlock(sexp[1]); +}); + +EXPRESSIONS.add(Ops.HasBlockParams, (sexp: E.HasBlockParams, builder: OpcodeBuilder) => { + builder.hasBlockParams(sexp[1]); +}); + +export type BlockMacro = (params: C.Params, hash: C.Hash, template: Option, inverse: Option, builder: OpcodeBuilder) => void; +export type MissingBlockMacro = (name: string, params: C.Params, hash: C.Hash, template: Option, inverse: Option, builder: OpcodeBuilder) => void; + +export class Blocks { + private names = dict(); + private funcs: BlockMacro[] = []; + private missing: MissingBlockMacro; + + add(name: string, func: BlockMacro) { + this.funcs.push(func); + this.names[name] = this.funcs.length - 1; + } + + addMissing(func: MissingBlockMacro) { + this.missing = func; + } + + compile(name: string, params: C.Params, hash: C.Hash, template: Option, inverse: Option, builder: OpcodeBuilder): void { + 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(name, params, hash, template, inverse, builder); + assert(!!handled, `${name} not found, and the catch-all block handler didn't handle it`); + } else { + let func = this.funcs[index]; + func(params, hash, template, inverse, builder); + } + } +} + +export const BLOCKS = new Blocks(); + +export type AppendSyntax = S.Append; +export type AppendMacro = (name: string, params: Option, hash: Option, builder: OpcodeBuilder) => ['expr', WireFormat.Expression] | true | false; + +export class Inlines { + private names = dict(); + 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', WireFormat.Expression] | 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 name: string; + let params: Option; + let hash: Option; + + if (value[0] === Ops.Helper) { + name = value[1]; + params = value[2]; + hash = value[3]; + } else if (value[0] === Ops.Unknown) { + name = value[1]; + params = hash = null; + } else { + return ['expr', value]; + } + + let index = this.names[name]; + + if (index === undefined && this.missing) { + let func = this.missing; + let returned = func(name, params, hash, builder); + return returned === false ? ['expr', value] : returned; + } else if (index !== undefined) { + let func = this.funcs[index]; + let returned = func(name, 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', (params, _hash, template, inverse, builder) => { + // PutArgs + // Test(Environment) + // Enter(BEGIN, END) + // BEGIN: Noop + // JumpUnless(ELSE) + // Evaluate(default) + // Jump(END) + // ELSE: Noop + // Evalulate(inverse) + // END: Noop + // Exit + + if (!params || params.length !== 1) { + throw new Error(`SYNTAX ERROR: #if requires a single argument`); + } + + builder.startLabels(); + + builder.pushFrame(); + + builder.returnTo('END'); + + expr(params[0], builder); + + builder.toBoolean(); + + builder.enter(1); + + builder.jumpUnless('ELSE'); + + builder.invokeStaticBlock(unwrap(template)); + + if (inverse) { + builder.jump('EXIT'); + + builder.label('ELSE'); + builder.invokeStaticBlock(inverse); + + builder.label('EXIT'); + builder.exit(); + builder.return(); + } else { + builder.label('ELSE'); + builder.exit(); + builder.return(); + } + + builder.label('END'); + builder.popFrame(); + + builder.stopLabels(); + }); + + blocks.add('unless', (params, _hash, template, inverse, builder) => { + // PutArgs + // Test(Environment) + // Enter(BEGIN, END) + // BEGIN: Noop + // JumpUnless(ELSE) + // Evaluate(default) + // Jump(END) + // ELSE: Noop + // Evalulate(inverse) + // END: Noop + // Exit + + if (!params || params.length !== 1) { + throw new Error(`SYNTAX ERROR: #unless requires a single argument`); + } + + builder.startLabels(); + + builder.pushFrame(); + + builder.returnTo('END'); + + expr(params[0], builder); + + builder.toBoolean(); + + builder.enter(1); + + builder.jumpIf('ELSE'); + + builder.invokeStaticBlock(unwrap(template)); + + if (inverse) { + builder.jump('EXIT'); + + builder.label('ELSE'); + builder.invokeStaticBlock(inverse); + + builder.label('EXIT'); + builder.exit(); + builder.return(); + } else { + builder.label('ELSE'); + builder.exit(); + builder.return(); + } + + builder.label('END'); + builder.popFrame(); + + builder.stopLabels(); + }); + + blocks.add('with', (params, _hash, template, inverse, builder) => { + // PutArgs + // Test(Environment) + // Enter(BEGIN, END) + // BEGIN: Noop + // JumpUnless(ELSE) + // Evaluate(default) + // Jump(END) + // ELSE: Noop + // Evalulate(inverse) + // END: Noop + // Exit + + if (!params || params.length !== 1) { + throw new Error(`SYNTAX ERROR: #with requires a single argument`); + } + + builder.startLabels(); + + builder.pushFrame(); + + builder.returnTo('END'); + + expr(params[0], builder); + + builder.dup(); + builder.toBoolean(); + + builder.enter(2); + + builder.jumpUnless('ELSE'); + + builder.invokeStaticBlock(unwrap(template), 1); + + if (inverse) { + builder.jump('EXIT'); + + builder.label('ELSE'); + builder.invokeStaticBlock(inverse); + + builder.label('EXIT'); + builder.exit(); + builder.return(); + } else { + builder.label('ELSE'); + builder.exit(); + builder.return(); + } + + builder.label('END'); + builder.popFrame(); + + builder.stopLabels(); + }); + + blocks.add('each', (params, hash, template, inverse, builder) => { + // Enter(BEGIN, END) + // BEGIN: Noop + // PutArgs + // PutIterable + // JumpUnless(ELSE) + // EnterList(BEGIN2, END2) + // ITER: Noop + // NextIter(BREAK) + // BEGIN2: Noop + // PushChildScope + // Evaluate(default) + // PopScope + // END2: Noop + // Exit + // Jump(ITER) + // BREAK: Noop + // ExitList + // Jump(END) + // ELSE: Noop + // Evalulate(inverse) + // END: Noop + // Exit + + builder.startLabels(); + + builder.pushFrame(); + + builder.returnTo('END'); + + if (hash && hash[0][0] === 'key') { + expr(hash[1][0], builder); + } else { + builder.pushPrimitiveReference(null); + } + + expr(params[0], builder); + + builder.enter(2); + + builder.putIterator(); + + builder.jumpUnless('ELSE'); + + builder.pushFrame(); + + builder.returnTo('ITER'); + + builder.dup(Register.fp, 1); + + builder.enterList('BODY'); + + builder.label('ITER'); + builder.iterate('BREAK'); + + builder.label('BODY'); + builder.invokeStaticBlock(unwrap(template), 2); + builder.pop(2); + builder.exit(); + builder.return(); + + builder.label('BREAK'); + builder.exitList(); + builder.popFrame(); + + if (inverse) { + builder.jump('EXIT'); + + builder.label('ELSE'); + builder.invokeStaticBlock(inverse); + + builder.label('EXIT'); + builder.exit(); + builder.return(); + } else { + builder.label('ELSE'); + builder.exit(); + builder.return(); + } + + builder.label('END'); + builder.popFrame(); + + builder.stopLabels(); + }); + + blocks.add('-in-element', (params, hash, template, _inverse, builder) => { + if (!params || params.length !== 1) { + throw new Error(`SYNTAX ERROR: #-in-element requires a single argument`); + } + + builder.startLabels(); + + builder.pushFrame(); + + builder.returnTo('END'); + + if (hash && hash[0].length) { + let [ keys, values ] = hash; + + if (keys.length === 1 && keys[0] === 'nextSibling') { + expr(values[0], builder); + } else { + throw new Error(`SYNTAX ERROR: #-in-element does not take a \`${keys[0]}\` option`); + } + } else { + expr(null, builder); + } + + expr(params[0], builder); + + builder.dup(); + + builder.enter(3); + + builder.jumpUnless('ELSE'); + + builder.pushRemoteElement(); + builder.invokeStaticBlock(unwrap(template)); + builder.popRemoteElement(); + + builder.label('ELSE'); + builder.exit(); + builder.return(); + + builder.label('END'); + builder.popFrame(); + + builder.stopLabels(); + }); + + blocks.add('-with-dynamic-vars', (_params, hash, template, _inverse, builder) => { + if (hash) { + let [names, expressions] = hash; + + builder.compileParams(expressions); + + builder.pushDynamicScope(); + builder.bindDynamicScope(names); + builder.invokeStaticBlock(unwrap(template)); + builder.popDynamicScope(); + } else { + builder.invokeStaticBlock(unwrap(template)); + } + }); + + blocks.add('component', (_params, hash, template, inverse, builder) => { + assert(_params && _params.length, 'SYNTAX ERROR: #component requires at least one argument'); + + let [definition, ...params] = _params!; + builder.dynamicComponent(definition, params, hash, true, template, inverse); + }); + + inlines.add('component', (_name, _params, hash, builder) => { + assert(_params && _params.length, 'SYNTAX ERROR: component helper requires at least one argument'); + + let [definition, ...params] = _params!; + builder.dynamicComponent(definition, params, hash, true, null, null); + + return true; + }); + + return { blocks, inlines }; +} + +export function compileStatement(statement: WireFormat.Statement, builder: OpcodeBuilder) { + STATEMENTS.compile(statement, builder); +} + +export function compileStatements(statements: WireFormat.Statement[], meta: CompilationMeta, env: CompilationOptions): { commit(heap: Heap): Handle } { + let b = new LazyOpcodeBuilder(env, meta); + + for (let i = 0; i < statements.length; i++) { + compileStatement(statements[i], b); + } + + return b; +} diff --git a/packages/@glimmer/opcode-compiler/package.json b/packages/@glimmer/opcode-compiler/package.json new file mode 100644 index 0000000000..da57beef4b --- /dev/null +++ b/packages/@glimmer/opcode-compiler/package.json @@ -0,0 +1,15 @@ +{ + "name": "@glimmer/opcode-compiler", + "version": "0.26.2", + "repository": "https://github.com/glimmerjs/glimmer-vm/tree/master/packages/@glimmer/opcode-compiler", + "dependencies": { + "@glimmer/syntax": "^0.26.2", + "@glimmer/util": "^0.26.2", + "@glimmer/wire-format": "^0.26.2", + "@glimmer/interfaces": "^0.26.2", + "simple-html-tokenizer": "^0.3.0" + }, + "devDependencies": { + "typescript": "^2.2.0" + } +} diff --git a/packages/@glimmer/runtime/lib/compiled/opcodes/builder.ts b/packages/@glimmer/runtime/lib/compiled/opcodes/builder.ts index bde24e33b2..a7ffa18957 100644 --- a/packages/@glimmer/runtime/lib/compiled/opcodes/builder.ts +++ b/packages/@glimmer/runtime/lib/compiled/opcodes/builder.ts @@ -2,10 +2,9 @@ import { CompilationMeta, Opaque, Option, ProgramSymbolTable, SymbolTable, Uniqu import { dict, EMPTY_ARRAY, expect, fillNulls, Stack, typePos, unreachable } from '@glimmer/util'; import { Op, Register } from '@glimmer/vm'; import * as WireFormat from '@glimmer/wire-format'; -import { Handle, Heap } from '../../environment'; +import { Handle, Heap, Program } from '../../environment'; import { ConstantArray, - ConstantOther, Constants, ConstantString, ConstantFloat, @@ -19,7 +18,7 @@ import RawInlineBlock from '../../syntax/raw-block'; import { TemplateMeta } from "@glimmer/wire-format"; import { ComponentBuilder } from "../../compiler"; import { ComponentDefinition } from '../../component/interfaces'; -import { CompilationOptions, Specifier } from "../../internal-interfaces"; +import { Specifier } from "../../internal-interfaces"; export interface CompilesInto { compile(builder: OpcodeBuilder): E; @@ -63,8 +62,8 @@ export abstract class OpcodeBuilder { } export class ComponentBuilder implements IComponentBuilder { - private options: InternalCompilationOptions; - - constructor(private builder: OpcodeBuilderDSL) { - this.options = builder.options; - } + constructor(private builder: OpcodeBuilderDSL) {} static(definition: Opaque, args: ComponentArgs) { let [params, hash, _default, inverse] = args; diff --git a/packages/@glimmer/runtime/lib/component/interfaces.ts b/packages/@glimmer/runtime/lib/component/interfaces.ts index 9d02586605..03badb2a4b 100644 --- a/packages/@glimmer/runtime/lib/component/interfaces.ts +++ b/packages/@glimmer/runtime/lib/component/interfaces.ts @@ -1,6 +1,7 @@ import { Simple, Dict, Opaque, Option, Resolver, Unique } from '@glimmer/interfaces'; import { Tag, VersionedPathReference } from '@glimmer/reference'; import { Destroyable } from '@glimmer/util'; +import { ComponentCapabilities } from '@glimmer/opcode-compiler'; import Bounds from '../bounds'; import { ElementOperations } from '../vm/element-builder'; import Environment, { DynamicScope } from '../environment'; @@ -107,15 +108,6 @@ export function isComponentDefinition>(obj: Opaque): obj return typeof obj === 'object' && obj !== null && obj[COMPONENT_DEFINITION_BRAND]; } -export interface ComponentCapabilities { - dynamicLayout: boolean; - dynamicTag: boolean; - prepareArgs: boolean; - createArgs: boolean; - attributeHook: boolean; - elementHook: boolean; -} - const ALL_CAPABILITIES: ComponentCapabilities = { dynamicLayout: true, dynamicTag: true, diff --git a/packages/@glimmer/runtime/lib/syntax/compilable-template.ts b/packages/@glimmer/runtime/lib/syntax/compilable-template.ts index f3b06d2e95..86a8d107c8 100644 --- a/packages/@glimmer/runtime/lib/syntax/compilable-template.ts +++ b/packages/@glimmer/runtime/lib/syntax/compilable-template.ts @@ -3,7 +3,7 @@ import { SymbolTable } from '@glimmer/interfaces'; import { Statement } from '@glimmer/wire-format'; -import { Handle } from '../environment'; +import { Handle, Program } from '../environment'; import { debugSlice } from '../opcodes'; import { compileStatements } from './functions'; import { DEBUG } from '@glimmer/local-debug-flags'; @@ -15,19 +15,19 @@ export { ICompilableTemplate }; export default class CompilableTemplate implements ICompilableTemplate { private compiled: Option = null; - constructor(public statements: Statement[], public symbolTable: S, private options: CompilationOptions) {} + constructor(public statements: Statement[], public symbolTable: S, private program: Program) {} compile(): Handle { let { compiled } = this; if (compiled !== null) return compiled; - let { options } = this; + let { program } = this; - let builder = compileStatements(this.statements, this.symbolTable.meta, options); - let handle = builder.commit(options.program.heap); + let builder = compileStatements(this.statements, this.symbolTable.meta, program); + let handle = builder.commit(program.heap); if (DEBUG) { - let { program, program: { heap } } = options; + let { heap } = program; let start = heap.getaddr(handle); let end = start + heap.sizeof(handle); debugSlice(program, start, end); diff --git a/packages/@glimmer/runtime/lib/syntax/functions.ts b/packages/@glimmer/runtime/lib/syntax/functions.ts index 4b7c74d6ce..3d764e59f7 100644 --- a/packages/@glimmer/runtime/lib/syntax/functions.ts +++ b/packages/@glimmer/runtime/lib/syntax/functions.ts @@ -3,7 +3,7 @@ import { assert, dict, EMPTY_ARRAY, unwrap } from '@glimmer/util'; import { Register } from '@glimmer/vm'; import * as WireFormat from '@glimmer/wire-format'; import OpcodeBuilder, { LazyOpcodeBuilder } from '../compiled/opcodes/builder'; -import { Handle, Heap } from '../environment'; +import { Handle, Heap, Program } from '../environment'; import { hasStaticLayout } from '../component/interfaces'; import { CompilationOptions, ComponentDefinition } from '../internal-interfaces'; import * as ClientSide from './client-side'; @@ -780,8 +780,8 @@ export function compileStatement(statement: WireFormat.Statement, builder: Opcod STATEMENTS.compile(statement, builder); } -export function compileStatements(statements: WireFormat.Statement[], meta: CompilationMeta, env: CompilationOptions): { commit(heap: Heap): Handle } { - let b = new LazyOpcodeBuilder(env, meta); +export function compileStatements(statements: WireFormat.Statement[], meta: CompilationMeta, program: Program): { commit(heap: Heap): Handle } { + let b = new LazyOpcodeBuilder(program, meta); for (let i = 0; i < statements.length; i++) { compileStatement(statements[i], b); diff --git a/packages/@glimmer/runtime/package.json b/packages/@glimmer/runtime/package.json index 1e008d786c..2b7645d54d 100644 --- a/packages/@glimmer/runtime/package.json +++ b/packages/@glimmer/runtime/package.json @@ -11,11 +11,10 @@ "@glimmer/object-reference": "^0.27.0", "@glimmer/wire-format": "^0.27.0", "@glimmer/interfaces": "^0.27.0", - "@glimmer/vm": "^0.27.0" + "@glimmer/opcode-compiler": "^0.27.0" }, "devDependencies": { "@types/qunit": "^2.0.31", - "@glimmer/local-debug-flags": "^0.27.0", "typescript": "^2.2.0" } -} \ No newline at end of file +} diff --git a/packages/@glimmer/runtime/test/template-test.ts b/packages/@glimmer/runtime/test/template-test.ts index 87346e33b8..00f5b611f3 100644 --- a/packages/@glimmer/runtime/test/template-test.ts +++ b/packages/@glimmer/runtime/test/template-test.ts @@ -1,6 +1,6 @@ import { TestEnvironment } from "@glimmer/test-helpers"; import { templateFactory } from "@glimmer/runtime"; -import { precompile } from "@glimmer/compiler"; +import { Templates } from "@glimmer/bundle-compiler"; import { SerializedTemplateWithLazyBlock, TemplateMeta } from "@glimmer/wire-format"; let env: TestEnvironment; @@ -21,17 +21,32 @@ let serializedTemplateNoId: SerializedTemplateWithLazyBlock; QUnit.module("templateFactory", { beforeEach() { env = new TestEnvironment(); - let templateJs = precompile("
{{name}}
", { + + let templates = new Templates(); + + templates.compile("
{{name}}
", { meta: { version: 12, lang: 'es', moduleName: "template/module/name" - } + }, + + plugins: [], + lookup: "template/module/name" }); - serializedTemplate = JSON.parse(templateJs); - serializedTemplate.id = 'server-id-1'; - serializedTemplateNoId = JSON.parse(templateJs); + let templateJs: string; + + templates.eachTemplate(template => { + templateJs = template; + }); + + // in the browser, require('crypto') will fail, and all we're testing + // here is that the factory scrapes the id off correctly. + serializedTemplate = JSON.parse(templateJs!); + serializedTemplate.id = "server-id-1"; + + serializedTemplateNoId = JSON.parse(templateJs!); serializedTemplateNoId.id = null; } }); From 080eeddfd25d9f8f3294cd01a68760a0a57ef382 Mon Sep 17 00:00:00 2001 From: Yehuda Katz Date: Thu, 3 Aug 2017 17:20:14 -0700 Subject: [PATCH 03/42] [BREAKING] Extract Opcode Compiler into its own package This commit is further preparation for eager mode. Previously, the opcode compiler was part of the runtime package. This meant that it inadvertantly had a number of dependencies (minor and major) on concepts in the runtime package. In some cases, those dependencies were inappropriate. In other cases, they simply didn't belong in the runtime package. This commit moves the entire opcode compiler into its own self-contained package. Some notable improvements: - There is now a `ParsedLayout` type that represents the deserialized template from the wire format. For silly reasons, this type didn't exist before, and the constituent parts of the parsed layout were passed around in an ad-hoc fashion. Moving to this type simplified many of the surrounding objects considerably. - There is now a `TemplateOptions` type that reflects what is needed to compile a template: the Program (array of int32) to compile into, any syntactic macros, and a static module lookup interface. - There is now a `CompileTimeLookup` interface that is a subset of the `Resolver` interface and is limited to static information only. Previously, the compiler had access to instantiate runtime objects, which it only used to get (statically available, ideally) information about component capabilities. That access was refactored into two new methods: getCapabilities and getLayout, which are expected to be resolvable statically in the future. - The debugging aspects of the opcode compiler (including the code to disassemble and log a program) were moved into the opcode-compiler package. --- .../@glimmer/bundle-compiler/lib/compiler.ts | 44 +- packages/@glimmer/interfaces/lib/core.d.ts | 3 +- .../interfaces/lib/tier1/symbol-table.d.ts | 8 +- packages/@glimmer/opcode-compiler/index.ts | 30 + .../opcode-compiler/lib/client-side.ts | 23 + .../lib/compilable-template.ts | 54 ++ .../@glimmer/opcode-compiler/lib/debug.ts | 188 ++++ .../opcode-compiler/lib/interfaces.ts | 40 +- .../opcode-compiler/lib/opcode-builder.ts | 62 +- .../@glimmer/opcode-compiler/lib/syntax.ts | 88 +- .../lib/wrapped-component.ts} | 62 +- packages/@glimmer/runtime/index.ts | 36 +- .../runtime/lib/compiled/opcodes/builder.ts | 858 ------------------ .../runtime/lib/compiled/opcodes/component.ts | 5 +- packages/@glimmer/runtime/lib/environment.ts | 2 +- .../runtime/lib/environment/lookup.ts | 46 + .../@glimmer/runtime/lib/opcode-builder.ts | 10 +- packages/@glimmer/runtime/lib/opcodes.ts | 186 +--- packages/@glimmer/runtime/lib/scanner.ts | 21 - .../runtime/lib/syntax/compilable-template.ts | 38 - .../@glimmer/runtime/lib/syntax/functions.ts | 791 ---------------- .../@glimmer/runtime/lib/syntax/macros.ts | 12 - .../@glimmer/runtime/lib/syntax/raw-block.ts | 19 - packages/@glimmer/runtime/lib/template.ts | 71 +- .../@glimmer/test-helpers/lib/environment.ts | 91 +- 25 files changed, 620 insertions(+), 2168 deletions(-) create mode 100644 packages/@glimmer/opcode-compiler/lib/client-side.ts create mode 100644 packages/@glimmer/opcode-compiler/lib/compilable-template.ts create mode 100644 packages/@glimmer/opcode-compiler/lib/debug.ts rename packages/@glimmer/{runtime/lib/compiler.ts => opcode-compiler/lib/wrapped-component.ts} (59%) delete mode 100644 packages/@glimmer/runtime/lib/compiled/opcodes/builder.ts create mode 100644 packages/@glimmer/runtime/lib/environment/lookup.ts delete mode 100644 packages/@glimmer/runtime/lib/scanner.ts delete mode 100644 packages/@glimmer/runtime/lib/syntax/compilable-template.ts delete mode 100644 packages/@glimmer/runtime/lib/syntax/functions.ts delete mode 100644 packages/@glimmer/runtime/lib/syntax/macros.ts delete mode 100644 packages/@glimmer/runtime/lib/syntax/raw-block.ts diff --git a/packages/@glimmer/bundle-compiler/lib/compiler.ts b/packages/@glimmer/bundle-compiler/lib/compiler.ts index d962181b53..f2953dee0f 100644 --- a/packages/@glimmer/bundle-compiler/lib/compiler.ts +++ b/packages/@glimmer/bundle-compiler/lib/compiler.ts @@ -1,26 +1,26 @@ -import { CompilationOptions, Program } from '@glimmer/runtime'; -import { Resolver } from "@glimmer/interfaces"; -import { TemplateMeta } from "@glimmer/wire-format"; -import { preprocess, ASTPluginBuilder } from "@glimmer/syntax"; +// import { CompilationOptions, Program } from '@glimmer/runtime'; +// import { Resolver } from "@glimmer/interfaces"; +// import { TemplateMeta } from "@glimmer/wire-format"; +// import { preprocess, ASTPluginBuilder } from "@glimmer/syntax"; -export interface EagerCompilerOptions { - meta: T; - plugins: ASTPluginBuilder[]; - id: string; -} +// export interface EagerCompilerOptions { +// meta: T; +// plugins: ASTPluginBuilder[]; +// id: string; +// } -export class EagerCompiler> { - private program: Program; - private resolver: R; +// export class EagerCompiler> { +// private program: Program; +// private resolver: R; - constructor(private options: CompilationOptions) { - this.program = options.program; - this.resolver = options.resolver; - } +// constructor(private options: CompilationOptions) { +// this.program = options.program; +// this.resolver = options.resolver; +// } - compile(input: string, options: EagerCompilerOptions) { - let ast = preprocess(input, options); - let { block, meta } = TemplateCompiler.compile(options, ast); - let { id } = options; - } -} +// compile(input: string, options: EagerCompilerOptions) { +// let ast = preprocess(input, options); +// let { block, meta } = TemplateCompiler.compile(options, ast); +// let { id } = options; +// } +// } diff --git a/packages/@glimmer/interfaces/lib/core.d.ts b/packages/@glimmer/interfaces/lib/core.d.ts index 7fb883c6d5..b8115d6cac 100644 --- a/packages/@glimmer/interfaces/lib/core.d.ts +++ b/packages/@glimmer/interfaces/lib/core.d.ts @@ -1,4 +1,5 @@ -export type Opaque = {} | void | null | undefined; +export type Present = {} | void; +export type Opaque = Present | null | undefined; export type Option = T | null; export type Maybe = Option | undefined | void; export type FIXME = T; diff --git a/packages/@glimmer/interfaces/lib/tier1/symbol-table.d.ts b/packages/@glimmer/interfaces/lib/tier1/symbol-table.d.ts index 06b8524d1d..593865bc9b 100644 --- a/packages/@glimmer/interfaces/lib/tier1/symbol-table.d.ts +++ b/packages/@glimmer/interfaces/lib/tier1/symbol-table.d.ts @@ -4,14 +4,8 @@ import { TemplateMeta } from '@glimmer/wire-format'; export interface Symbols { } -export interface CompilationMeta { - symbols: string[]; - templateMeta: TemplateMeta; - asPartial: boolean; -} - export interface SymbolTable { - meta: CompilationMeta; + meta: TemplateMeta; } export interface ProgramSymbolTable extends SymbolTable { diff --git a/packages/@glimmer/opcode-compiler/index.ts b/packages/@glimmer/opcode-compiler/index.ts index 20b4812f8a..312477d59f 100644 --- a/packages/@glimmer/opcode-compiler/index.ts +++ b/packages/@glimmer/opcode-compiler/index.ts @@ -1 +1,31 @@ export * from './lib/interfaces'; + +export { + ATTRS_BLOCK, + CompileOptions, + Macros, + TemplateOptions, + compileStatements +} from './lib/syntax'; + +export { + AbstractTemplate, + CompileTimeLookup, + LazyOpcodeBuilder, + OpcodeBuilder +} from './lib/opcode-builder'; + +export { + default as CompilableTemplate, + ICompilableTemplate +} from './lib/compilable-template'; + +export { + debug, + debugSlice, + logOpcode +} from './lib/debug'; + +export { + WrappedBuilder, +} from './lib/wrapped-component'; diff --git a/packages/@glimmer/opcode-compiler/lib/client-side.ts b/packages/@glimmer/opcode-compiler/lib/client-side.ts new file mode 100644 index 0000000000..a67a06ccb9 --- /dev/null +++ b/packages/@glimmer/opcode-compiler/lib/client-side.ts @@ -0,0 +1,23 @@ +import { Ops as WireFormatOps } from '@glimmer/wire-format'; + +export enum Ops { + OpenComponentElement, + DidCreateElement, + SetComponentAttrs, + DidRenderLayout, + Debugger +} + +import ClientSideStatement = WireFormatOps.ClientSideStatement; + +export type OpenComponentElement = [ClientSideStatement, Ops.OpenComponentElement, string]; +export type DidCreateElement = [ClientSideStatement, Ops.DidCreateElement]; +export type SetComponentAttrs = [ClientSideStatement, Ops.SetComponentAttrs, boolean]; +export type DidRenderLayout = [ClientSideStatement, Ops.DidRenderLayout]; + +export type ClientSideStatement = + OpenComponentElement + | DidCreateElement + | SetComponentAttrs + | DidRenderLayout + ; diff --git a/packages/@glimmer/opcode-compiler/lib/compilable-template.ts b/packages/@glimmer/opcode-compiler/lib/compilable-template.ts new file mode 100644 index 0000000000..da2e41a804 --- /dev/null +++ b/packages/@glimmer/opcode-compiler/lib/compilable-template.ts @@ -0,0 +1,54 @@ +import { + Option, + SymbolTable +} from '@glimmer/interfaces'; +import { Statement, SerializedInlineBlock } from '@glimmer/wire-format'; +import { DEBUG } from '@glimmer/local-debug-flags'; +import { debugSlice } from './debug'; +import { Handle } from './interfaces'; +import { CompilableTemplate as ICompilableTemplate, BlockSyntax, ParsedLayout } from './interfaces'; +import { CompileOptions, compileStatements } from './syntax';; + +export { ICompilableTemplate }; + +export default class CompilableTemplate implements ICompilableTemplate { + private compiled: Option = null; + + constructor(private statements: Statement[], private containingLayout: ParsedLayout, private options: CompileOptions, public symbolTable: S) {} + + compile(): Handle { + let { compiled } = this; + if (compiled !== null) return compiled; + + let { options, statements, containingLayout } = this; + let { program } = options; + + let builder = compileStatements(statements, containingLayout, options); + let handle = builder.commit(program.heap); + + if (DEBUG) { + let { heap } = program; + let start = heap.getaddr(handle); + let end = start + heap.sizeof(handle); + debugSlice(program, start, end); + } + + return (this.compiled = handle); + } +} + +// TODO: Kill +export class RawInlineBlock { + constructor( + private parsedBlock: SerializedInlineBlock, + private containingLayout: ParsedLayout, + private options: CompileOptions + ) { + } + + scan(): BlockSyntax { + let { parameters, statements } = this.parsedBlock; + let symbolTable = { parameters, meta: this.containingLayout.meta }; + return new CompilableTemplate(statements, this.containingLayout, this.options, symbolTable); + } +} diff --git a/packages/@glimmer/opcode-compiler/lib/debug.ts b/packages/@glimmer/opcode-compiler/lib/debug.ts new file mode 100644 index 0000000000..886ae4ff6a --- /dev/null +++ b/packages/@glimmer/opcode-compiler/lib/debug.ts @@ -0,0 +1,188 @@ +import { Program, Constants } from './interfaces'; +import { Option, Opaque, SymbolTable, Recast } from '@glimmer/interfaces'; +import { Op, Register } from '@glimmer/vm'; +import { DEBUG, CI } from '@glimmer/local-debug-flags'; +import { unreachable } from "@glimmer/util"; + +export interface DebugConstants { + getString(value: number): string; + getStringArray(value: number): string[]; + getArray(value: number): number[]; + getSymbolTable(value: number): T; + getSerializable(s: number): T; + resolveSpecifier(s: number): T; +} + +interface LazyDebugConstants { + getOther(s: number): T; +} + +export function debugSlice(program: Program, start: number, end: number) { + if (!CI && DEBUG) { + /* tslint:disable:no-console */ + let { constants } = program; + + // console is not available in IE9 + if (typeof console === 'undefined') { return; } + + // IE10 does not have `console.group` + if (typeof console.group !== 'function') { return; } + + (console as any).group(`%c${start}:${end}`, 'color: #999'); + + for (let i=start; i, type, op1, op2, op3); + console.log(`${i}. ${logOpcode(name, params)}`); + } + + console.groupEnd(); + /* tslint:enable:no-console */ + } +} + +export function logOpcode(type: string, params: Option): string | void { + if (!CI && DEBUG) { + let out = type; + + if (params) { + let args = Object.keys(params).map(p => ` ${p}=${json(params[p])}`).join(''); + out += args; + } + return `(${out})`; + } +} + +function json(param: Opaque) { + if (typeof param === 'function') { + return ''; + } + + let string; + try { + string = JSON.stringify(param); + } catch(e) { + return ''; + } + + if (string === undefined) { + return 'undefined'; + } + + let debug = JSON.parse(string); + if (typeof debug === 'object' && debug !== null && debug.GlimmerDebug !== undefined) { + return debug.GlimmerDebug; + } + + return string; +} + +export function debug(c: DebugConstants, op: Op, op1: number, op2: number, op3: number): [string, object] { + if (!CI && DEBUG) { + switch (op) { + case Op.Bug: throw unreachable(); + + case Op.Helper: return ['Helper', { helper: c.resolveSpecifier(op1) }]; + case Op.SetVariable: return ['SetVariable', { symbol: op1 }]; + case Op.SetBlock: return ['SetBlock', { symbol: op1 }]; + case Op.GetVariable: return ['GetVariable', { symbol: op1 }]; + case Op.GetProperty: return ['GetProperty', { key: c.getString(op1) }]; + case Op.GetBlock: return ['GetBlock', { symbol: op1 }]; + case Op.HasBlock: return ['HasBlock', { block: op1 }]; + case Op.HasBlockParams: return ['HasBlockParams', { block: op1 }]; + case Op.Concat: return ['Concat', { size: op1 }]; + case Op.Constant: return ['Constant', { value: (c as Recast).getOther(op1) }]; + case Op.Primitive: return ['Primitive', { primitive: op1 }]; + case Op.PrimitiveReference: return ['PrimitiveReference', {}]; + case Op.Dup: return ['Dup', { register: Register[op1], offset: op2 }]; + case Op.Pop: return ['Pop', { count: op1 }]; + case Op.Load: return ['Load', { register: Register[op1] }]; + case Op.Fetch: return ['Fetch', { register: Register[op1] }]; + + /// PRELUDE & EXIT + case Op.RootScope: return ['RootScope', { symbols: op1, bindCallerScope: !!op2 }]; + case Op.ChildScope: return ['ChildScope', {}]; + case Op.PopScope: return ['PopScope', {}]; + case Op.Return: return ['Return', {}]; + case Op.ReturnTo: return ['ReturnTo', { offset: op1 }]; + + /// HTML + case Op.Text: return ['Text', { text: c.getString(op1) }]; + case Op.Comment: return ['Comment', { comment: c.getString(op1) }]; + case Op.DynamicContent: return ['DynamicContent', { trusting: !!op1 }]; + case Op.OpenElement: return ['OpenElement', { tag: c.getString(op1) }]; + case Op.OpenElementWithOperations: return ['OpenElementWithOperations', { tag: c.getString(op1) }]; + case Op.OpenDynamicElement: return ['OpenDynamicElement', {}]; + case Op.StaticAttr: return ['StaticAttr', { name: c.getString(op1), value: c.getString(op2), namespace: op3 ? c.getString(op3) : null }]; + case Op.DynamicAttr: return ['DynamicAttr', { name: c.getString(op1), trusting: !!op2, namespace: op3 ? c.getString(op3) : null }]; + case Op.ComponentAttr: return ['ComponentAttr', { name: c.getString(op1), trusting: !!op2, namespace: op3 ? c.getString(op3) : null }]; + case Op.FlushElement: return ['FlushElement', {}]; + case Op.CloseElement: return ['CloseElement', {}]; + + /// MODIFIER + case Op.Modifier: return ['Modifier', {}]; + + /// WORMHOLE + case Op.PushRemoteElement: return ['PushRemoteElement', {}]; + case Op.PopRemoteElement: return ['PopRemoteElement', {}]; + + /// DYNAMIC SCOPE + case Op.BindDynamicScope: return ['BindDynamicScope', {}]; + case Op.PushDynamicScope: return ['PushDynamicScope', {}]; + case Op.PopDynamicScope: return ['PopDynamicScope', {}]; + + /// VM + case Op.CompileBlock: return ['CompileBlock', {}]; + case Op.InvokeStatic: return ['InvokeStatic', {}]; + case Op.InvokeYield: return ['InvokeYield', {}]; + case Op.Jump: return ['Jump', { to: op1 }]; + case Op.JumpIf: return ['JumpIf', { to: op1 }]; + case Op.JumpUnless: return ['JumpUnless', { to: op1 }]; + case Op.PushFrame: return ['PushFrame', {}]; + case Op.PopFrame: return ['PopFrame', {}]; + case Op.Enter: return ['Enter', { args: op1 }]; + case Op.Exit: return ['Exit', {}]; + case Op.ToBoolean: return ['ToBoolean', {}]; + + /// LISTS + case Op.EnterList: return ['EnterList', { start: op1 }]; + case Op.ExitList: return ['ExitList', {}]; + case Op.PutIterator: return ['PutIterator', {}]; + case Op.Iterate: return ['Iterate', { end: op1 }]; + + /// COMPONENTS + case Op.IsComponent: return ['IsComponent', {}]; + case Op.CurryComponent: return ['CurryComponent', { meta: c.getSerializable(op1) }]; + case Op.PushComponentManager: return ['PushComponentManager', { definition: c.resolveSpecifier(op1) }]; + case Op.PushDynamicComponentManager: return ['PushDynamicComponentManager', { meta: c.getSerializable(op1) }]; + case Op.PushArgs: return ['PushArgs', { names: c.getStringArray(op1), positionals: op2, synthetic: !!op3 }]; + case Op.PrepareArgs: return ['PrepareArgs', { state: Register[op1] }]; + case Op.CreateComponent: return ['CreateComponent', { flags: op1, state: Register[op2] }]; + case Op.RegisterComponentDestructor: return ['RegisterComponentDestructor', {}]; + case Op.PutComponentOperations: return ['PutComponentOperations', {}]; + case Op.GetComponentSelf: return ['GetComponentSelf', { state: Register[op1] }]; + case Op.GetComponentTagName: return ['GetComponentTagName', { state: Register[op1] }]; + case Op.GetComponentLayout: return ['GetComponentLayout', { state: Register[op1] }]; + case Op.InvokeComponentLayout: return ['InvokeComponentLayout', {}]; + case Op.BeginComponentTransaction: return ['BeginComponentTransaction', {}]; + case Op.CommitComponentTransaction: return ['CommitComponentTransaction', {}]; + case Op.DidCreateElement: return ['DidCreateElement', { state: Register[op1] }]; + case Op.DidRenderLayout: return ['DidRenderLayout', {}]; + + /// PARTIALS + case Op.InvokePartial: return ['InvokePartial', { templateMeta: c.getSerializable(op1), symbols: c.getStringArray(op2), evalInfo: c.getArray(op3) }]; + case Op.ResolveMaybeLocal: return ['ResolveMaybeLocal', { name: c.getString(op1)} ]; + + /// DEBUGGER + case Op.Debugger: return ['Debugger', { symbols: c.getStringArray(op1), evalInfo: c.getArray(op2) }]; + + /// STATEMENTS + + case Op.Size: throw unreachable(); + } + + throw unreachable(); + } + + return ['', {}]; +} diff --git a/packages/@glimmer/opcode-compiler/lib/interfaces.ts b/packages/@glimmer/opcode-compiler/lib/interfaces.ts index 442c820a82..2f3f449ef9 100644 --- a/packages/@glimmer/opcode-compiler/lib/interfaces.ts +++ b/packages/@glimmer/opcode-compiler/lib/interfaces.ts @@ -1,7 +1,7 @@ import { Unique, Opaque, SymbolTable, Option, BlockSymbolTable } from "@glimmer/interfaces"; import { VersionedPathReference } from "@glimmer/reference"; -import { Core as C, Statements as S, Expression, Core } from "@glimmer/wire-format"; -import { OpcodeBuilder } from './opcode-builder'; +import { Core, SerializedTemplateBlock, TemplateMeta } from "@glimmer/wire-format"; +import { Macros } from './syntax'; export type Handle = Unique<"Handle">; @@ -9,6 +9,10 @@ export interface Heap { push(name: /* TODO: Op */ number, op1?: number, op2?: number, op3?: number): void; malloc(): Handle; finishMalloc(handle: Handle): void; + + // for debugging + getaddr(handle: Handle): number; + sizeof(handle: Handle): number; } export interface ComponentCapabilities { @@ -30,38 +34,12 @@ export interface EagerCompilationOptions { symbolTable: S; compile(): Handle; } -export interface Macros { - blocks: Blocks; - inlines: Inlines; -} - export type BlockSyntax = CompilableTemplate; -export type BlockMacro = (params: C.Params, hash: C.Hash, template: Option, inverse: Option, builder: OpcodeBuilder) => void; -export type MissingBlockMacro = (name: string, params: C.Params, hash: C.Hash, template: Option, inverse: Option, builder: OpcodeBuilder) => void; - -export interface Blocks { - add(name: string, func: BlockMacro): void; - addMissing(func: MissingBlockMacro): void; - compile(name: string, params: C.Params, hash: C.Hash, template: Option, inverse: Option, builder: OpcodeBuilder): void; -} - -export type AppendSyntax = S.Append; -export type AppendMacro = (name: string, params: Option, hash: Option, builder: OpcodeBuilder) => ['expr', Expression] | true | false; - -export interface Inlines { - add(name: string, func: AppendMacro): void; - addMissing(func: AppendMacro): void; - compile(sexp: AppendSyntax, builder: OpcodeBuilder): ['expr', Expression] | true; -} export interface Program { [key: number]: never; @@ -102,3 +80,9 @@ export type Specifier = Opaque; export interface ComponentBuilder { static(definition: Specifier, args: ComponentArgs): void; } + +export interface ParsedLayout { + id?: Option; + block: SerializedTemplateBlock; + meta: TemplateMeta; +} diff --git a/packages/@glimmer/opcode-compiler/lib/opcode-builder.ts b/packages/@glimmer/opcode-compiler/lib/opcode-builder.ts index f7fb2ca9b8..0a72bd08a6 100644 --- a/packages/@glimmer/opcode-compiler/lib/opcode-builder.ts +++ b/packages/@glimmer/opcode-compiler/lib/opcode-builder.ts @@ -1,8 +1,8 @@ -import { CompilationMeta, Opaque, Option, ProgramSymbolTable, SymbolTable } from '@glimmer/interfaces'; +import { Opaque, Option, ProgramSymbolTable, SymbolTable, Present } from '@glimmer/interfaces'; import { dict, EMPTY_ARRAY, expect, fillNulls, Stack, typePos, unreachable } from '@glimmer/util'; import { Op, Register } from '@glimmer/vm'; import * as WireFormat from '@glimmer/wire-format'; -import { TemplateMeta } from "@glimmer/wire-format"; +import { TemplateMeta, SerializedInlineBlock } from "@glimmer/wire-format"; import { Handle, @@ -10,20 +10,27 @@ import { LazyConstants, Primitive, BlockSyntax, - CompilableTemplate, Constants, - RawInlineBlock, Specifier, Program, - ComponentBuilder, - ComponentCapabilities + ComponentCapabilities, + ParsedLayout } from './interfaces'; import { ATTRS_BLOCK, + Macros, expr } from './syntax'; +import CompilableTemplate, { + RawInlineBlock +} from './compilable-template'; + +import { + ComponentBuilder +} from './wrapped-component'; + export interface CompilesInto { compile(builder: OpcodeBuilder): E; } @@ -58,14 +65,36 @@ export interface AbstractTemplate { symbolTable: S; } +export interface CompileTimeLookup { + getCapabilities(name: string, meta: TemplateMeta): ComponentCapabilities; + getLayout(name: string, meta: TemplateMeta): Option<{ symbolTable: ProgramSymbolTable, handle: Handle }>; + + // This interface produces specifiers (and indicates if a name is present), but does not + // produce any actual objects. The main use-case for producing objects is handled above, + // with getCapabilities and getLayout, which drastically shrinks the size of the object + // that the core interface is forced to reify. + lookupHelper(name: string, meta: TemplateMeta): Option; + lookupModifier(name: string, meta: TemplateMeta): Option; + lookupComponent(name: string, meta: TemplateMeta): Option; + lookupPartial(name: string, meta: TemplateMeta): Option; +} + export abstract class OpcodeBuilder = AbstractTemplate> { public constants: Constants; private buffer: number[] = []; private labelsStack = new Stack(); private isComponentAttrs = false; - - constructor(public program: Program, public meta: CompilationMeta, public component: ComponentBuilder) { + public component: ComponentBuilder = new ComponentBuilder(this); + + constructor( + public program: Program, + public lookup: CompileTimeLookup, + public meta: TemplateMeta, + public macros: Macros, + public containingLayout: ParsedLayout, + public asPartial: boolean + ) { this.constants = program.constants; } @@ -507,6 +536,16 @@ export abstract class OpcodeBuilder { + let { containingLayout: { block } } = this; + + return block.hasEval ? block.symbols : null; + } + compileParams(params: Option) { if (!params) return 0; @@ -576,7 +615,7 @@ export abstract class OpcodeBuilder): Option { if (!block) return null; - // TODO: DI the Concrete RawInlineBlock - return new RawInlineBlock(block.statements, block.parameters, this.meta, this.program); + return this.inlineBlock(block); } } diff --git a/packages/@glimmer/opcode-compiler/lib/syntax.ts b/packages/@glimmer/opcode-compiler/lib/syntax.ts index db76c55b2b..aa7d8f6e0d 100644 --- a/packages/@glimmer/opcode-compiler/lib/syntax.ts +++ b/packages/@glimmer/opcode-compiler/lib/syntax.ts @@ -1,13 +1,11 @@ -import { CompilationMeta, Option, ProgramSymbolTable } from '@glimmer/interfaces'; -import { assert, dict, EMPTY_ARRAY, unwrap } from '@glimmer/util'; +import { Option } from '@glimmer/interfaces'; +import { assert, dict, unwrap, EMPTY_ARRAY } from '@glimmer/util'; import { Register } from '@glimmer/vm'; import * as WireFormat from '@glimmer/wire-format'; -import OpcodeBuilder, { LazyOpcodeBuilder } from '../compiled/opcodes/builder'; -import { Handle, Heap } from './interfaces'; -import { ComponentDefinition } from '../internal-interfaces'; import * as ClientSide from './client-side'; -import { BlockSyntax, CompilationOptions } from './interfaces'; -import RawInlineBlock from './raw-block'; +import OpcodeBuilder, { LazyOpcodeBuilder, CompileTimeLookup } from './opcode-builder'; +import { BlockSyntax, Handle, Heap, ParsedLayout, Program } from './interfaces'; + import Ops = WireFormat.Ops; export type TupleSyntax = WireFormat.Statement | WireFormat.TupleExpression; @@ -57,10 +55,10 @@ STATEMENTS.add(Ops.FlushElement, (_sexp: S.FlushElement, builder: OpcodeBuilder) }); STATEMENTS.add(Ops.Modifier, (sexp: S.Modifier, builder: OpcodeBuilder) => { - let { options: { resolver }, meta } = builder; + let { lookup, meta } = builder; let [, name, params, hash] = sexp; - let specifier = resolver.lookupModifier(name, meta.templateMeta); + let specifier = lookup.lookupModifier(name, meta); if (specifier) { builder.compileArgs(params, hash, true); @@ -130,7 +128,7 @@ CLIENT_SIDE.add(ClientSide.Ops.DidRenderLayout, (_sexp: ClientSide.DidRenderLayo STATEMENTS.add(Ops.Append, (sexp: S.Append, builder: OpcodeBuilder) => { let [, value, trusting] = sexp; - let { inlines } = builder.options.macros; + let { inlines } = builder.macros; let returned = inlines.compile(sexp, builder) || value; if (returned === true) return; @@ -158,35 +156,33 @@ STATEMENTS.add(Ops.Block, (sexp: S.Block, builder: OpcodeBuilder) => { let templateBlock = template && template.scan(); let inverseBlock = inverse && inverse.scan(); - let { blocks } = builder.options.macros; + let { blocks } = builder.macros; blocks.compile(name, params, hash, templateBlock, inverseBlock, builder); }); STATEMENTS.add(Ops.Component, (sexp: S.Component, builder: OpcodeBuilder) => { let [, tag, _attrs, args, block] = sexp; - let options = builder.options; - let resolver = options.resolver; - let specifier = resolver.lookupComponent(tag, builder.meta.templateMeta); + let lookup = builder.lookup; + let meta = builder.meta; + let specifier = lookup.lookupComponent(tag, builder.meta); if (specifier) { - let definition = resolver.resolve(specifier); - let manager = definition.manager; + let capabilities = lookup.getCapabilities(tag, meta); let attrs: WireFormat.Statement[] = [ [Ops.ClientSideStatement, ClientSide.Ops.SetComponentAttrs, true], ..._attrs, [Ops.ClientSideStatement, ClientSide.Ops.SetComponentAttrs, false] ]; - let attrsBlock = new RawInlineBlock(attrs, EMPTY_ARRAY, builder.meta, builder.options); + let attrsBlock = builder.inlineBlock({ statements: attrs, parameters: EMPTY_ARRAY }); let child = builder.template(block); - if (hasStaticLayout(definition, manager)) { - let layoutSpecifier = manager.getLayout(definition, resolver); - let layout = resolver.resolve<{ symbolTable: ProgramSymbolTable, template: Handle }>(layoutSpecifier); + if (capabilities.dynamicLayout === false) { + let layout = lookup.getLayout(tag, meta)!; builder.pushComponentManager(specifier); - builder.invokeStaticComponent(definition, layout, attrsBlock, null, args, false, child && child.scan()); + builder.invokeStaticComponent(capabilities, layout, attrsBlock, null, args, false, child && child.scan()); } else { builder.pushComponentManager(specifier); builder.invokeComponent(attrsBlock, null, args, false, child && child.scan()); @@ -199,7 +195,7 @@ STATEMENTS.add(Ops.Component, (sexp: S.Component, builder: OpcodeBuilder) => { STATEMENTS.add(Ops.Partial, (sexp: S.Partial, builder: OpcodeBuilder) => { let [, name, evalInfo] = sexp; - let { meta: { templateMeta, symbols } } = builder; + let { meta } = builder; builder.startLabels(); @@ -215,7 +211,7 @@ STATEMENTS.add(Ops.Partial, (sexp: S.Partial, builder: OpcodeBuilder) => { builder.jumpUnless('ELSE'); - builder.invokePartial(templateMeta, symbols, evalInfo); + builder.invokePartial(meta, builder.evalSymbols()!, evalInfo); builder.popScope(); builder.popFrame(); @@ -246,7 +242,7 @@ STATEMENTS.add(Ops.AttrSplat, (sexp: WireFormat.Statements.AttrSplat, builder: O STATEMENTS.add(Ops.Debugger, (sexp: WireFormat.Statements.Debugger, builder: OpcodeBuilder) => { let [, evalInfo] = sexp; - builder.debugger(builder.meta.symbols, evalInfo); + builder.debugger(builder.evalSymbols()!, evalInfo); }); STATEMENTS.add(Ops.ClientSideStatement, (sexp: WireFormat.Statements.ClientSide, builder: OpcodeBuilder) => { @@ -257,6 +253,7 @@ const EXPRESSIONS = new Compilers(); import E = WireFormat.Expressions; import C = WireFormat.Core; +import { Statement } from "@glimmer/wire-format"; export function expr(expression: WireFormat.Expression, builder: OpcodeBuilder): void { if (Array.isArray(expression)) { @@ -267,15 +264,15 @@ export function expr(expression: WireFormat.Expression, builder: OpcodeBuilder): } EXPRESSIONS.add(Ops.Unknown, (sexp: E.Unknown, builder: OpcodeBuilder) => { - let { options: { resolver }, meta } = builder; + let { lookup, asPartial, meta } = builder; let name = sexp[1]; - let specifier = resolver.lookupHelper(name, meta.templateMeta); + let specifier = lookup.lookupHelper(name, meta); if (specifier) { builder.compileArgs(null, null, true); builder.helper(specifier); - } else if (meta.asPartial) { + } else if (asPartial) { builder.resolveMaybeLocal(name); } else { builder.getVariable(0); @@ -292,7 +289,7 @@ EXPRESSIONS.add(Ops.Concat, ((sexp: E.Concat, builder: OpcodeBuilder) => { }) as any); EXPRESSIONS.add(Ops.Helper, (sexp: E.Helper, builder: OpcodeBuilder) => { - let { options: { resolver }, meta } = builder; + let { lookup, meta } = builder; let [, name, params, hash] = sexp; // TODO: triage this in the WF compiler @@ -304,7 +301,7 @@ EXPRESSIONS.add(Ops.Helper, (sexp: E.Helper, builder: OpcodeBuilder) => { return; } - let specifier = resolver.lookupHelper(name, meta.templateMeta); + let specifier = lookup.lookupHelper(name, meta); if (specifier) { builder.compileArgs(params, hash, true); @@ -325,7 +322,7 @@ EXPRESSIONS.add(Ops.Get, (sexp: E.Get, builder: OpcodeBuilder) => { EXPRESSIONS.add(Ops.MaybeLocal, (sexp: E.MaybeLocal, builder: OpcodeBuilder) => { let [, path] = sexp; - if (builder.meta.asPartial) { + if (builder.asPartial) { let head = path[0]; path = path.slice(1); @@ -351,6 +348,17 @@ EXPRESSIONS.add(Ops.HasBlockParams, (sexp: E.HasBlockParams, builder: OpcodeBuil builder.hasBlockParams(sexp[1]); }); +export class Macros { + public blocks: Blocks; + public inlines: Inlines; + + constructor() { + let { blocks, inlines } = populateBuiltins(); + this.blocks = blocks; + this.inlines = inlines; + } +} + export type BlockMacro = (params: C.Params, hash: C.Hash, template: Option, inverse: Option, builder: OpcodeBuilder) => void; export type MissingBlockMacro = (name: string, params: C.Params, hash: C.Hash, template: Option, inverse: Option, builder: OpcodeBuilder) => void; @@ -779,8 +787,24 @@ export function compileStatement(statement: WireFormat.Statement, builder: Opcod STATEMENTS.compile(statement, builder); } -export function compileStatements(statements: WireFormat.Statement[], meta: CompilationMeta, env: CompilationOptions): { commit(heap: Heap): Handle } { - let b = new LazyOpcodeBuilder(env, meta); +export interface TemplateOptions { + // already in compilation options + program: Program; + macros: Macros; + + // a subset of the resolver w/ a couple of small tweaks + lookup: CompileTimeLookup; +} + +export interface CompileOptions extends TemplateOptions { + asPartial: boolean; +} + +export function compileStatements(statements: Statement[], containingLayout: ParsedLayout, options: CompileOptions): { commit(heap: Heap): Handle } { + let { program, lookup, macros, asPartial } = options; + let { meta } = containingLayout; + + let b = new LazyOpcodeBuilder(program, lookup, meta, macros, containingLayout, asPartial); for (let i = 0; i < statements.length; i++) { compileStatement(statements[i], b); diff --git a/packages/@glimmer/runtime/lib/compiler.ts b/packages/@glimmer/opcode-compiler/lib/wrapped-component.ts similarity index 59% rename from packages/@glimmer/runtime/lib/compiler.ts rename to packages/@glimmer/opcode-compiler/lib/wrapped-component.ts index a3e919e7cc..467148aee6 100644 --- a/packages/@glimmer/runtime/lib/compiler.ts +++ b/packages/@glimmer/opcode-compiler/lib/wrapped-component.ts @@ -1,38 +1,42 @@ +import { + ATTRS_BLOCK, + ComponentCapabilities, + ICompilableTemplate, + LazyOpcodeBuilder, + OpcodeBuilder, + debugSlice +} from '@glimmer/opcode-compiler'; import { Register } from '@glimmer/vm'; -import { ProgramSymbolTable, Opaque } from '@glimmer/interfaces'; +import { ProgramSymbolTable, Opaque, BlockSymbolTable } from '@glimmer/interfaces'; import { TemplateMeta } from '@glimmer/wire-format'; -import { Template } from './template'; -import { debugSlice } from './opcodes'; -import { ATTRS_BLOCK } from './syntax/functions'; -import { Handle, CompilationOptions, Program } from './environment'; -import { ComponentCapabilities } from './component/interfaces'; -import { ICompilableTemplate } from './syntax/compilable-template'; -import { CompilationOptions as InternalCompilationOptions, Specifier } from './internal-interfaces'; import { + Handle, ComponentArgs, - ComponentBuilder as IComponentBuilder -} from './opcode-builder'; + ComponentBuilder as IComponentBuilder, + ParsedLayout, + Specifier +} from './interfaces'; -import OpcodeBuilderDSL, { LazyOpcodeBuilder } from './compiled/opcodes/builder'; +import { CompileOptions } from './syntax'; +import CompilableTemplate from './compilable-template'; import { DEBUG } from "@glimmer/local-debug-flags"; +import { EMPTY_ARRAY } from "@glimmer/util"; -export function prepareLayout>(options: O, layout: Template, capabilities: ComponentCapabilities): ICompilableTemplate { - return new WrappedBuilder(options, layout, capabilities); -} - -class WrappedBuilder implements ICompilableTemplate { +export class WrappedBuilder implements ICompilableTemplate { public symbolTable: ProgramSymbolTable; - private meta: { templateMeta: TemplateMeta, symbols: string[], asPartial: false }; + private meta: TemplateMeta; - constructor(public options: InternalCompilationOptions, private layout: Template, private capabilities: ComponentCapabilities) { - let meta = this.meta = { templateMeta: layout.meta, symbols: layout.symbols, asPartial: false }; + constructor(public options: CompileOptions, private layout: ParsedLayout, private capabilities: ComponentCapabilities) { + let { block } = layout; + this.meta = layout.meta; + let meta = this.meta = { templateMeta: layout.meta, symbols: block.symbols, asPartial: false }; this.symbolTable = { meta, - hasEval: layout.hasEval, - symbols: layout.symbols.concat([ATTRS_BLOCK]) + hasEval: block.hasEval, + symbols: block.symbols.concat([ATTRS_BLOCK]) }; } @@ -65,10 +69,10 @@ class WrappedBuilder implements ICompilableTemplate { // DidRenderLayout // Exit - let { options, layout } = this; - let meta = { templateMeta: layout.meta, symbols: layout.symbols, asPartial: false }; + let { options, layout, meta } = this; + let { program, lookup, macros, asPartial } = options; - let b = new LazyOpcodeBuilder(options, meta); + let b: LazyOpcodeBuilder = new LazyOpcodeBuilder(program, lookup, meta, macros, layout, asPartial); b.startLabels(); @@ -92,7 +96,7 @@ class WrappedBuilder implements ICompilableTemplate { b.label('BODY'); } - b.invokeStaticBlock(layout.asBlock()); + b.invokeStaticBlock(blockFor(layout, this.options)); if (this.capabilities.dynamicTag) { b.fetch(Register.s1); @@ -118,8 +122,14 @@ class WrappedBuilder implements ICompilableTemplate { } } +function blockFor(layout: ParsedLayout, options: CompileOptions): CompilableTemplate { + let { block, meta } = layout; + + return new CompilableTemplate(block.statements, layout, options, { meta, parameters: EMPTY_ARRAY }); +} + export class ComponentBuilder implements IComponentBuilder { - constructor(private builder: OpcodeBuilderDSL) {} + constructor(private builder: OpcodeBuilder) {} static(definition: Opaque, args: ComponentArgs) { let [params, hash, _default, inverse] = args; diff --git a/packages/@glimmer/runtime/index.ts b/packages/@glimmer/runtime/index.ts index e796458776..c94400a649 100644 --- a/packages/@glimmer/runtime/index.ts +++ b/packages/@glimmer/runtime/index.ts @@ -1,26 +1,9 @@ import './lib/bootstrap'; -export { default as templateFactory, TemplateFactory, Template, TemplateIterator, RenderOptions, RenderLayoutOptions } from './lib/template'; +export { default as templateFactory, ScannableTemplate, TemplateFactory, Template, TemplateIterator, RenderOptions, RenderLayoutOptions } from './lib/template'; export { NULL_REFERENCE, UNDEFINED_REFERENCE, PrimitiveReference, ConditionalReference } from './lib/references'; -export { - default as OpcodeBuilderDSL -} from './lib/compiled/opcodes/builder'; - -export { - prepareLayout -} from './lib/compiler'; - -export { - ComponentBuilder, - ComponentArgs -} from './lib/opcode-builder'; - -export { - debugSlice -} from './lib/opcodes'; - export { setDebuggerCallback, resetDebuggerCallback, @@ -31,24 +14,12 @@ export { default as getDynamicVar } from './lib/helpers/get-dynamic-var'; -export { - Blocks as BlockMacros, - Inlines as InlineMacros, - BlockMacro, - MissingBlockMacro, - expr as compileExpression -} from './lib/syntax/functions'; - export { CompilableTemplate, BlockSyntax, TopLevelSyntax } from './lib/syntax/interfaces'; -export { - Macros -} from './lib/syntax/macros'; - export { PublicVM as VM, UpdatingVM, RenderResult, IteratorResult } from './lib/vm'; export { @@ -78,12 +49,15 @@ export { CompilationOptions } from './lib/environment'; +export { + Lookup +} from './lib/environment/lookup'; + export { PartialDefinition } from './lib/partial'; export { - ComponentCapabilities, ComponentManager, ComponentDefinition, WithDynamicTagName, diff --git a/packages/@glimmer/runtime/lib/compiled/opcodes/builder.ts b/packages/@glimmer/runtime/lib/compiled/opcodes/builder.ts deleted file mode 100644 index a7ffa18957..0000000000 --- a/packages/@glimmer/runtime/lib/compiled/opcodes/builder.ts +++ /dev/null @@ -1,858 +0,0 @@ -import { CompilationMeta, Opaque, Option, ProgramSymbolTable, SymbolTable, Unique } from '@glimmer/interfaces'; -import { dict, EMPTY_ARRAY, expect, fillNulls, Stack, typePos, unreachable } from '@glimmer/util'; -import { Op, Register } from '@glimmer/vm'; -import * as WireFormat from '@glimmer/wire-format'; -import { Handle, Heap, Program } from '../../environment'; -import { - ConstantArray, - Constants, - ConstantString, - ConstantFloat, - LazyConstants -} from '../../environment/constants'; -import { ComponentBuilder as IComponentBuilder } from '../../opcode-builder'; -import { Primitive, PrimitiveType } from '../../references'; -import { expr, ATTRS_BLOCK } from '../../syntax/functions'; -import { BlockSyntax, CompilableTemplate } from '../../syntax/interfaces'; -import RawInlineBlock from '../../syntax/raw-block'; -import { TemplateMeta } from "@glimmer/wire-format"; -import { ComponentBuilder } from "../../compiler"; -import { ComponentDefinition } from '../../component/interfaces'; -import { Specifier } from "../../internal-interfaces"; - -export interface CompilesInto { - compile(builder: OpcodeBuilder): E; -} - -export type Label = string; - -type TargetOpcode = Op.Jump | Op.JumpIf | Op.JumpUnless | Op.EnterList | Op.Iterate | Op.ReturnTo; - -class Labels { - labels = dict(); - targets: Array<{ at: number, Target: TargetOpcode, target: string }> = []; - - label(name: string, index: number) { - this.labels[name] = index; - } - - target(at: number, Target: TargetOpcode, target: string) { - this.targets.push({ at, Target, target }); - } - - patch(buffer: number[]): void { - let { targets, labels } = this; - for (let i = 0; i < targets.length; i++) { - let { at, target } = targets[i]; - let address = labels[target] - at; - buffer[at + 1] = address; - } - } -} - -export interface AbstractTemplate { - symbolTable: S; -} - -export abstract class OpcodeBuilder = AbstractTemplate> { - public constants: Constants; - - private buffer: number[] = []; - private labelsStack = new Stack(); - private isComponentAttrs = false; - public component: IComponentBuilder = new ComponentBuilder(this); - - constructor(public program: Program, public meta: CompilationMeta) { - this.constants = program.constants; - } - - private get pos(): number { - return typePos(this.buffer.length); - } - - private get nextPos(): number { - return this.buffer.length; - } - - upvars(count: number): T { - return fillNulls(count) as T; - } - - reserve(name: Op) { - this.push(name, 0, 0, 0); - } - - push(name: Op, op1 = 0, op2 = 0, op3 = 0) { - let { buffer } = this; - buffer.push(name); - buffer.push(op1); - buffer.push(op2); - buffer.push(op3); - } - - commit(heap: Heap): Handle { - this.push(Op.Return); - - let { buffer } = this; - - // TODO: change the whole malloc API and do something more efficient - let handle = heap.malloc(); - - for (let i = 0; i < buffer.length; i++) { - heap.push(buffer[i]); - } - - heap.finishMalloc(handle); - - return handle; - } - - setComponentAttrs(enabled: boolean): void { - this.isComponentAttrs = enabled; - } - - // args - - pushArgs(names: string[], positionalCount: number, synthetic: boolean) { - let serialized = this.constants.stringArray(names); - this.push(Op.PushArgs, serialized, positionalCount, synthetic === true ? 1 : 0); - } - - // 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.buffer); - } - - // components - - pushComponentManager(specifier: Specifier) { - this.push(Op.PushComponentManager, this.constants.specifier(specifier)); - } - - pushDynamicComponentManager(meta: TemplateMeta) { - this.push(Op.PushDynamicComponentManager, this.constants.serializable(meta)); - } - - prepareArgs(state: Register) { - this.push(Op.PrepareArgs, state); - } - - createComponent(state: Register, hasDefault: boolean, hasInverse: boolean) { - let flag = (hasDefault === true ? 1 : 0) | ((hasInverse === true ? 1 : 0) << 1); - this.push(Op.CreateComponent, flag, state); - } - - registerComponentDestructor(state: Register) { - this.push(Op.RegisterComponentDestructor, state); - } - - beginComponentTransaction() { - this.push(Op.BeginComponentTransaction); - } - - commitComponentTransaction() { - this.push(Op.CommitComponentTransaction); - } - - putComponentOperations() { - this.push(Op.PutComponentOperations); - } - - getComponentSelf(state: Register) { - this.push(Op.GetComponentSelf, state); - } - - getComponentTagName(state: Register) { - this.push(Op.GetComponentTagName, state); - } - - getComponentLayout(state: Register) { - this.push(Op.GetComponentLayout, state); - } - - invokeComponentLayout() { - this.push(Op.InvokeComponentLayout); - } - - didCreateElement(state: Register) { - this.push(Op.DidCreateElement, state); - } - - didRenderLayout(state: Register) { - this.push(Op.DidRenderLayout, state); - } - - // partial - - invokePartial(meta: TemplateMeta, symbols: string[], evalInfo: number[]) { - let _meta = this.constants.serializable(meta); - let _symbols = this.constants.stringArray(symbols); - let _evalInfo = this.constants.array(evalInfo); - - this.push(Op.InvokePartial, _meta, _symbols, _evalInfo); - } - - resolveMaybeLocal(name: string) { - this.push(Op.ResolveMaybeLocal, this.string(name)); - } - - // debugger - - debugger(symbols: string[], evalInfo: number[]) { - this.push(Op.Debugger, this.constants.stringArray(symbols), this.constants.array(evalInfo)); - } - - // content - - dynamicContent(isTrusting: boolean) { - this.push(Op.DynamicContent, isTrusting ? 1 : 0); - } - - // dom - - text(text: string) { - this.push(Op.Text, this.constants.string(text)); - } - - openPrimitiveElement(tag: string) { - this.push(Op.OpenElement, this.constants.string(tag)); - } - - openDynamicElement() { - this.push(Op.OpenDynamicElement); - } - - flushElement() { - this.push(Op.FlushElement); - } - - closeElement() { - this.push(Op.CloseElement); - } - - staticAttr(_name: string, _namespace: Option, _value: string) { - let name = this.constants.string(_name); - let namespace = _namespace ? this.constants.string(_namespace) : 0; - - if (this.isComponentAttrs) { - this.pushPrimitiveReference(_value); - this.push(Op.ComponentAttr, name, 1, namespace); - } else { - let value = this.constants.string(_value); - this.push(Op.StaticAttr, name, value, namespace); - } - } - - dynamicAttr(_name: string, _namespace: Option, trusting: boolean) { - let name = this.constants.string(_name); - let namespace = _namespace ? this.constants.string(_namespace) : 0; - - if (this.isComponentAttrs) { - this.push(Op.ComponentAttr, name, (trusting === true ? 1 : 0), namespace); - } else { - this.push(Op.DynamicAttr, name, (trusting === true ? 1 : 0), namespace); - } - } - - comment(_comment: string) { - let comment = this.constants.string(_comment); - this.push(Op.Comment, comment); - } - - modifier(specifier: Specifier) { - this.push(Op.Modifier, this.constants.specifier(specifier)); - } - - // lists - - putIterator() { - this.push(Op.PutIterator); - } - - enterList(start: string) { - this.reserve(Op.EnterList); - this.labels.target(this.pos, Op.EnterList, start); - } - - exitList() { - this.push(Op.ExitList); - } - - iterate(breaks: string) { - this.reserve(Op.Iterate); - this.labels.target(this.pos, Op.Iterate, breaks); - } - - // expressions - - setVariable(symbol: number) { - this.push(Op.SetVariable, symbol); - } - - setBlock(symbol: number) { - this.push(Op.SetBlock, symbol); - } - - getVariable(symbol: number) { - this.push(Op.GetVariable, symbol); - } - - getProperty(key: string) { - this.push(Op.GetProperty, this.string(key)); - } - - getBlock(symbol: number) { - this.push(Op.GetBlock, symbol); - } - - hasBlock(symbol: number) { - this.push(Op.HasBlock, symbol); - } - - hasBlockParams(symbol: number) { - this.push(Op.HasBlockParams, symbol); - } - - concat(size: number) { - this.push(Op.Concat, size); - } - - load(register: Register) { - this.push(Op.Load, register); - } - - fetch(register: Register) { - this.push(Op.Fetch, register); - } - - dup(register = Register.sp, offset = 0) { - return this.push(Op.Dup, register, offset); - } - - pop(count = 1) { - return this.push(Op.Pop, count); - } - - // vm - - pushRemoteElement() { - this.push(Op.PushRemoteElement); - } - - popRemoteElement() { - this.push(Op.PopRemoteElement); - } - - label(name: string) { - this.labels.label(name, this.nextPos); - } - - pushRootScope(symbols: number, bindCallerScope: boolean) { - this.push(Op.RootScope, symbols, (bindCallerScope ? 1 : 0)); - } - - pushChildScope() { - this.push(Op.ChildScope); - } - - popScope() { - this.push(Op.PopScope); - } - - returnTo(label: string) { - this.reserve(Op.ReturnTo); - this.labels.target(this.pos, Op.ReturnTo, label); - } - - pushDynamicScope() { - this.push(Op.PushDynamicScope); - } - - popDynamicScope() { - this.push(Op.PopDynamicScope); - } - - primitive(_primitive: Primitive) { - let type: PrimitiveType = PrimitiveType.NUMBER; - let primitive: number; - switch (typeof _primitive) { - case 'number': - if (_primitive as number % 1 === 0) { - primitive = _primitive as number; - } else { - primitive = this.float(_primitive as number); - type = PrimitiveType.FLOAT; - } - break; - case 'string': - primitive = this.string(_primitive as string); - type = PrimitiveType.STRING; - break; - case 'boolean': - primitive = (_primitive as any) | 0; - type = PrimitiveType.BOOLEAN_OR_VOID; - break; - case 'object': - // assume null - primitive = 2; - type = PrimitiveType.BOOLEAN_OR_VOID; - break; - case 'undefined': - primitive = 3; - type = PrimitiveType.BOOLEAN_OR_VOID; - break; - default: - throw new Error('Invalid primitive passed to pushPrimitive'); - } - - this.push(Op.Primitive, primitive << 3 | type); - } - - pushPrimitiveReference(primitive: Primitive) { - this.primitive(primitive); - this.primitiveReference(); - } - - primitiveReference() { - this.push(Op.PrimitiveReference); - } - - helper(helper: Specifier) { - this.push(Op.Helper, this.constants.specifier(helper)); - } - - bindDynamicScope(_names: string[]) { - this.push(Op.BindDynamicScope, this.names(_names)); - } - - enter(args: number) { - this.push(Op.Enter, args); - } - - exit() { - this.push(Op.Exit); - } - - return() { - this.push(Op.Return); - } - - pushFrame() { - this.push(Op.PushFrame); - } - - popFrame() { - this.push(Op.PopFrame); - } - - invokeStatic(): void { - this.push(Op.InvokeStatic); - } - - invokeYield(): void { - this.push(Op.InvokeYield); - } - - toBoolean() { - this.push(Op.ToBoolean); - } - - jump(target: string) { - this.reserve(Op.Jump); - this.labels.target(this.pos, Op.Jump, target); - } - - jumpIf(target: string) { - this.reserve(Op.JumpIf); - this.labels.target(this.pos, Op.JumpIf, target); - } - - jumpUnless(target: string) { - this.reserve(Op.JumpUnless); - this.labels.target(this.pos, Op.JumpUnless, target); - } - - // internal helpers - - string(_string: string): ConstantString { - return this.constants.string(_string); - } - - float(num: number): ConstantFloat { - return this.constants.float(num); - } - - protected names(_names: string[]): ConstantArray { - let names: number[] = []; - - for (let i = 0; i < _names.length; i++) { - let n = _names[i]; - names[i]= this.constants.string(n); - } - - return this.constants.array(names); - } - - protected symbols(symbols: number[]): ConstantArray { - return this.constants.array(symbols); - } - - // convenience methods - - compileParams(params: Option) { - if (!params) return 0; - - for (let i = 0; i < params.length; i++) { - expr(params[i], this); - } - - return params.length; - } - - compileArgs(params: Option, hash: Option, synthetic: boolean) { - let count = this.compileParams(params); - - let names: string[] = EMPTY_ARRAY; - - if (hash) { - names = hash[0]; - let val = hash[1]; - for (let i = 0; i < val.length; i++) { - expr(val[i], this); - } - } - - this.pushArgs(names, count, synthetic); - } - - invokeStaticBlock(block: BlockSyntax, callerCount = 0): void { - let { parameters } = block.symbolTable; - let calleeCount = parameters.length; - let count = Math.min(callerCount, calleeCount); - - this.pushFrame(); - - if (count) { - this.pushChildScope(); - - for (let i = 0; i < count; i++) { - this.dup(Register.fp, callerCount - i); - this.setVariable(parameters[i]); - } - } - - this.pushBlock(block); - this.resolveBlock(); - this.invokeStatic(); - - if (count) { - this.popScope(); - } - - this.popFrame(); - } - - guardedAppend(expression: WireFormat.Expression, trusting: boolean) { - this.startLabels(); - - this.pushFrame(); - - this.returnTo('END'); - - expr(expression, this); - - this.dup(); - this.isComponent(); - - this.enter(2); - - this.jumpUnless('ELSE'); - - this.pushDynamicComponentManager(this.meta.templateMeta); - this.invokeComponent(null, null, null, false, null, null); - - this.exit(); - - this.return(); - - this.label('ELSE'); - - this.dynamicContent(trusting); - - this.exit(); - - this.return(); - - this.label('END'); - - this.popFrame(); - - this.stopLabels(); - } - - yield(to: number, params: Option) { - this.compileArgs(params, null, false); - this.getBlock(to); - this.resolveBlock(); - this.invokeYield(); - this.popScope(); - this.popFrame(); - } - - invokeComponent(attrs: Option, params: Option, hash: WireFormat.Core.Hash, synthetic: boolean, block: Option, inverse: Option = null) { - this.fetch(Register.s0); - this.dup(Register.sp, 1); - this.load(Register.s0); - - this.pushYieldableBlock(block); - this.pushYieldableBlock(inverse); - this.pushYieldableBlock(attrs && attrs.scan()); - - this.compileArgs(params, hash, synthetic); - this.prepareArgs(Register.s0); - - this.beginComponentTransaction(); - this.pushDynamicScope(); - this.createComponent(Register.s0, block !== null, inverse !== null); - this.registerComponentDestructor(Register.s0); - - this.getComponentSelf(Register.s0); - - this.getComponentLayout(Register.s0); - this.resolveLayout(); - this.invokeComponentLayout(); - this.didRenderLayout(Register.s0); - this.popFrame(); - - this.popScope(); - this.popDynamicScope(); - this.commitComponentTransaction(); - - this.load(Register.s0); - } - - invokeStaticComponent(definition: ComponentDefinition>, layout: Layout, attrs: Option, params: Option, hash: WireFormat.Core.Hash, synthetic: boolean, block: Option, inverse: Option = null) { - let { capabilities } = definition; - let { symbolTable } = layout; - - let bailOut = - symbolTable.hasEval || - capabilities.prepareArgs || - capabilities.createArgs; - - if (bailOut) { - this.invokeComponent(attrs, params, hash, synthetic, block, inverse); - return; - } - - this.fetch(Register.s0); - this.dup(Register.sp, 1); - this.load(Register.s0); - - let { symbols } = symbolTable; - - this.beginComponentTransaction(); - this.pushDynamicScope(); - this.createComponent(Register.s0, block !== null, inverse !== null); - this.registerComponentDestructor(Register.s0); - - let bindings: { symbol: number, isBlock: boolean }[] = []; - - this.getComponentSelf(Register.s0); - bindings.push({ symbol: 0, isBlock: false }); - - for (let i = 0; i < symbols.length; i++) { - let symbol = symbols[i]; - - switch (symbol.charAt(0)) { - case '&': - let callerBlock: Option = null; - - if (symbol === '&default') { - callerBlock = block; - } else if (symbol === '&inverse') { - callerBlock = inverse; - } else if (symbol === ATTRS_BLOCK) { - callerBlock = attrs && attrs.scan(); - } else { - throw unreachable(); - } - - if (callerBlock) { - this.pushYieldableBlock(callerBlock); - bindings.push({ symbol: i + 1, isBlock: true }); - } else { - this.primitive(null); - bindings.push({ symbol: i + 1, isBlock: false }); - } - - break; - - case '@': - if (!hash) { - break; - } - - let [keys, values] = hash; - let lookupName = symbol; - - if (synthetic) { - lookupName = symbol.slice(1); - } - - let index = keys.indexOf(lookupName); - - if (index !== -1) { - expr(values[index], this); - bindings.push({ symbol: i + 1, isBlock: false }); - } - - break; - } - } - - this.pushRootScope(symbols.length + 1, !!(block || inverse || attrs)); - - for (let i = bindings.length - 1; i >= 0; i--) { - let { symbol, isBlock } = bindings[i]; - - if (isBlock) { - this.setBlock(symbol); - } else { - this.setVariable(symbol); - } - } - - this.pushFrame(); - - this.pushSymbolTable(layout.symbolTable); - this.pushLayout(layout); - this.resolveLayout(); - this.invokeStatic(); - this.didRenderLayout(Register.s0); - this.popFrame(); - - this.popScope(); - this.popDynamicScope(); - this.commitComponentTransaction(); - - this.load(Register.s0); - } - - dynamicComponent(definition: WireFormat.Expression, /* TODO: attrs: Option, */ params: Option, hash: WireFormat.Core.Hash, synthetic: boolean, block: Option, inverse: Option = null) { - this.startLabels(); - - this.pushFrame(); - - this.returnTo('END'); - - expr(definition, this); - - this.dup(); - - this.enter(2); - - this.jumpUnless('ELSE'); - - this.pushDynamicComponentManager(this.meta.templateMeta); - this.invokeComponent(null, params, hash, synthetic, block, inverse); - - this.label('ELSE'); - this.exit(); - this.return(); - - this.label('END'); - this.popFrame(); - - this.stopLabels(); - } - - isComponent() { - this.push(Op.IsComponent); - } - - curryComponent(definition: WireFormat.Expression, /* TODO: attrs: Option, */ params: Option, hash: WireFormat.Core.Hash, synthetic: boolean) { - let meta = this.meta.templateMeta; - - expr(definition, this); - this.compileArgs(params, hash, synthetic); - this.push(Op.CurryComponent, this.constants.serializable(meta)); - } - - abstract pushBlock(block: Option): void; - abstract resolveBlock(): void; - abstract pushLayout(layout: Option): void; - abstract resolveLayout(): void; - abstract pushSymbolTable(block: Option): void; - - pushYieldableBlock(block: Option): void { - this.pushSymbolTable(block && block.symbolTable); - this.pushBlock(block); - } - - template(block: Option): Option { - if (!block) return null; - return new RawInlineBlock(block.statements, block.parameters, this.meta, this.options); - } -} - -export default OpcodeBuilder; - -export class LazyOpcodeBuilder extends OpcodeBuilder> { - public constants: LazyConstants; - - pushSymbolTable(symbolTable: Option) { - if (symbolTable) { - this.pushOther(symbolTable); - } else { - this.primitive(null); - } - } - - pushBlock(block: Option): void { - if (block) { - this.pushOther(block); - } else { - this.primitive(null); - } - } - - resolveBlock(): void { - this.push(Op.CompileBlock); - } - - pushLayout(layout: Option>) { - if (layout) { - this.pushOther(layout); - } else { - this.primitive(null); - } - } - - resolveLayout(): void { - this.push(Op.CompileBlock); - } - - protected pushOther(value: T) { - this.push(Op.Constant, this.other(value)); - } - - protected other(value: Opaque): number { - return this.constants.other(value); - } -} - -// export class EagerOpcodeBuilder extends OpcodeBuilder { -// } - -export type BlockCallback = (dsl: OpcodeBuilder, BEGIN: Label, END: Label) => void; diff --git a/packages/@glimmer/runtime/lib/compiled/opcodes/component.ts b/packages/@glimmer/runtime/lib/compiled/opcodes/component.ts index 1e2e9c45de..3642655623 100644 --- a/packages/@glimmer/runtime/lib/compiled/opcodes/component.ts +++ b/packages/@glimmer/runtime/lib/compiled/opcodes/component.ts @@ -21,10 +21,8 @@ import { } from '../../component/interfaces'; import { normalizeStringValue } from '../../dom/normalize'; import { DynamicScope, Handle, ScopeBlock, ScopeSlot } from '../../environment'; -import { APPEND_OPCODES, UpdatingOpcode } from '../../opcodes'; -import { AbstractTemplate } from './builder'; +import { APPEND_OPCODES, OpcodeJSON, UpdatingOpcode } from '../../opcodes'; import { UNDEFINED_REFERENCE } from '../../references'; -import { ATTRS_BLOCK } from '../../syntax/functions'; import { UpdatingVM, VM } from '../../vm'; import { Arguments, IArguments, ICapturedArguments } from '../../vm/arguments'; import { IsComponentDefinitionReference } from './content'; @@ -33,6 +31,7 @@ import { Resolver, Specifier, ComponentDefinition, ComponentManager, Component } import { dict, assert, unreachable } from "@glimmer/util"; import { Op, Register } from '@glimmer/vm'; import { TemplateMeta } from "@glimmer/wire-format"; +import { AbstractTemplate, ATTRS_BLOCK } from '@glimmer/opcode-compiler'; const ARGS = new Arguments(); diff --git a/packages/@glimmer/runtime/lib/environment.ts b/packages/@glimmer/runtime/lib/environment.ts index 3fb429c202..deef34db88 100644 --- a/packages/@glimmer/runtime/lib/environment.ts +++ b/packages/@glimmer/runtime/lib/environment.ts @@ -24,7 +24,7 @@ import { import { PublicVM } from './vm/append'; -import { Macros } from './syntax/macros'; +import { Macros } from '@glimmer/opcode-compiler'; import { IArguments } from './vm/arguments'; import { DEBUG } from "@glimmer/local-debug-flags"; import { Simple, Unique, Resolver, BlockSymbolTable, Recast } from "@glimmer/interfaces"; diff --git a/packages/@glimmer/runtime/lib/environment/lookup.ts b/packages/@glimmer/runtime/lib/environment/lookup.ts new file mode 100644 index 0000000000..52ed670174 --- /dev/null +++ b/packages/@glimmer/runtime/lib/environment/lookup.ts @@ -0,0 +1,46 @@ +import { CompileTimeLookup, ComponentCapabilities } from "@glimmer/opcode-compiler"; +import { Resolver, Opaque, ProgramSymbolTable, Unique, Option } from "@glimmer/interfaces"; +import { TemplateMeta } from "@glimmer/wire-format"; +import { ComponentDefinition, WithStaticLayout } from '../component/interfaces'; + +export class Lookup implements CompileTimeLookup { + constructor(private resolver: Resolver) { + } + + getCapabilities(name: string, meta: TemplateMeta): ComponentCapabilities { + let specifier = this.resolver.lookupComponent(name, meta); + let definition = this.resolver.resolve>(specifier); + + return definition.capabilities; + } + + getLayout(name: string, meta: TemplateMeta): Option<{ symbolTable: ProgramSymbolTable; handle: Unique<"Handle">; }> { + let specifier = this.resolver.lookupComponent(name, meta); + let definition = this.resolver.resolve>(specifier); + let capabilities = definition.capabilities; + + if (capabilities.dynamicLayout === true) { + return null; + } + + let layoutSpecifier = (definition.manager as WithStaticLayout).getLayout(definition, this.resolver); + return this.resolver.resolve<{ symbolTable: ProgramSymbolTable, handle: Unique<'Handle'> }>(layoutSpecifier); + } + + lookupHelper(name: string, meta: TemplateMeta): Opaque { + return this.resolver.lookupHelper(name, meta); + } + + lookupModifier(name: string, meta: TemplateMeta): Opaque { + return this.resolver.lookupModifier(name, meta); + } + + lookupComponent(name: string, meta: TemplateMeta): Opaque { + return this.resolver.lookupComponent(name, meta); + } + + lookupPartial(name: string, meta: TemplateMeta): Opaque { + return this.resolver.lookupPartial(name, meta); + } + +} diff --git a/packages/@glimmer/runtime/lib/opcode-builder.ts b/packages/@glimmer/runtime/lib/opcode-builder.ts index 0f0826fc2d..79c445ec84 100644 --- a/packages/@glimmer/runtime/lib/opcode-builder.ts +++ b/packages/@glimmer/runtime/lib/opcode-builder.ts @@ -9,12 +9,8 @@ import { import * as WireFormat from '@glimmer/wire-format'; -import { BlockSyntax } from './syntax/interfaces'; - import { PublicVM } from './vm/append'; -import { Specifier, Resolver } from './internal-interfaces'; - -export type ComponentArgs = [WireFormat.Core.Params, WireFormat.Core.Hash, Option, Option]; +import { Resolver } from './internal-interfaces'; export interface DynamicComponentDefinition { ( @@ -24,7 +20,3 @@ export interface DynamicComponentDefinition { resolver: Resolver ): VersionedPathReference>>; } - -export interface ComponentBuilder { - static(definition: Specifier, args: ComponentArgs): void; -} diff --git a/packages/@glimmer/runtime/lib/opcodes.ts b/packages/@glimmer/runtime/lib/opcodes.ts index 122bcd80fd..d6c085f3f6 100644 --- a/packages/@glimmer/runtime/lib/opcodes.ts +++ b/packages/@glimmer/runtime/lib/opcodes.ts @@ -1,180 +1,18 @@ -import { Opaque, Option, Slice as ListSlice, initializeGuid, fillNulls, unreachable, typePos } from '@glimmer/util'; -import { Op, Register } from '@glimmer/vm'; +import { Option, Dict, Slice as ListSlice, initializeGuid, fillNulls, typePos } from '@glimmer/util'; +import { Op } from '@glimmer/vm'; import { Tag } from '@glimmer/reference'; import { VM, UpdatingVM } from './vm'; -import { Opcode, Program } from './environment'; -import { Constants, LazyConstants } from './environment/constants'; +import { Opcode } from './environment'; import { DEBUG } from '@glimmer/local-debug-flags'; - -export function debugSlice(program: Program, start: number, end: number) { - if (DEBUG) { - /* tslint:disable:no-console */ - let { constants } = program; - - // console is not available in IE9 - if (typeof console === 'undefined') { return; } - - // IE10 does not have `console.group` - if (typeof console.group !== 'function') { return; } - - (console as any).group(`%c${start}:${end}`, 'color: #999'); - - for (let i=start; i): string | void { - if (DEBUG) { - let out = type; - - if (params) { - let args = Object.keys(params).map(p => ` ${p}=${json(params[p])}`).join(''); - out += args; - } - return `(${out})`; - } -} - -function json(param: Opaque) { - if (DEBUG) { - if (typeof param === 'function') { - return ''; - } - - let string; - try { - string = JSON.stringify(param); - } catch(e) { - return ''; - } - - if (string === undefined) { - return 'undefined'; - } - - let debug = JSON.parse(string); - if (typeof debug === 'object' && debug !== null && debug.GlimmerDebug !== undefined) { - return debug.GlimmerDebug; - } - - return string; - } -} - -function debug(c: Constants, op: Op, op1: number, op2: number, op3: number): [string, object] { - if (DEBUG) { - switch (op) { - case Op.Bug: throw unreachable(); - - case Op.Helper: return ['Helper', { helper: c.resolveSpecifier(op1) }]; - case Op.SetVariable: return ['SetVariable', { symbol: op1 }]; - case Op.SetBlock: return ['SetBlock', { symbol: op1 }]; - case Op.GetVariable: return ['GetVariable', { symbol: op1 }]; - case Op.GetProperty: return ['GetProperty', { key: c.getString(op1) }]; - case Op.GetBlock: return ['GetBlock', { symbol: op1 }]; - case Op.HasBlock: return ['HasBlock', { block: op1 }]; - case Op.HasBlockParams: return ['HasBlockParams', { block: op1 }]; - case Op.Concat: return ['Concat', { size: op1 }]; - case Op.Constant: return ['Constant', { value: (c as LazyConstants).getOther(op1) }]; - case Op.Primitive: return ['Primitive', { primitive: op1 }]; - case Op.PrimitiveReference: return ['PrimitiveReference', {}]; - case Op.Dup: return ['Dup', { register: Register[op1], offset: op2 }]; - case Op.Pop: return ['Pop', { count: op1 }]; - case Op.Load: return ['Load', { register: Register[op1] }]; - case Op.Fetch: return ['Fetch', { register: Register[op1] }]; - - /// PRELUDE & EXIT - case Op.RootScope: return ['RootScope', { symbols: op1, bindCallerScope: !!op2 }]; - case Op.ChildScope: return ['ChildScope', {}]; - case Op.PopScope: return ['PopScope', {}]; - case Op.Return: return ['Return', {}]; - case Op.ReturnTo: return ['ReturnTo', { offset: op1 }]; - - /// HTML - case Op.Text: return ['Text', { text: c.getString(op1) }]; - case Op.Comment: return ['Comment', { comment: c.getString(op1) }]; - case Op.DynamicContent: return ['DynamicContent', { trusting: !!op1 }]; - case Op.OpenElement: return ['OpenElement', { tag: c.getString(op1) }]; - case Op.OpenDynamicElement: return ['OpenDynamicElement', {}]; - case Op.StaticAttr: return ['StaticAttr', { name: c.getString(op1), value: c.getString(op2), namespace: op3 ? c.getString(op3) : null }]; - case Op.DynamicAttr: return ['DynamicAttr', { name: c.getString(op1), trusting: !!op2, namespace: op3 ? c.getString(op3) : null }]; - case Op.ComponentAttr: return ['ComponentAttr', { name: c.getString(op1), trusting: !!op2, namespace: op3 ? c.getString(op3) : null }]; - case Op.FlushElement: return ['FlushElement', {}]; - case Op.CloseElement: return ['CloseElement', {}]; - - /// MODIFIER - case Op.Modifier: return ['Modifier', {}]; - - /// WORMHOLE - case Op.PushRemoteElement: return ['PushRemoteElement', {}]; - case Op.PopRemoteElement: return ['PopRemoteElement', {}]; - - /// DYNAMIC SCOPE - case Op.BindDynamicScope: return ['BindDynamicScope', {}]; - case Op.PushDynamicScope: return ['PushDynamicScope', {}]; - case Op.PopDynamicScope: return ['PopDynamicScope', {}]; - - /// VM - case Op.CompileBlock: return ['CompileBlock', {}]; - case Op.InvokeStatic: return ['InvokeStatic', {}]; - case Op.InvokeYield: return ['InvokeYield', {}]; - case Op.Jump: return ['Jump', { to: op1 }]; - case Op.JumpIf: return ['JumpIf', { to: op1 }]; - case Op.JumpUnless: return ['JumpUnless', { to: op1 }]; - case Op.PushFrame: return ['PushFrame', {}]; - case Op.PopFrame: return ['PopFrame', {}]; - case Op.Enter: return ['Enter', { args: op1 }]; - case Op.Exit: return ['Exit', {}]; - case Op.ToBoolean: return ['ToBoolean', {}]; - - /// LISTS - case Op.EnterList: return ['EnterList', { start: op1 }]; - case Op.ExitList: return ['ExitList', {}]; - case Op.PutIterator: return ['PutIterator', {}]; - case Op.Iterate: return ['Iterate', { end: op1 }]; - - /// COMPONENTS - case Op.IsComponent: return ['IsComponent', {}]; - case Op.CurryComponent: return ['CurryComponent', { meta: c.getSerializable(op1) }]; - case Op.PushComponentManager: return ['PushComponentManager', { definition: c.resolveSpecifier(op1) }]; - case Op.PushDynamicComponentManager: return ['PushDynamicComponentManager', { meta: c.getSerializable(op1) }]; - case Op.PushArgs: return ['PushArgs', { names: c.getStringArray(op1), positionals: op2, synthetic: !!op3 }]; - case Op.PrepareArgs: return ['PrepareArgs', { state: Register[op1] }]; - case Op.CreateComponent: return ['CreateComponent', { flags: op1, state: Register[op2] }]; - case Op.RegisterComponentDestructor: return ['RegisterComponentDestructor', {}]; - case Op.PutComponentOperations: return ['PutComponentOperations', {}]; - case Op.GetComponentSelf: return ['GetComponentSelf', { state: Register[op1] }]; - case Op.GetComponentTagName: return ['GetComponentTagName', { state: Register[op1] }]; - case Op.GetComponentLayout: return ['GetComponentLayout', { state: Register[op1] }]; - case Op.InvokeComponentLayout: return ['InvokeComponentLayout', {}]; - case Op.BeginComponentTransaction: return ['BeginComponentTransaction', {}]; - case Op.CommitComponentTransaction: return ['CommitComponentTransaction', {}]; - case Op.DidCreateElement: return ['DidCreateElement', { state: Register[op1] }]; - case Op.DidRenderLayout: return ['DidRenderLayout', {}]; - - /// PARTIALS - case Op.InvokePartial: return ['InvokePartial', { templateMeta: c.getSerializable(op1), symbols: c.getStringArray(op2), evalInfo: c.getArray(op3) }]; - case Op.ResolveMaybeLocal: return ['ResolveMaybeLocal', { name: c.getString(op1)} ]; - - /// DEBUGGER - case Op.Debugger: return ['Debugger', { symbols: c.getStringArray(op1), evalInfo: c.getArray(op2) }]; - - /// STATEMENTS - - case Op.Size: throw unreachable(); - } - - throw unreachable(); - } - - return ['', {}]; +import { debug, logOpcode } from "@glimmer/opcode-compiler"; + +export interface OpcodeJSON { + type: number | string; + guid?: Option; + deopted?: boolean; + args?: string[]; + details?: Dict>; + children?: OpcodeJSON[]; } export type Operand1 = number; diff --git a/packages/@glimmer/runtime/lib/scanner.ts b/packages/@glimmer/runtime/lib/scanner.ts deleted file mode 100644 index 7d39384e98..0000000000 --- a/packages/@glimmer/runtime/lib/scanner.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { CompilationMeta } from '@glimmer/interfaces'; -import * as WireFormat from '@glimmer/wire-format'; -import CompilableTemplate from './syntax/compilable-template'; -import { - TopLevelSyntax, -} from './syntax/interfaces'; -import { CompilationOptions } from './internal-interfaces'; - -export type DeserializedStatement = WireFormat.Statement | WireFormat.Statements.Attribute | WireFormat.Statements.Argument; - -export default class Scanner { - constructor(private block: WireFormat.SerializedTemplateBlock, private options: CompilationOptions) { - } - - scanLayout(meta: CompilationMeta): TopLevelSyntax { - let { block, options } = this; - let { symbols, hasEval } = block; - - return new CompilableTemplate(block.statements, { meta, hasEval, symbols }, options); - } -} diff --git a/packages/@glimmer/runtime/lib/syntax/compilable-template.ts b/packages/@glimmer/runtime/lib/syntax/compilable-template.ts deleted file mode 100644 index 86a8d107c8..0000000000 --- a/packages/@glimmer/runtime/lib/syntax/compilable-template.ts +++ /dev/null @@ -1,38 +0,0 @@ -import { - Option, - SymbolTable -} from '@glimmer/interfaces'; -import { Statement } from '@glimmer/wire-format'; -import { Handle, Program } from '../environment'; -import { debugSlice } from '../opcodes'; -import { compileStatements } from './functions'; -import { DEBUG } from '@glimmer/local-debug-flags'; -import { CompilableTemplate as ICompilableTemplate } from './interfaces'; -import { CompilationOptions } from '../internal-interfaces'; - -export { ICompilableTemplate }; - -export default class CompilableTemplate implements ICompilableTemplate { - private compiled: Option = null; - - constructor(public statements: Statement[], public symbolTable: S, private program: Program) {} - - compile(): Handle { - let { compiled } = this; - if (compiled !== null) return compiled; - - let { program } = this; - - let builder = compileStatements(this.statements, this.symbolTable.meta, program); - let handle = builder.commit(program.heap); - - if (DEBUG) { - let { heap } = program; - let start = heap.getaddr(handle); - let end = start + heap.sizeof(handle); - debugSlice(program, start, end); - } - - return (this.compiled = handle); - } -} diff --git a/packages/@glimmer/runtime/lib/syntax/functions.ts b/packages/@glimmer/runtime/lib/syntax/functions.ts deleted file mode 100644 index 3d764e59f7..0000000000 --- a/packages/@glimmer/runtime/lib/syntax/functions.ts +++ /dev/null @@ -1,791 +0,0 @@ -import { CompilationMeta, Option, ProgramSymbolTable } from '@glimmer/interfaces'; -import { assert, dict, EMPTY_ARRAY, unwrap } from '@glimmer/util'; -import { Register } from '@glimmer/vm'; -import * as WireFormat from '@glimmer/wire-format'; -import OpcodeBuilder, { LazyOpcodeBuilder } from '../compiled/opcodes/builder'; -import { Handle, Heap, Program } from '../environment'; -import { hasStaticLayout } from '../component/interfaces'; -import { CompilationOptions, ComponentDefinition } from '../internal-interfaces'; -import * as ClientSide from './client-side'; -import { BlockSyntax } from './interfaces'; -import RawInlineBlock from './raw-block'; -import Ops = WireFormat.Ops; - -export type TupleSyntax = WireFormat.Statement | WireFormat.TupleExpression; -export type CompilerFunction = ((sexp: T, builder: OpcodeBuilder) => void); - -export const ATTRS_BLOCK = '&attrs'; - -class Compilers { - private names = dict(); - private funcs: CompilerFunction[] = []; - - constructor(private offset = 0) {} - - add(name: number, func: CompilerFunction): void { - this.funcs.push(func); - this.names[name] = this.funcs.length - 1; - } - - compile(sexp: T, builder: OpcodeBuilder): void { - let name: number = sexp[this.offset]; - let index = this.names[name]; - let func = this.funcs[index]; - assert(!!func, `expected an implementation for ${this.offset === 0 ? Ops[sexp[0]] : ClientSide.Ops[sexp[1]]}`); - func(sexp, builder); - } -} - -import S = WireFormat.Statements; - -const STATEMENTS = new Compilers(); -const CLIENT_SIDE = new Compilers(1); - -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: S.CloseElement, builder: OpcodeBuilder) => { - builder.closeElement(); -}); - -STATEMENTS.add(Ops.FlushElement, (_sexp: S.FlushElement, builder: OpcodeBuilder) => { - builder.flushElement(); -}); - -STATEMENTS.add(Ops.Modifier, (sexp: S.Modifier, builder: OpcodeBuilder) => { - let { options: { resolver }, meta } = builder; - let [, name, params, hash] = sexp; - - let specifier = resolver.lookupModifier(name, meta.templateMeta); - - if (specifier) { - builder.compileArgs(params, hash, true); - builder.modifier(specifier); - } else { - throw new Error(`Compile Error ${name} 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.DynamicAttr, (sexp: S.DynamicAttr, builder: OpcodeBuilder) => { - dynamicAttr(sexp, false, builder); -}); - -STATEMENTS.add(Ops.TrustingAttr, (sexp: S.DynamicAttr, builder: OpcodeBuilder) => { - dynamicAttr(sexp, true, builder); -}); - -function dynamicAttr(sexp: S.DynamicAttr | S.TrustingAttr, trusting: boolean, builder: OpcodeBuilder) { - let [, name, value, namespace] = sexp; - - expr(value, builder); - - if (namespace) { - builder.dynamicAttr(name, namespace, trusting); - } else { - builder.dynamicAttr(name, null, trusting); - } -} - -STATEMENTS.add(Ops.OpenElement, (sexp: S.OpenElement, builder: OpcodeBuilder) => { - builder.openPrimitiveElement(sexp[1]); -}); - -STATEMENTS.add(Ops.OpenSplattedElement, (sexp: S.SplatElement, builder) => { - builder.setComponentAttrs(true); - builder.putComponentOperations(); - builder.openPrimitiveElement(sexp[1]); -}); - -CLIENT_SIDE.add(ClientSide.Ops.OpenComponentElement, (sexp: ClientSide.OpenComponentElement, builder: OpcodeBuilder) => { - builder.putComponentOperations(); - builder.openPrimitiveElement(sexp[2]); -}); - -CLIENT_SIDE.add(ClientSide.Ops.DidCreateElement, (_sexp: ClientSide.DidCreateElement, builder: OpcodeBuilder) => { - builder.didCreateElement(Register.s0); -}); - -CLIENT_SIDE.add(ClientSide.Ops.SetComponentAttrs, (sexp: ClientSide.SetComponentAttrs, builder) => { - builder.setComponentAttrs(sexp[2]); -}); - -CLIENT_SIDE.add(ClientSide.Ops.Debugger, () => { - // tslint:disable-next-line:no-debugger - debugger; -}); - -CLIENT_SIDE.add(ClientSide.Ops.DidRenderLayout, (_sexp: ClientSide.DidRenderLayout, builder: OpcodeBuilder) => { - builder.didRenderLayout(Register.s0); -}); - -STATEMENTS.add(Ops.Append, (sexp: S.Append, builder: OpcodeBuilder) => { - let [, value, trusting] = sexp; - - let { inlines } = builder.options.macros; - let returned = inlines.compile(sexp, builder) || value; - - if (returned === true) return; - - let isGet = E.isGet(value); - let isMaybeLocal = E.isMaybeLocal(value); - - if (trusting) { - builder.guardedAppend(value, true); - } else { - if (isGet || isMaybeLocal) { - builder.guardedAppend(value, false); - } else { - expr(value, builder); - builder.dynamicContent(false); - } - } -}); - -STATEMENTS.add(Ops.Block, (sexp: S.Block, builder: OpcodeBuilder) => { - let [, name, params, hash, _template, _inverse] = sexp; - let template = builder.template(_template); - let inverse = builder.template(_inverse); - - let templateBlock = template && template.scan(); - let inverseBlock = inverse && inverse.scan(); - - let { blocks } = builder.options.macros; - blocks.compile(name, params, hash, templateBlock, inverseBlock, builder); -}); - -STATEMENTS.add(Ops.Component, (sexp: S.Component, builder: OpcodeBuilder) => { - let [, tag, _attrs, args, block] = sexp; - - let options = builder.options; - let resolver = options.resolver; - let specifier = resolver.lookupComponent(tag, builder.meta.templateMeta); - - if (specifier) { - let definition = resolver.resolve(specifier); - let manager = definition.manager; - - let attrs: WireFormat.Statement[] = [ - [Ops.ClientSideStatement, ClientSide.Ops.SetComponentAttrs, true], - ..._attrs, - [Ops.ClientSideStatement, ClientSide.Ops.SetComponentAttrs, false] - ]; - let attrsBlock = new RawInlineBlock(attrs, EMPTY_ARRAY, builder.meta, builder.options); - let child = builder.template(block); - - if (hasStaticLayout(definition, manager)) { - let layoutSpecifier = manager.getLayout(definition, resolver); - let layout = resolver.resolve<{ symbolTable: ProgramSymbolTable, template: Handle }>(layoutSpecifier); - - builder.pushComponentManager(specifier); - builder.invokeStaticComponent(definition, layout, attrsBlock, null, args, false, child && child.scan()); - } else { - builder.pushComponentManager(specifier); - builder.invokeComponent(attrsBlock, null, args, false, child && child.scan()); - } - } else { - throw new Error(`Compile Error: Cannot find component ${tag}`); - } -}); - -STATEMENTS.add(Ops.Partial, (sexp: S.Partial, builder: OpcodeBuilder) => { - let [, name, evalInfo] = sexp; - - let { meta: { templateMeta, symbols } } = builder; - - builder.startLabels(); - - builder.pushFrame(); - - builder.returnTo('END'); - - expr(name, builder); - - builder.dup(); - - builder.enter(2); - - builder.jumpUnless('ELSE'); - - builder.invokePartial(templateMeta, symbols, evalInfo); - builder.popScope(); - builder.popFrame(); - - builder.label('ELSE'); - builder.exit(); - builder.return(); - - builder.label('END'); - builder.popFrame(); - - builder.stopLabels(); -}); - -STATEMENTS.add(Ops.Yield, (sexp: WireFormat.Statements.Yield, builder: OpcodeBuilder) => { - let [, to, params] = sexp; - - builder.yield(to, params); -}); - -STATEMENTS.add(Ops.AttrSplat, (sexp: WireFormat.Statements.AttrSplat, builder: OpcodeBuilder) => { - let [, to] = sexp; - - builder.yield(to, []); - builder.didCreateElement(Register.s0); - builder.setComponentAttrs(false); -}); - -STATEMENTS.add(Ops.Debugger, (sexp: WireFormat.Statements.Debugger, builder: OpcodeBuilder) => { - let [, evalInfo] = sexp; - - builder.debugger(builder.meta.symbols, evalInfo); -}); - -STATEMENTS.add(Ops.ClientSideStatement, (sexp: WireFormat.Statements.ClientSide, builder: OpcodeBuilder) => { - CLIENT_SIDE.compile(sexp as ClientSide.ClientSideStatement, builder); -}); - -const EXPRESSIONS = new Compilers(); - -import E = WireFormat.Expressions; -import C = WireFormat.Core; - -export function expr(expression: WireFormat.Expression, builder: OpcodeBuilder): void { - if (Array.isArray(expression)) { - EXPRESSIONS.compile(expression, builder); - } else { - builder.pushPrimitiveReference(expression); - } -} - -EXPRESSIONS.add(Ops.Unknown, (sexp: E.Unknown, builder: OpcodeBuilder) => { - let { options: { resolver }, meta } = builder; - let name = sexp[1]; - - let specifier = resolver.lookupHelper(name, meta.templateMeta); - - if (specifier) { - builder.compileArgs(null, null, true); - builder.helper(specifier); - } else if (meta.asPartial) { - builder.resolveMaybeLocal(name); - } else { - builder.getVariable(0); - builder.getProperty(name); - } -}); - -EXPRESSIONS.add(Ops.Concat, ((sexp: E.Concat, builder: OpcodeBuilder) => { - let parts = sexp[1]; - for (let i = 0; i < parts.length; i++) { - expr(parts[i], builder); - } - builder.concat(parts.length); -}) as any); - -EXPRESSIONS.add(Ops.Helper, (sexp: E.Helper, builder: OpcodeBuilder) => { - let { options: { resolver }, meta } = builder; - let [, name, params, hash] = sexp; - - // TODO: triage this in the WF compiler - if (name === 'component') { - assert(params.length, 'SYNTAX ERROR: component helper requires at least one argument'); - - let [definition, ...restArgs] = params; - builder.curryComponent(definition, restArgs, hash, true); - return; - } - - let specifier = resolver.lookupHelper(name, meta.templateMeta); - - if (specifier) { - builder.compileArgs(params, hash, true); - builder.helper(specifier); - } else { - throw new Error(`Compile Error: ${name} is not a helper`); - } -}); - -EXPRESSIONS.add(Ops.Get, (sexp: E.Get, builder: OpcodeBuilder) => { - let [, head, path] = sexp; - builder.getVariable(head); - for (let i = 0; i < path.length; i++) { - builder.getProperty(path[i]); - } -}); - -EXPRESSIONS.add(Ops.MaybeLocal, (sexp: E.MaybeLocal, builder: OpcodeBuilder) => { - let [, path] = sexp; - - if (builder.meta.asPartial) { - let head = path[0]; - path = path.slice(1); - - builder.resolveMaybeLocal(head); - } else { - builder.getVariable(0); - } - - for(let i = 0; i < path.length; i++) { - builder.getProperty(path[i]); - } -}); - -EXPRESSIONS.add(Ops.Undefined, (_sexp, builder) => { - return builder.pushPrimitiveReference(undefined); -}); - -EXPRESSIONS.add(Ops.HasBlock, (sexp: E.HasBlock, builder: OpcodeBuilder) => { - builder.hasBlock(sexp[1]); -}); - -EXPRESSIONS.add(Ops.HasBlockParams, (sexp: E.HasBlockParams, builder: OpcodeBuilder) => { - builder.hasBlockParams(sexp[1]); -}); - -export type BlockMacro = (params: C.Params, hash: C.Hash, template: Option, inverse: Option, builder: OpcodeBuilder) => void; -export type MissingBlockMacro = (name: string, params: C.Params, hash: C.Hash, template: Option, inverse: Option, builder: OpcodeBuilder) => void; - -export class Blocks { - private names = dict(); - private funcs: BlockMacro[] = []; - private missing: MissingBlockMacro; - - add(name: string, func: BlockMacro) { - this.funcs.push(func); - this.names[name] = this.funcs.length - 1; - } - - addMissing(func: MissingBlockMacro) { - this.missing = func; - } - - compile(name: string, params: C.Params, hash: C.Hash, template: Option, inverse: Option, builder: OpcodeBuilder): void { - 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(name, params, hash, template, inverse, builder); - assert(!!handled, `${name} not found, and the catch-all block handler didn't handle it`); - } else { - let func = this.funcs[index]; - func(params, hash, template, inverse, builder); - } - } -} - -export const BLOCKS = new Blocks(); - -export type AppendSyntax = S.Append; -export type AppendMacro = (name: string, params: Option, hash: Option, builder: OpcodeBuilder) => ['expr', WireFormat.Expression] | true | false; - -export class Inlines { - private names = dict(); - 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', WireFormat.Expression] | 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 name: string; - let params: Option; - let hash: Option; - - if (value[0] === Ops.Helper) { - name = value[1]; - params = value[2]; - hash = value[3]; - } else if (value[0] === Ops.Unknown) { - name = value[1]; - params = hash = null; - } else { - return ['expr', value]; - } - - let index = this.names[name]; - - if (index === undefined && this.missing) { - let func = this.missing; - let returned = func(name, params, hash, builder); - return returned === false ? ['expr', value] : returned; - } else if (index !== undefined) { - let func = this.funcs[index]; - let returned = func(name, 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', (params, _hash, template, inverse, builder) => { - // PutArgs - // Test(Environment) - // Enter(BEGIN, END) - // BEGIN: Noop - // JumpUnless(ELSE) - // Evaluate(default) - // Jump(END) - // ELSE: Noop - // Evalulate(inverse) - // END: Noop - // Exit - - if (!params || params.length !== 1) { - throw new Error(`SYNTAX ERROR: #if requires a single argument`); - } - - builder.startLabels(); - - builder.pushFrame(); - - builder.returnTo('END'); - - expr(params[0], builder); - - builder.toBoolean(); - - builder.enter(1); - - builder.jumpUnless('ELSE'); - - builder.invokeStaticBlock(unwrap(template)); - - if (inverse) { - builder.jump('EXIT'); - - builder.label('ELSE'); - builder.invokeStaticBlock(inverse); - - builder.label('EXIT'); - builder.exit(); - builder.return(); - } else { - builder.label('ELSE'); - builder.exit(); - builder.return(); - } - - builder.label('END'); - builder.popFrame(); - - builder.stopLabels(); - }); - - blocks.add('unless', (params, _hash, template, inverse, builder) => { - // PutArgs - // Test(Environment) - // Enter(BEGIN, END) - // BEGIN: Noop - // JumpUnless(ELSE) - // Evaluate(default) - // Jump(END) - // ELSE: Noop - // Evalulate(inverse) - // END: Noop - // Exit - - if (!params || params.length !== 1) { - throw new Error(`SYNTAX ERROR: #unless requires a single argument`); - } - - builder.startLabels(); - - builder.pushFrame(); - - builder.returnTo('END'); - - expr(params[0], builder); - - builder.toBoolean(); - - builder.enter(1); - - builder.jumpIf('ELSE'); - - builder.invokeStaticBlock(unwrap(template)); - - if (inverse) { - builder.jump('EXIT'); - - builder.label('ELSE'); - builder.invokeStaticBlock(inverse); - - builder.label('EXIT'); - builder.exit(); - builder.return(); - } else { - builder.label('ELSE'); - builder.exit(); - builder.return(); - } - - builder.label('END'); - builder.popFrame(); - - builder.stopLabels(); - }); - - blocks.add('with', (params, _hash, template, inverse, builder) => { - // PutArgs - // Test(Environment) - // Enter(BEGIN, END) - // BEGIN: Noop - // JumpUnless(ELSE) - // Evaluate(default) - // Jump(END) - // ELSE: Noop - // Evalulate(inverse) - // END: Noop - // Exit - - if (!params || params.length !== 1) { - throw new Error(`SYNTAX ERROR: #with requires a single argument`); - } - - builder.startLabels(); - - builder.pushFrame(); - - builder.returnTo('END'); - - expr(params[0], builder); - - builder.dup(); - builder.toBoolean(); - - builder.enter(2); - - builder.jumpUnless('ELSE'); - - builder.invokeStaticBlock(unwrap(template), 1); - - if (inverse) { - builder.jump('EXIT'); - - builder.label('ELSE'); - builder.invokeStaticBlock(inverse); - - builder.label('EXIT'); - builder.exit(); - builder.return(); - } else { - builder.label('ELSE'); - builder.exit(); - builder.return(); - } - - builder.label('END'); - builder.popFrame(); - - builder.stopLabels(); - }); - - blocks.add('each', (params, hash, template, inverse, builder) => { - // Enter(BEGIN, END) - // BEGIN: Noop - // PutArgs - // PutIterable - // JumpUnless(ELSE) - // EnterList(BEGIN2, END2) - // ITER: Noop - // NextIter(BREAK) - // BEGIN2: Noop - // PushChildScope - // Evaluate(default) - // PopScope - // END2: Noop - // Exit - // Jump(ITER) - // BREAK: Noop - // ExitList - // Jump(END) - // ELSE: Noop - // Evalulate(inverse) - // END: Noop - // Exit - - builder.startLabels(); - - builder.pushFrame(); - - builder.returnTo('END'); - - if (hash && hash[0][0] === 'key') { - expr(hash[1][0], builder); - } else { - builder.pushPrimitiveReference(null); - } - - expr(params[0], builder); - - builder.enter(2); - - builder.putIterator(); - - builder.jumpUnless('ELSE'); - - builder.pushFrame(); - - builder.returnTo('ITER'); - - builder.dup(Register.fp, 1); - - builder.enterList('BODY'); - - builder.label('ITER'); - builder.iterate('BREAK'); - - builder.label('BODY'); - builder.invokeStaticBlock(unwrap(template), 2); - builder.pop(2); - builder.exit(); - builder.return(); - - builder.label('BREAK'); - builder.exitList(); - builder.popFrame(); - - if (inverse) { - builder.jump('EXIT'); - - builder.label('ELSE'); - builder.invokeStaticBlock(inverse); - - builder.label('EXIT'); - builder.exit(); - builder.return(); - } else { - builder.label('ELSE'); - builder.exit(); - builder.return(); - } - - builder.label('END'); - builder.popFrame(); - - builder.stopLabels(); - }); - - blocks.add('in-element', (params, hash, template, _inverse, builder) => { - if (!params || params.length !== 1) { - throw new Error(`SYNTAX ERROR: #in-element requires a single argument`); - } - - builder.startLabels(); - - builder.pushFrame(); - - builder.returnTo('END'); - - if (hash && hash[0].length) { - let [ keys, values ] = hash; - - if (keys.length === 1 && keys[0] === 'nextSibling') { - expr(values[0], builder); - } else { - throw new Error(`SYNTAX ERROR: #in-element does not take a \`${keys[0]}\` option`); - } - } else { - expr(null, builder); - } - - expr(params[0], builder); - - builder.dup(); - - builder.enter(3); - - builder.jumpUnless('ELSE'); - - builder.pushRemoteElement(); - builder.invokeStaticBlock(unwrap(template)); - builder.popRemoteElement(); - - builder.label('ELSE'); - builder.exit(); - builder.return(); - - builder.label('END'); - builder.popFrame(); - - builder.stopLabels(); - }); - - blocks.add('-with-dynamic-vars', (_params, hash, template, _inverse, builder) => { - if (hash) { - let [names, expressions] = hash; - - builder.compileParams(expressions); - - builder.pushDynamicScope(); - builder.bindDynamicScope(names); - builder.invokeStaticBlock(unwrap(template)); - builder.popDynamicScope(); - } else { - builder.invokeStaticBlock(unwrap(template)); - } - }); - - blocks.add('component', (_params, hash, template, inverse, builder) => { - assert(_params && _params.length, 'SYNTAX ERROR: #component requires at least one argument'); - - let [definition, ...params] = _params!; - builder.dynamicComponent(definition, params, hash, true, template, inverse); - }); - - inlines.add('component', (_name, _params, hash, builder) => { - assert(_params && _params.length, 'SYNTAX ERROR: component helper requires at least one argument'); - - let [definition, ...params] = _params!; - builder.dynamicComponent(definition, params, hash, true, null, null); - - return true; - }); - - return { blocks, inlines }; -} - -export function compileStatement(statement: WireFormat.Statement, builder: OpcodeBuilder) { - STATEMENTS.compile(statement, builder); -} - -export function compileStatements(statements: WireFormat.Statement[], meta: CompilationMeta, program: Program): { commit(heap: Heap): Handle } { - let b = new LazyOpcodeBuilder(program, meta); - - for (let i = 0; i < statements.length; i++) { - compileStatement(statements[i], b); - } - - return b; -} diff --git a/packages/@glimmer/runtime/lib/syntax/macros.ts b/packages/@glimmer/runtime/lib/syntax/macros.ts deleted file mode 100644 index c4f072be08..0000000000 --- a/packages/@glimmer/runtime/lib/syntax/macros.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { Blocks, Inlines, populateBuiltins } from "./functions"; - -export class Macros { - public blocks: Blocks; - public inlines: Inlines; - - constructor() { - let { blocks, inlines } = populateBuiltins(); - this.blocks = blocks; - this.inlines = inlines; - } -} diff --git a/packages/@glimmer/runtime/lib/syntax/raw-block.ts b/packages/@glimmer/runtime/lib/syntax/raw-block.ts deleted file mode 100644 index eef1bc9030..0000000000 --- a/packages/@glimmer/runtime/lib/syntax/raw-block.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { BlockSymbolTable, CompilationMeta } from '@glimmer/interfaces'; -import { Statement } from '@glimmer/wire-format'; -import CompilableTemplate from './compilable-template'; -import { BlockSyntax, ScannableTemplate } from './interfaces'; -import { CompilationOptions } from '../internal-interfaces'; - -export default class RawInlineBlock implements ScannableTemplate { - constructor( - private statements: Statement[], - private parameters: number[], - private meta: CompilationMeta, - private options: CompilationOptions - ) { - } - - scan(): BlockSyntax { - return new CompilableTemplate(this.statements, { parameters: this.parameters, meta: this.meta }, this.options); - } -} diff --git a/packages/@glimmer/runtime/lib/template.ts b/packages/@glimmer/runtime/lib/template.ts index 289160953b..d149fcbe1c 100644 --- a/packages/@glimmer/runtime/lib/template.ts +++ b/packages/@glimmer/runtime/lib/template.ts @@ -1,8 +1,7 @@ import { Cursor } from './bounds'; -import CompilableTemplate from './syntax/compilable-template'; -import { Simple, Opaque, Option, BlockSymbolTable } from '@glimmer/interfaces'; +import { Simple, Opaque, Option } from '@glimmer/interfaces'; import { PathReference } from '@glimmer/reference'; -import { assign, EMPTY_ARRAY } from '@glimmer/util'; +import { assign } from '@glimmer/util'; import { SerializedTemplateBlock, SerializedTemplateWithLazyBlock, @@ -12,12 +11,11 @@ import { import { NewElementBuilder } from './vm/element-builder'; import { RehydrateBuilder } from './vm/rehydrate-builder'; import { SerializeBuilder } from './vm/serialize-builder'; -import { DynamicScope, Environment, CompilationOptions as PublicCompilationOptions } from './environment'; -import Scanner from './scanner'; -import { BlockSyntax, TopLevelSyntax } from './syntax/interfaces'; +import { DynamicScope, Environment, Program } from './environment'; +import { TopLevelSyntax } from './syntax/interfaces'; import { IteratorResult, RenderResult, VM } from './vm'; -import { CompilationOptions } from './internal-interfaces'; import { EMPTY_ARGS, ICapturedArguments } from './vm/arguments'; +import { CompilableTemplate, ParsedLayout, TemplateOptions } from "@glimmer/opcode-compiler"; export interface RenderOptions { env: Environment; @@ -64,7 +62,6 @@ export interface Template { // internal casts, these are lazily created and cached asLayout(): TopLevelSyntax; asPartial(): TopLevelSyntax; - asBlock(): BlockSyntax; } export interface TemplateFactory { @@ -85,7 +82,7 @@ export interface TemplateFactory { * * @param {Environment} env glimmer Environment */ - create(env: PublicCompilationOptions): Template; + create(env: TemplateOptions): Template; /** * Used to create an environment specific singleton instance * of the template. @@ -93,7 +90,7 @@ export interface TemplateFactory { * @param {Environment} env glimmer Environment * @param {Object} meta environment specific injections into meta */ - create(env: PublicCompilationOptions, meta: U): Template; + create(env: TemplateOptions, meta: U): Template; } export class TemplateIterator { @@ -115,30 +112,32 @@ export default function templateFactory(serializedTem export default function templateFactory({ id: templateId, meta, block }: SerializedTemplateWithLazyBlock): TemplateFactory<{}, {}> { let parsedBlock: SerializedTemplateBlock; let id = templateId || `client-${clientId++}`; - let create = (options: CompilationOptions, envMeta?: {}) => { + let create = (options: TemplateOptions, envMeta?: {}) => { let newMeta = envMeta ? assign({}, envMeta, meta) : meta; if (!parsedBlock) { parsedBlock = JSON.parse(block); } - return new ScannableTemplate(id, newMeta, options, parsedBlock); + return new ScannableTemplate(options, { id, block: parsedBlock, meta: newMeta }); }; return { id, meta, create }; } -class ScannableTemplate implements Template { +export class ScannableTemplate implements Template { private layout: Option = null; private partial: Option = null; - private block: Option = null; - private scanner: Scanner; public symbols: string[]; public hasEval: boolean; + public id: string; + public meta: TemplateMeta; private statements: Statement[]; - constructor(public id: string, public meta: TemplateMeta, private options: CompilationOptions, rawBlock: SerializedTemplateBlock) { - this.scanner = new Scanner(rawBlock, options); - this.symbols = rawBlock.symbols; - this.hasEval = rawBlock.hasEval; - this.statements = rawBlock.statements; + constructor(private options: TemplateOptions, private parsedLayout: ParsedLayout) { + let { block } = parsedLayout; + this.symbols = block.symbols; + this.hasEval = block.hasEval; + this.statements = block.statements; + this.meta = parsedLayout.meta; + this.id = parsedLayout.id || `client-${clientId++}`; } renderLayout(options: RenderLayoutOptions): TemplateIterator { @@ -148,35 +147,27 @@ class ScannableTemplate implements Template { let layout = this.asLayout(); let handle = layout.compile(); - let vm = VM.initial(this.options.program, env, self, args, dynamicScope, builder, layout.symbolTable, handle); + let vm = VM.initial(this.options.program as any as Program, env, self, args, dynamicScope, builder, layout.symbolTable, handle); return new TemplateIterator(vm); } asLayout(): TopLevelSyntax { - if (!this.layout) this.layout = this.scanner.scanLayout(this.compilationMeta()); - return this.layout; + if (this.layout) return this.layout; + return this.layout = compilable(this.parsedLayout, this.options, false); } asPartial(): TopLevelSyntax { - if (!this.partial) this.partial = this.scanner.scanLayout(this.compilationMeta(true)); - return this.partial; + if (this.partial) return this.partial; + return this.partial = compilable(this.parsedLayout, this.options, true); } +} - asBlock(): BlockSyntax { - let { options, statements } = this; - let { block } = this; - - if (!block) { - let meta = this.compilationMeta(); - block = this.block = new CompilableTemplate(statements, { meta, parameters: EMPTY_ARRAY }, options); - } +export function compilable(layout: ParsedLayout, options: TemplateOptions, asPartial: boolean) { + let { block, meta } = layout; + let { hasEval, symbols } = block; + let compileOptions = { ...options, asPartial }; - return block!; - } - - private compilationMeta(asPartial = false) { - return { templateMeta: this.meta, symbols: this.symbols, asPartial }; - } + return new CompilableTemplate(block.statements, layout, compileOptions, { meta, hasEval, symbols }); } function elementBuilder({ mode, env, cursor }: Pick) { @@ -186,4 +177,4 @@ function elementBuilder({ mode, env, cursor }: Pick; @@ -305,12 +310,12 @@ class BasicComponentManager implements WithStaticLayout { + let layout = createTemplate(source); + return new ScannableTemplate(options, layout).asLayout(); + }; - let compile = (source: string, options: TestCompilationOptions) => compileWithOptions(source, options).asLayout(); let specifier = resolver.lookup('template-source', name, {})!; return resolver.compileTemplate(specifier, compile); @@ -349,17 +354,14 @@ const BASIC_COMPONENT_MANAGER = new BasicComponentManager(); class StaticTaglessComponentManager extends BasicComponentManager { getLayout(definition: BasicComponentDefinition, resolver: TestResolver): TestSpecifier { - let { layout, name } = definition; - - if (!layout) { - throw new Error('BUG: missing static layout'); - } + let { name, capabilities } = definition; let specifier = resolver.lookup('template-source', name, {})!; - return resolver.compileTemplate(specifier, (source: string, options: TestCompilationOptions) => { - let template = compileWithOptions(source, options, {}); - return prepareLayout(options, template, definition.capabilities); + return resolver.compileTemplate(specifier, (source, options) => { + let template = createTemplate(source, {}); + let compileOptions = { ...options, asPartial: false }; + return new WrappedBuilder(compileOptions, template, capabilities); }); } } @@ -396,12 +398,12 @@ class EmberishGlimmerComponentManager implements ComponentManager { + let layout = createTemplate(source); + return new ScannableTemplate(options, layout).asLayout(); + }; - let compile = (source: string, options: TestCompilationOptions) => compileWithOptions(source, options).asLayout(); let specifier = resolver.lookup('template-source', name, {})!; return resolver.compileTemplate(specifier, compile); @@ -541,9 +543,9 @@ class EmberishCurlyComponentManager implements WithDynamicTagName { - let template = compileWithOptions(source, options, {}); - return prepareLayout(options, template, CURLY_CAPABILITIES); + return resolver.compileTemplate(specifier, (source, options) => { + let template = createTemplate(source); + return new WrappedBuilder({ ...options, asPartial: false }, template, CURLY_CAPABILITIES); }); } @@ -752,7 +754,7 @@ export interface Lookup { modifier: ModifierManager; partial: PartialDefinition; component: ComponentDefinition; - template: CompileTemplate; + template: { compile(): Handle }; 'template-source': string; } @@ -790,11 +792,11 @@ export class TestResolver implements Resolver { modifier: new TypedRegistry(), partial: new TypedRegistry(), component: new TypedRegistry>(), - template: new TypedRegistry(), + template: new TypedRegistry<{ compile(): Handle }>(), 'template-source': new TypedRegistry() }; - private options: TestCompilationOptions; + private options: TemplateOptions; register(type: K, name: string, value: Lookup[K]): TestSpecifier { (this.registry[type] as TypedRegistry).register(name, value); @@ -809,7 +811,7 @@ export class TestResolver implements Resolver { } } - compileTemplate(sourceSpecifier: TestSpecifier, compiler: (source: string, options: TestCompilationOptions) => CompilableTemplate): TestSpecifier { + compileTemplate(sourceSpecifier: TestSpecifier, create: (source: string, options: TemplateOptions) => { compile(): Handle }): TestSpecifier { let templateName = sourceSpecifier.name; let specifier = this.lookup('template', templateName, {}); @@ -819,7 +821,7 @@ export class TestResolver implements Resolver { let source = this.resolve(sourceSpecifier); - return this.register('template', templateName, compiler(source, this.options)); + return this.register('template', templateName, create(source, this.options)); } lookupHelper(name: string, meta: TemplateMeta): Option { @@ -862,9 +864,9 @@ class TestMacros extends Macros { params = []; } - let resolver = builder.options.resolver; + let lookup = builder.lookup; - let specifier = resolver.lookupComponent(name, builder.meta.templateMeta); + let specifier = lookup.lookupComponent(name, builder.meta); if (specifier) { builder.component.static(specifier, [params, hashToArgs(hash), template, inverse]); @@ -875,8 +877,8 @@ class TestMacros extends Macros { }); inlines.addMissing((name, params, hash, builder) => { - let resolver = builder.options.resolver; - let specifier = resolver.lookupComponent(name, builder.meta.templateMeta); + let lookup = builder.lookup; + let specifier = lookup.lookupComponent(name, builder.meta); if (specifier) { builder.component.static(specifier, [params!, hashToArgs(hash), null, null]); @@ -893,9 +895,10 @@ export class TestEnvironment extends Environment { private program = new Program(this.resolver); private uselessAnchor: HTMLAnchorElement; public compiledLayouts = dict(); + private lookup: LookupResolver; - public compileOptions: TestCompilationOptions = { - resolver: this.resolver, + public compileOptions: TemplateOptions = { + lookup: new LookupResolver(this.resolver), program: this.program, macros: new TestMacros() }; @@ -910,6 +913,7 @@ export class TestEnvironment extends Environment { // recursive field, so "unsafely" set one half late (but before the resolver is actually used) this.resolver['options'] = this.compileOptions; + this.lookup = new LookupResolver(this.resolver); let document = options.document || window.document; this.uselessAnchor = document.createElement('a') as HTMLAnchorElement; @@ -1054,10 +1058,11 @@ export class TestEnvironment extends Environment { } } -export function compileWithOptions(templateSource: string, options: TestCompilationOptions, meta: TemplateMeta = {}) { - let wrapper = JSON.parse(precompile(templateSource, { meta })); - let factory = templateFactory(wrapper); - return factory.create(options); +export function createTemplate(templateSource: string, meta: TemplateMeta = {}): ParsedLayout { + let wrapper: SerializedTemplateWithLazyBlock = JSON.parse(precompile(templateSource, { meta })); + let block: SerializedTemplateBlock = JSON.parse(wrapper.block); + + return { block, meta }; } export class TestDynamicScope implements DynamicScope { From d7527f5e3a839c599ed405ceb7c84d3e79fa57d4 Mon Sep 17 00:00:00 2001 From: Yehuda Katz Date: Thu, 3 Aug 2017 17:45:56 -0700 Subject: [PATCH 04/42] Remove unnecessary InlineBlock construct --- .../lib/compilable-template.ts | 20 ++--------- .../opcode-compiler/lib/interfaces.ts | 4 +-- .../opcode-compiler/lib/opcode-builder.ts | 34 +++++++++---------- .../@glimmer/opcode-compiler/lib/syntax.ts | 16 ++++----- 4 files changed, 29 insertions(+), 45 deletions(-) diff --git a/packages/@glimmer/opcode-compiler/lib/compilable-template.ts b/packages/@glimmer/opcode-compiler/lib/compilable-template.ts index da2e41a804..751db1a76f 100644 --- a/packages/@glimmer/opcode-compiler/lib/compilable-template.ts +++ b/packages/@glimmer/opcode-compiler/lib/compilable-template.ts @@ -2,11 +2,11 @@ import { Option, SymbolTable } from '@glimmer/interfaces'; -import { Statement, SerializedInlineBlock } from '@glimmer/wire-format'; +import { Statement } from '@glimmer/wire-format'; import { DEBUG } from '@glimmer/local-debug-flags'; import { debugSlice } from './debug'; import { Handle } from './interfaces'; -import { CompilableTemplate as ICompilableTemplate, BlockSyntax, ParsedLayout } from './interfaces'; +import { CompilableTemplate as ICompilableTemplate, ParsedLayout } from './interfaces'; import { CompileOptions, compileStatements } from './syntax';; export { ICompilableTemplate }; @@ -36,19 +36,3 @@ export default class CompilableTemplate implements ICompi return (this.compiled = handle); } } - -// TODO: Kill -export class RawInlineBlock { - constructor( - private parsedBlock: SerializedInlineBlock, - private containingLayout: ParsedLayout, - private options: CompileOptions - ) { - } - - scan(): BlockSyntax { - let { parameters, statements } = this.parsedBlock; - let symbolTable = { parameters, meta: this.containingLayout.meta }; - return new CompilableTemplate(statements, this.containingLayout, this.options, symbolTable); - } -} diff --git a/packages/@glimmer/opcode-compiler/lib/interfaces.ts b/packages/@glimmer/opcode-compiler/lib/interfaces.ts index 2f3f449ef9..4686fd08dd 100644 --- a/packages/@glimmer/opcode-compiler/lib/interfaces.ts +++ b/packages/@glimmer/opcode-compiler/lib/interfaces.ts @@ -39,7 +39,7 @@ export interface CompilableTemplate { compile(): Handle; } -export type BlockSyntax = CompilableTemplate; +export type CompilableBlock = CompilableTemplate; export interface Program { [key: number]: never; @@ -74,7 +74,7 @@ export interface LazyConstants extends Constants { other(value: Opaque): number; } -export type ComponentArgs = [Core.Params, Core.Hash, Option, Option]; +export type ComponentArgs = [Core.Params, Core.Hash, Option, Option]; export type Specifier = Opaque; export interface ComponentBuilder { diff --git a/packages/@glimmer/opcode-compiler/lib/opcode-builder.ts b/packages/@glimmer/opcode-compiler/lib/opcode-builder.ts index 0a72bd08a6..0dce326994 100644 --- a/packages/@glimmer/opcode-compiler/lib/opcode-builder.ts +++ b/packages/@glimmer/opcode-compiler/lib/opcode-builder.ts @@ -9,7 +9,7 @@ import { Heap, LazyConstants, Primitive, - BlockSyntax, + CompilableBlock, Constants, Specifier, Program, @@ -23,9 +23,7 @@ import { expr } from './syntax'; -import CompilableTemplate, { - RawInlineBlock -} from './compilable-template'; +import CompilableTemplate from './compilable-template'; import { ComponentBuilder @@ -536,8 +534,10 @@ export abstract class OpcodeBuilder { @@ -572,7 +572,7 @@ export abstract class OpcodeBuilder, params: Option, hash: WireFormat.Core.Hash, synthetic: boolean, block: Option, inverse: Option = null) { + invokeComponent(attrs: Option, params: Option, hash: WireFormat.Core.Hash, synthetic: boolean, block: Option, inverse: Option = null) { this.fetch(Register.s0); this.dup(Register.sp, 1); this.load(Register.s0); this.pushYieldableBlock(block); this.pushYieldableBlock(inverse); - this.pushYieldableBlock(attrs && attrs.scan()); + this.pushYieldableBlock(attrs); this.compileArgs(params, hash, synthetic); this.prepareArgs(Register.s0); @@ -678,7 +678,7 @@ export abstract class OpcodeBuilder, params: Option, hash: WireFormat.Core.Hash, synthetic: boolean, block: Option, inverse: Option = null) { + invokeStaticComponent(capabilities: ComponentCapabilities, layout: Layout, attrs: Option, params: Option, hash: WireFormat.Core.Hash, synthetic: boolean, block: Option, inverse: Option = null) { let { symbolTable } = layout; let bailOut = @@ -712,14 +712,14 @@ export abstract class OpcodeBuilder = null; + let callerBlock: Option = null; if (symbol === '&default') { callerBlock = block; } else if (symbol === '&inverse') { callerBlock = inverse; } else if (symbol === ATTRS_BLOCK) { - callerBlock = attrs && attrs.scan(); + callerBlock = attrs; } else { throw unreachable(); } @@ -785,7 +785,7 @@ export abstract class OpcodeBuilder, */ params: Option, hash: WireFormat.Core.Hash, synthetic: boolean, block: Option, inverse: Option = null) { + dynamicComponent(definition: WireFormat.Expression, /* TODO: attrs: Option, */ params: Option, hash: WireFormat.Core.Hash, synthetic: boolean, block: Option, inverse: Option = null) { this.startLabels(); this.pushFrame(); @@ -825,18 +825,18 @@ export abstract class OpcodeBuilder): void; + abstract pushBlock(block: Option): void; abstract resolveBlock(): void; abstract pushLayout(layout: Option): void; abstract resolveLayout(): void; abstract pushSymbolTable(block: Option): void; - pushYieldableBlock(block: Option): void { + pushYieldableBlock(block: Option): void { this.pushSymbolTable(block && block.symbolTable); this.pushBlock(block); } - template(block: Option): Option { + template(block: Option): Option { if (!block) return null; return this.inlineBlock(block); @@ -856,7 +856,7 @@ export class LazyOpcodeBuilder extends OpcodeBuilder): void { + pushBlock(block: Option): void { if (block) { this.pushOther(block); } else { diff --git a/packages/@glimmer/opcode-compiler/lib/syntax.ts b/packages/@glimmer/opcode-compiler/lib/syntax.ts index aa7d8f6e0d..4d21d8030f 100644 --- a/packages/@glimmer/opcode-compiler/lib/syntax.ts +++ b/packages/@glimmer/opcode-compiler/lib/syntax.ts @@ -4,7 +4,7 @@ import { Register } from '@glimmer/vm'; import * as WireFormat from '@glimmer/wire-format'; import * as ClientSide from './client-side'; import OpcodeBuilder, { LazyOpcodeBuilder, CompileTimeLookup } from './opcode-builder'; -import { BlockSyntax, Handle, Heap, ParsedLayout, Program } from './interfaces'; +import { CompilableBlock, Handle, Heap, ParsedLayout, Program } from './interfaces'; import Ops = WireFormat.Ops; @@ -153,8 +153,8 @@ STATEMENTS.add(Ops.Block, (sexp: S.Block, builder: OpcodeBuilder) => { let template = builder.template(_template); let inverse = builder.template(_inverse); - let templateBlock = template && template.scan(); - let inverseBlock = inverse && inverse.scan(); + let templateBlock = template && template; + let inverseBlock = inverse && inverse; let { blocks } = builder.macros; blocks.compile(name, params, hash, templateBlock, inverseBlock, builder); @@ -182,10 +182,10 @@ STATEMENTS.add(Ops.Component, (sexp: S.Component, builder: OpcodeBuilder) => { let layout = lookup.getLayout(tag, meta)!; builder.pushComponentManager(specifier); - builder.invokeStaticComponent(capabilities, layout, attrsBlock, null, args, false, child && child.scan()); + builder.invokeStaticComponent(capabilities, layout, attrsBlock, null, args, false, child && child); } else { builder.pushComponentManager(specifier); - builder.invokeComponent(attrsBlock, null, args, false, child && child.scan()); + builder.invokeComponent(attrsBlock, null, args, false, child && child); } } else { throw new Error(`Compile Error: Cannot find component ${tag}`); @@ -359,8 +359,8 @@ export class Macros { } } -export type BlockMacro = (params: C.Params, hash: C.Hash, template: Option, inverse: Option, builder: OpcodeBuilder) => void; -export type MissingBlockMacro = (name: string, params: C.Params, hash: C.Hash, template: Option, inverse: Option, builder: OpcodeBuilder) => void; +export type BlockMacro = (params: C.Params, hash: C.Hash, template: Option, inverse: Option, builder: OpcodeBuilder) => void; +export type MissingBlockMacro = (name: string, params: C.Params, hash: C.Hash, template: Option, inverse: Option, builder: OpcodeBuilder) => void; export class Blocks { private names = dict(); @@ -376,7 +376,7 @@ export class Blocks { this.missing = func; } - compile(name: string, params: C.Params, hash: C.Hash, template: Option, inverse: Option, builder: OpcodeBuilder): void { + compile(name: string, params: C.Params, hash: C.Hash, template: Option, inverse: Option, builder: OpcodeBuilder): void { let index = this.names[name]; if (index === undefined) { From cb471d679b442a29c9a0a8af5ae27b0516927b6c Mon Sep 17 00:00:00 2001 From: Chad Hietala Date: Fri, 4 Aug 2017 10:13:30 -0400 Subject: [PATCH 05/42] Inject OpcodeBuilder Prior to this commit parts of the compiler hardcoded the references to the LazyOpcodeBuilder. To be able to switch between eager and lazy compilation we need to pass an OpcodeBuilderConstructor function into all the places where we were hardcoding the LazyOpcodeBuilder, specifically CompilableTemplate and WrappedTemplate. By treating the Builder's concrete type unknown, we can swap between builders. --- packages/@glimmer/opcode-compiler/index.ts | 6 +++--- .../lib/compilable-template.ts | 13 ++++++++++--- .../opcode-compiler/lib/opcode-builder.ts | 13 ++++++++++++- .../@glimmer/opcode-compiler/lib/syntax.ts | 19 +++---------------- .../opcode-compiler/lib/wrapped-component.ts | 4 ++-- packages/@glimmer/runtime/index.ts | 2 +- packages/@glimmer/runtime/lib/environment.ts | 3 ++- packages/@glimmer/runtime/lib/template.ts | 17 ++++++----------- .../@glimmer/test-helpers/lib/environment.ts | 8 +++++--- 9 files changed, 44 insertions(+), 41 deletions(-) diff --git a/packages/@glimmer/opcode-compiler/index.ts b/packages/@glimmer/opcode-compiler/index.ts index 312477d59f..9955ea788f 100644 --- a/packages/@glimmer/opcode-compiler/index.ts +++ b/packages/@glimmer/opcode-compiler/index.ts @@ -4,15 +4,15 @@ export { ATTRS_BLOCK, CompileOptions, Macros, - TemplateOptions, - compileStatements + TemplateOptions } from './lib/syntax'; export { AbstractTemplate, CompileTimeLookup, LazyOpcodeBuilder, - OpcodeBuilder + OpcodeBuilder, + OpcodeBuilderConstructor } from './lib/opcode-builder'; export { diff --git a/packages/@glimmer/opcode-compiler/lib/compilable-template.ts b/packages/@glimmer/opcode-compiler/lib/compilable-template.ts index 751db1a76f..06740c1c42 100644 --- a/packages/@glimmer/opcode-compiler/lib/compilable-template.ts +++ b/packages/@glimmer/opcode-compiler/lib/compilable-template.ts @@ -7,7 +7,8 @@ import { DEBUG } from '@glimmer/local-debug-flags'; import { debugSlice } from './debug'; import { Handle } from './interfaces'; import { CompilableTemplate as ICompilableTemplate, ParsedLayout } from './interfaces'; -import { CompileOptions, compileStatements } from './syntax';; +import { CompileOptions, compileStatement } from './syntax'; +import { OpcodeBuilder } from "@glimmer/opcode-compiler"; export { ICompilableTemplate }; @@ -21,9 +22,15 @@ export default class CompilableTemplate implements ICompi if (compiled !== null) return compiled; let { options, statements, containingLayout } = this; - let { program } = options; + let { meta } = containingLayout; + let { program, lookup, macros, asPartial, Builder } = options; + + let builder = new Builder(program, lookup, meta, macros, containingLayout, asPartial, Builder); + + for (let i = 0; i < statements.length; i++) { + compileStatement(statements[i], builder as OpcodeBuilder); + } - let builder = compileStatements(statements, containingLayout, options); let handle = builder.commit(program.heap); if (DEBUG) { diff --git a/packages/@glimmer/opcode-compiler/lib/opcode-builder.ts b/packages/@glimmer/opcode-compiler/lib/opcode-builder.ts index 0dce326994..9be287d03f 100644 --- a/packages/@glimmer/opcode-compiler/lib/opcode-builder.ts +++ b/packages/@glimmer/opcode-compiler/lib/opcode-builder.ts @@ -77,6 +77,16 @@ export interface CompileTimeLookup { lookupPartial(name: string, meta: TemplateMeta): Option; } +export interface OpcodeBuilderConstructor { + new(program: Program, + lookup: CompileTimeLookup, + meta: TemplateMeta, + macros: Macros, + containingLayout: ParsedLayout, + asPartial: boolean, + Builder: OpcodeBuilderConstructor): { commit(heap: Heap): Handle }; +} + export abstract class OpcodeBuilder = AbstractTemplate> { public constants: Constants; @@ -91,7 +101,8 @@ export abstract class OpcodeBuilder(); import E = WireFormat.Expressions; import C = WireFormat.Core; -import { Statement } from "@glimmer/wire-format"; export function expr(expression: WireFormat.Expression, builder: OpcodeBuilder): void { if (Array.isArray(expression)) { @@ -791,6 +790,7 @@ export interface TemplateOptions { // already in compilation options program: Program; macros: Macros; + Builder: OpcodeBuilderConstructor; // a subset of the resolver w/ a couple of small tweaks lookup: CompileTimeLookup; @@ -799,16 +799,3 @@ export interface TemplateOptions { export interface CompileOptions extends TemplateOptions { asPartial: boolean; } - -export function compileStatements(statements: Statement[], containingLayout: ParsedLayout, options: CompileOptions): { commit(heap: Heap): Handle } { - let { program, lookup, macros, asPartial } = options; - let { meta } = containingLayout; - - let b = new LazyOpcodeBuilder(program, lookup, meta, macros, containingLayout, asPartial); - - for (let i = 0; i < statements.length; i++) { - compileStatement(statements[i], b); - } - - return b; -} diff --git a/packages/@glimmer/opcode-compiler/lib/wrapped-component.ts b/packages/@glimmer/opcode-compiler/lib/wrapped-component.ts index 467148aee6..e8d1c54f6d 100644 --- a/packages/@glimmer/opcode-compiler/lib/wrapped-component.ts +++ b/packages/@glimmer/opcode-compiler/lib/wrapped-component.ts @@ -2,7 +2,6 @@ import { ATTRS_BLOCK, ComponentCapabilities, ICompilableTemplate, - LazyOpcodeBuilder, OpcodeBuilder, debugSlice } from '@glimmer/opcode-compiler'; @@ -71,8 +70,9 @@ export class WrappedBuilder implements ICompilableTemplate { let { options, layout, meta } = this; let { program, lookup, macros, asPartial } = options; + let { Builder } = options; - let b: LazyOpcodeBuilder = new LazyOpcodeBuilder(program, lookup, meta, macros, layout, asPartial); + let b = new Builder(program, lookup, meta, macros, layout, asPartial, options.Builder) as OpcodeBuilder; b.startLabels(); diff --git a/packages/@glimmer/runtime/index.ts b/packages/@glimmer/runtime/index.ts index c94400a649..52adce098b 100644 --- a/packages/@glimmer/runtime/index.ts +++ b/packages/@glimmer/runtime/index.ts @@ -1,6 +1,6 @@ import './lib/bootstrap'; -export { default as templateFactory, ScannableTemplate, TemplateFactory, Template, TemplateIterator, RenderOptions, RenderLayoutOptions } from './lib/template'; +export { default as templateFactory, ScannableTemplate, TemplateFactory, Template, TemplateIterator, RenderLayoutOptions } from './lib/template'; export { NULL_REFERENCE, UNDEFINED_REFERENCE, PrimitiveReference, ConditionalReference } from './lib/references'; diff --git a/packages/@glimmer/runtime/lib/environment.ts b/packages/@glimmer/runtime/lib/environment.ts index deef34db88..e6cbfaa749 100644 --- a/packages/@glimmer/runtime/lib/environment.ts +++ b/packages/@glimmer/runtime/lib/environment.ts @@ -24,7 +24,7 @@ import { import { PublicVM } from './vm/append'; -import { Macros } from '@glimmer/opcode-compiler'; +import { Macros, OpcodeBuilderConstructor } from "@glimmer/opcode-compiler"; import { IArguments } from './vm/arguments'; import { DEBUG } from "@glimmer/local-debug-flags"; import { Simple, Unique, Resolver, BlockSymbolTable, Recast } from "@glimmer/interfaces"; @@ -378,6 +378,7 @@ export interface CompilationOptions; - parentNode: Simple.Element; - nextSibling?: Option; - dynamicScope: DynamicScope; - mode?: 'rehydrate' | 'serialize'; -} +import { + CompilableTemplate, + ParsedLayout, + TemplateOptions +} from "@glimmer/opcode-compiler"; export interface RenderLayoutOptions { env: Environment; diff --git a/packages/@glimmer/test-helpers/lib/environment.ts b/packages/@glimmer/test-helpers/lib/environment.ts index ca3c92842e..42be2a43a1 100644 --- a/packages/@glimmer/test-helpers/lib/environment.ts +++ b/packages/@glimmer/test-helpers/lib/environment.ts @@ -4,8 +4,9 @@ import { Macros, ParsedLayout, WrappedBuilder, - TemplateOptions -} from '@glimmer/opcode-compiler'; + TemplateOptions, + LazyOpcodeBuilder +} from "@glimmer/opcode-compiler"; import { // VM @@ -900,7 +901,8 @@ export class TestEnvironment extends Environment { public compileOptions: TemplateOptions = { lookup: new LookupResolver(this.resolver), program: this.program, - macros: new TestMacros() + macros: new TestMacros(), + Builder: LazyOpcodeBuilder }; constructor(options: TestEnvironmentOptions = {}) { From 55b216ac2e8e18755458613253bdc591419fa55a Mon Sep 17 00:00:00 2001 From: Yehuda Katz Date: Tue, 8 Aug 2017 16:03:10 -0700 Subject: [PATCH 06/42] WIP --- design.md | 138 ++++++++++++ packages/@glimmer/interfaces/lib/di.d.ts | 4 +- .../opcode-compiler/lib/interfaces.ts | 3 +- packages/@glimmer/runtime/index.ts | 11 +- .../runtime/lib/compiled/opcodes/component.ts | 196 +++++++++++++----- .../runtime/lib/compiled/opcodes/content.ts | 10 +- .../runtime/lib/component/interfaces.ts | 117 +++++++---- .../runtime/lib/environment/lookup.ts | 23 +- .../runtime/lib/internal-interfaces.d.ts | 2 +- .../@glimmer/runtime/lib/opcode-builder.ts | 5 +- .../runtime/test/ember-component-test.ts | 10 +- .../@glimmer/test-helpers/lib/environment.ts | 64 ++++-- 12 files changed, 435 insertions(+), 148 deletions(-) create mode 100644 design.md diff --git a/design.md b/design.md new file mode 100644 index 0000000000..6644241a69 --- /dev/null +++ b/design.md @@ -0,0 +1,138 @@ +```ts +renderComponent({ manager: GlimmerManager, definition: Tab, handle: 11 }) +``` + +```hbs +{{! Inside }} + +``` + +```ts +ambientManager.getComponentDefinition(Tab /* ComponentDefinition */, 12) +``` + +--- + +What the user types: + +```ts +import TabBar from './TabBar'; +class Tab extends Component { + static template = hbs``; +} +``` + +Pass 1: + +```ts +import TabBar from './TabBar'; +class Tab extends Component { + static template = hbs({ moduleId: 'ui/components/Tab' })``; +} +``` + +Pass 2 (against the entire bundle): + +```ts +import TabBar from './TabBar'; +class Tab extends Component { + static capabilities = DEFAULT_CAPABILITIES; + static template = 11 /* Handle */; + static templates = { + 12: { definition: TabBar, manager: GlimmerManager /* statically determined (maybe pragma) */ } + } +} +``` + +Handle 11: + +```asm + ; assume an ambient manager +getstate 12 ; state.manager.getComponentDefinition(state.definition, 12) + ; populates the state register +invokecomponent +``` + +--- + +> Note, in all cases, invokecomponent expects a populated state register with +> { definition, manager, component: null } and a Handle on the stack + +What the user types: + +```hbs +{{! inside Tab }} + +``` + +We generate: + +```json +['component', 'TabBar', { moduleId: 'ui/components/Tab' }] +``` + +Lazy compiler, today: + +```asm +lookup 'TabBar', { moduleId: 'ui/components/Tab' } +; ^ string const ^ template meta constant +resolve ; -> { definition: ComponentDefinition, manager: Manager } +setstate ; { manager: definition.manager, definition, component: null } +getlayout ; definition.manager.getLayout(definition, env.resolver) -> + ; resolver.lookup({ type: 'template', name: moduleId }) + ; -> CompilableLayout +compile ; -> Handle +invokecomponent +``` + +Eager compiler, plan: + +```asm +push 'ui/components/Tab/TabBar' +resolve ; env.resolver.resolve('ui/components/Tab/TabBar') + ; { manager: GlimmerManager, definition: TabBar } +setstate ; { manager, definition, component: null } +pushhandle 12 +invokecomponent +``` + +Tom Mode, plan: + +```asm + ; assume an ambient manager +getcomponent 12 ; state.manager.getComponentDefinition(state.definition, 12) + ; tomManager.getComponentDefinition(Tab, 12) -> + ; { manager: GlimmerManager, definition: TabBar } +getstate 12 ; populates the state register + +pushhandle 12 +invokecomponent +``` + +``` +env.resolver.lookupComponent('TabBar', { moduleId: 'ui/components/Tab' }) -> Specifier -> resolve() -> Definition +Definition { ComponentClass: TabBar, manager: GlimmerManager } + +{{component someComponent}} +['component', ['lookup', ['get', 0, 'someComponent']]] + +locals: { TabBar: Handle, TabBody: Handle } +globals: { Tab: Handle } + +globals { Tab: CompilableTemplate } +(name: string, meta: TemplateMeta) => CompilableTemplate + +lookup("Tab") -> locals["Tab"] || globals["Tab"] + +{ manager: 'glimmer' } -> manager = lookupManager('glimmer') -> manager.getComponentDefinition('Tab', templateMeta) + +class extends Component { + static capabilities = ...; + static template = hbs`...`; +} + +class extends Component { + static capabilities = ...; + static template = hbs({ moduleId: ... })`...`; +} +``` diff --git a/packages/@glimmer/interfaces/lib/di.d.ts b/packages/@glimmer/interfaces/lib/di.d.ts index 60853a6441..a63c4c176a 100644 --- a/packages/@glimmer/interfaces/lib/di.d.ts +++ b/packages/@glimmer/interfaces/lib/di.d.ts @@ -4,8 +4,8 @@ import { Opaque, Option, Unique } from './core'; export interface Resolver { lookupHelper(name: string, meta: T): Option; lookupModifier(name: string, meta: T): Option; - lookupComponent(name: string, meta: T): Option; + lookupComponent(name: string, meta: T): Option; // ComponentSpec lookupPartial(name: string, meta: T): Option; resolve(specifier: Specifier): U; -} \ No newline at end of file +} diff --git a/packages/@glimmer/opcode-compiler/lib/interfaces.ts b/packages/@glimmer/opcode-compiler/lib/interfaces.ts index 4686fd08dd..d06bbb2d32 100644 --- a/packages/@glimmer/opcode-compiler/lib/interfaces.ts +++ b/packages/@glimmer/opcode-compiler/lib/interfaces.ts @@ -1,5 +1,4 @@ import { Unique, Opaque, SymbolTable, Option, BlockSymbolTable } from "@glimmer/interfaces"; -import { VersionedPathReference } from "@glimmer/reference"; import { Core, SerializedTemplateBlock, TemplateMeta } from "@glimmer/wire-format"; import { Macros } from './syntax'; @@ -16,6 +15,7 @@ export interface Heap { } export interface ComponentCapabilities { + staticDefinitions: boolean; dynamicLayout: boolean; dynamicTag: boolean; prepareArgs: boolean; @@ -61,7 +61,6 @@ export interface Opcode { export type Primitive = undefined | null | boolean | number | string; export interface Constants { - reference(value: VersionedPathReference): number; string(value: string): number; stringArray(strings: string[]): number; array(values: number[]): number; diff --git a/packages/@glimmer/runtime/index.ts b/packages/@glimmer/runtime/index.ts index 52adce098b..1e9e9a2a08 100644 --- a/packages/@glimmer/runtime/index.ts +++ b/packages/@glimmer/runtime/index.ts @@ -59,14 +59,19 @@ export { export { ComponentManager, - ComponentDefinition, + CurriedComponentDefinition, + PublicComponentSpec as ComponentSpec, WithDynamicTagName, PreparedArguments, WithDynamicLayout, - WithStaticLayout, - isComponentDefinition + WithStaticLayout } from './lib/component/interfaces'; +export { + CurriedDefinition, + curry +} from './lib/compiled/opcodes/component'; + export { ModifierManager } from './lib/modifier/interfaces'; diff --git a/packages/@glimmer/runtime/lib/compiled/opcodes/component.ts b/packages/@glimmer/runtime/lib/compiled/opcodes/component.ts index 3642655623..73c01a7824 100644 --- a/packages/@glimmer/runtime/lib/compiled/opcodes/component.ts +++ b/packages/@glimmer/runtime/lib/compiled/opcodes/component.ts @@ -14,18 +14,19 @@ import { CurriedComponentDefinition, hasDynamicLayout, hasStaticLayout, - isComponentDefinition, isCurriedComponentDefinition, WithDynamicTagName, WithElementHook, + ComponentSpec, + PublicComponentSpec } from '../../component/interfaces'; import { normalizeStringValue } from '../../dom/normalize'; import { DynamicScope, Handle, ScopeBlock, ScopeSlot } from '../../environment'; -import { APPEND_OPCODES, OpcodeJSON, UpdatingOpcode } from '../../opcodes'; +import { APPEND_OPCODES, UpdatingOpcode } from '../../opcodes'; import { UNDEFINED_REFERENCE } from '../../references'; import { UpdatingVM, VM } from '../../vm'; import { Arguments, IArguments, ICapturedArguments } from '../../vm/arguments'; -import { IsComponentDefinitionReference } from './content'; +import { IsCurriedComponentDefinitionReference } from './content'; import { UpdateDynamicAttributeOpcode } from './dom'; import { Resolver, Specifier, ComponentDefinition, ComponentManager, Component } from '../../internal-interfaces'; import { dict, assert, unreachable } from "@glimmer/util"; @@ -35,73 +36,133 @@ import { AbstractTemplate, ATTRS_BLOCK } from '@glimmer/opcode-compiler'; const ARGS = new Arguments(); -function resolveComponent(resolver: Resolver, name: string, meta: TemplateMeta): ComponentDefinition { +function resolveComponent(resolver: Resolver, name: string, meta: TemplateMeta): Option { let specifier = resolver.lookupComponent(name, meta); assert(specifier, `Could not find a component named "${name}"`); - return resolver.resolve(specifier!); + return resolver.resolve(specifier!); } -class CurryComponentReference implements VersionedPathReference> { +export const PUBLIC_CURRIED_BRAND = "[CURRIED COMPONENT] 607561bf-57fd-43b2-bf18-fb53d6f9bfb9"; + +export const enum CurriedComponentType { + Spec, + Definition, + Null +} + +export interface CurriedValue { + "[CURRIED COMPONENT] 607561bf-57fd-43b2-bf18-fb53d6f9bfb9": true; + type: CurriedComponentType; + value: Option; +} + +export interface CurriedSpec extends CurriedValue { + type: CurriedComponentType.Spec; + value: ComponentSpec; +} + +export interface CurriedDefinition extends CurriedValue { + type: CurriedComponentType.Definition; + value: CurriedComponentDefinition; +} + +export interface CurriedNull extends CurriedValue{ + type: CurriedComponentType.Null; + value: null; +} + +class BrandedCurriedValue implements CurriedValue { + public "[CURRIED COMPONENT] 607561bf-57fd-43b2-bf18-fb53d6f9bfb9": true = true; + constructor(public type: CurriedComponentType, public value: Option) {} +} + +export function isCurriedValue(value: Opaque): value is CurriedValue { + return value && value[PUBLIC_CURRIED_BRAND]; +} + +export function isCurriedNull(value: CurriedValue): value is CurriedNull { + return value.type === CurriedComponentType.Null; +} + +export function isCurriedSpec(value: CurriedValue): value is CurriedSpec { + return value.type === CurriedComponentType.Spec; +} + +export function isPublicCurriedDefinition(value: CurriedValue): value is CurriedDefinition { + return value.type === CurriedComponentType.Definition; +} + +export type InnerCurriedComponent = CurriedComponentDefinition | ComponentSpec; + +export function curry(spec: PublicComponentSpec, args: Option = null): CurriedDefinition { + return new BrandedCurriedValue(CurriedComponentType.Definition, new CurriedComponentDefinition(spec as ComponentSpec, args)) as CurriedDefinition; +} + +class CurryComponentReference implements VersionedPathReference { public tag: Tag; - private lastValue: Opaque; - private lastDefinition: Option; + private lastValue: Opaque = null; + private lastReturn: Option = null; constructor( - private inner: VersionedReference, + private innerRef: VersionedReference, private resolver: Resolver, private meta: TemplateMeta, private args: Option ) { - this.tag = inner.tag; - this.lastValue = null; - this.lastDefinition = null; + this.tag = innerRef.tag; } - value(): Option { - let { inner, lastValue } = this; + value(): CurriedValue { + let { innerRef, lastValue } = this; - let value = inner.value(); + let value = innerRef.value(); if (value === lastValue) { - return this.lastDefinition; + return this.lastReturn!; } - let definition: Option = null; + let next: Option = null; + let type: CurriedComponentType; - if (isComponentDefinition(value)) { - definition = value; - } else if(typeof value === 'string' && value) { + if (isCurriedComponentDefinition(value)) { + next = value; + type = CurriedComponentType.Definition; + } else if (typeof value === 'string' && value) { let { resolver, meta } = this; - definition = resolveComponent(resolver, value, meta); + next = resolveComponent(resolver, value, meta)!; + type = CurriedComponentType.Spec; + } else if (value === null) { + next = value; + type = CurriedComponentType.Null; + } else { + throw new Error('Passed an illegal value to (component)'); } - definition = this.curry(definition); + next = this.curry(next); this.lastValue = value; - this.lastDefinition = definition; - - return definition; + return this.lastReturn = new BrandedCurriedValue(type, next); } get(): VersionedPathReference { return UNDEFINED_REFERENCE; } - private curry(definition: Option): Option { + private curry(inner: Option): Option { let { args } = this; - if (!definition || !args) { - return definition; + if (!inner || !args) { + return inner; } - return new CurriedComponentDefinition(definition, args); + return new CurriedComponentDefinition(inner, args); } } APPEND_OPCODES.add(Op.IsComponent, vm => { let stack = vm.stack; - stack.push(IsComponentDefinitionReference.create(stack.pop())); + stack.push(IsCurriedComponentDefinitionReference.create(stack.pop())); }); APPEND_OPCODES.add(Op.CurryComponent, (vm, { op1: _meta }) => { @@ -123,32 +184,60 @@ APPEND_OPCODES.add(Op.CurryComponent, (vm, { op1: _meta }) => { }); APPEND_OPCODES.add(Op.PushComponentManager, (vm, { op1: specifier }) => { - let definition = vm.constants.resolveSpecifier(specifier); + let spec = vm.constants.resolveSpecifier(specifier); + + assert(!!spec, `Missing component for ${specifier}`); + let stack = vm.stack; - stack.push({ definition, manager: definition.manager, component: null }); + let { definition, manager } = spec; + stack.push({ definition, manager, component: null }); }); APPEND_OPCODES.add(Op.PushDynamicComponentManager, (vm, { op1: _meta }) => { let stack = vm.stack; - let value = stack.pop>().value(); - let definition: ComponentDefinition; - - if (isComponentDefinition(value)) { - definition = value; - } else { - assert(typeof value === 'string', `Could not find a component named "${String(value)}"`); + let component = stack.pop>().value(); + let definition: ComponentSpec['definition'] | CurriedComponentDefinition; + let manager: Option = null; + if (typeof component === 'string') { let { constants, constants: { resolver } } = vm; let meta = constants.getSerializable(_meta); - definition = resolveComponent(resolver, value as string, meta); + let spec = resolveComponent(resolver, component, meta); + + assert(!!spec, `Could not find a component named "${component}"`); + + definition = spec!.definition; + manager = spec!.manager; + } else if (isCurriedValue(component)) { + debugger; + if (isPublicCurriedDefinition(component)) { + definition = component.value; + } else if (isCurriedSpec(component)) { + let { value: { definition: def, manager: mgr } } = component; + definition = def; + manager = mgr; + } else if (isCurriedNull(component)) { + throw new Error('not implemented (component) resolving to null'); + } else { + assert(typeof component === 'string', `Could not find a component named "${String(component)}"`); + throw unreachable(); + } + } else { + throw new Error('unexpected') } - stack.push({ definition, manager: definition.manager, component: null }); + stack.push({ definition, manager, component: null }); }); interface InitialComponentState { + definition: ComponentDefinition; + manager: Option; + component: null; +} + +interface PopulatedComponentState { definition: ComponentDefinition; manager: ComponentManager; component: null; @@ -172,18 +261,25 @@ APPEND_OPCODES.add(Op.PrepareArgs, (vm, { op1: _state }) => { let state = vm.fetchValue(_state); let { definition, manager } = state; + let args: Arguments; - if (definition.capabilities.prepareArgs !== true) { - return; - } + if (isCurriedComponentDefinition(definition)) { + assert(!manager, "If the component definition was curried, we don't yet have a manager"); - let args = stack.pop(); + args = stack.pop(); - if (isCurriedComponentDefinition(definition)) { - state.definition = definition = definition.unwrap(args); - state.manager = manager = definition.manager; + let { manager: curriedManager } = definition.unwrap(args); + state.manager = manager = curriedManager as ComponentManager; + } else if (manager && manager.getCapabilities(definition).prepareArgs !== true) { + return; + } else if (manager) { + args = stack.pop(); + } else { + throw unreachable(); } + // After this point, the component state is a PopulatedComponentState for sure + let preparedArgs = manager.prepareArgs(definition, args); if (preparedArgs) { @@ -213,13 +309,13 @@ APPEND_OPCODES.add(Op.CreateComponent, (vm, { op1: flags, op2: _state }) => { let definition: ComponentDefinition; let manager: ComponentManager; let dynamicScope = vm.dynamicScope(); - let state = { definition, manager } = vm.fetchValue(_state); + let state = { definition, manager } = vm.fetchValue(_state); let hasDefaultBlock = flags & 1; let args: Option = null; - if (definition.capabilities.createArgs) { + if (manager.getCapabilities(definition).createArgs) { args = vm.stack.peek(); } diff --git a/packages/@glimmer/runtime/lib/compiled/opcodes/content.ts b/packages/@glimmer/runtime/lib/compiled/opcodes/content.ts index f2165ed748..50be01c820 100644 --- a/packages/@glimmer/runtime/lib/compiled/opcodes/content.ts +++ b/packages/@glimmer/runtime/lib/compiled/opcodes/content.ts @@ -2,18 +2,18 @@ import { Opaque } from '@glimmer/interfaces'; import { isConst, Reference, VersionedPathReference, Tag, VersionedReference } from '@glimmer/reference'; import { Op } from '@glimmer/vm'; import { DynamicContentWrapper } from '../../vm/content/dynamic'; -import { isComponentDefinition } from '../../component/interfaces'; import { APPEND_OPCODES, UpdatingOpcode } from '../../opcodes'; import { ConditionalReference } from '../../references'; import { UpdatingVM } from '../../vm'; +import { isPublicCurriedDefinition, isCurriedValue } from './component'; -export class IsComponentDefinitionReference extends ConditionalReference { - static create(inner: Reference): IsComponentDefinitionReference { - return new IsComponentDefinitionReference(inner); +export class IsCurriedComponentDefinitionReference extends ConditionalReference { + static create(inner: Reference): IsCurriedComponentDefinitionReference { + return new IsCurriedComponentDefinitionReference(inner); } toBool(value: Opaque): boolean { - return isComponentDefinition(value); + return isCurriedValue(value) && isPublicCurriedDefinition(value); } } diff --git a/packages/@glimmer/runtime/lib/component/interfaces.ts b/packages/@glimmer/runtime/lib/component/interfaces.ts index 03badb2a4b..46a6d58f0d 100644 --- a/packages/@glimmer/runtime/lib/component/interfaces.ts +++ b/packages/@glimmer/runtime/lib/component/interfaces.ts @@ -1,7 +1,7 @@ import { Simple, Dict, Opaque, Option, Resolver, Unique } from '@glimmer/interfaces'; import { Tag, VersionedPathReference } from '@glimmer/reference'; import { Destroyable } from '@glimmer/util'; -import { ComponentCapabilities } from '@glimmer/opcode-compiler'; +import { ComponentCapabilities, Handle } from '@glimmer/opcode-compiler'; import Bounds from '../bounds'; import { ElementOperations } from '../vm/element-builder'; import Environment, { DynamicScope } from '../environment'; @@ -12,17 +12,38 @@ export interface PreparedArguments { named: Dict>; } -export interface ComponentManager { +// TODO: Think about renaming the concepts: +// +// - ComponentDefinition (opaque static state bucket) +// - Component (opaque instance state bucket) +// - ComponentManager (same thing) +// - ComponentSpec (pair of definition / manager) +export type ComponentDefinition = Unique<'ComponentDefinition'>; + +/** @internal */ +export interface ComponentSpec { + definition: ComponentDefinition; + manager: InternalComponentManager; +} + +export interface PublicComponentSpec> { + definition: Definition; + manager: Manager; +} + +export interface ComponentManager { + getCapabilities(definition: Definition): ComponentCapabilities; + // First, the component manager is asked to prepare the arguments needed // for `create`. This allows for things like closure components where the // args need to be curried before constructing the instance of the state // bucket. - prepareArgs(definition: ComponentDefinition, args: IArguments): Option; + prepareArgs(definition: Definition, args: IArguments): Option; // Then, the component manager is asked to create a bucket of state for // the supplied arguments. From the perspective of Glimmer, this is // an opaque token, but in practice it is probably a component object. - create(env: Environment, definition: ComponentDefinition, args: Option, dynamicScope: DynamicScope, caller: VersionedPathReference, hasDefaultBlock: boolean): Component; + create(env: Environment, definition: Definition, args: Option, dynamicScope: DynamicScope, caller: VersionedPathReference, hasDefaultBlock: boolean): Component; // Next, Glimmer asks the manager to create a reference for the `self` // it should use in the layout. @@ -61,21 +82,21 @@ export interface ComponentManager { getDestructor(component: Component): Option; } -export interface WithDynamicTagName extends ComponentManager { +export interface WithDynamicTagName extends ComponentManager { // If the component asks for the dynamic tag name capability, ask for // the tag name to use. (Only used in the "WrappedBuilder".) getTagName(component: Component): Option; } -export interface WithStaticLayout> extends ComponentManager { - getLayout(definition: ComponentDefinition, resolver: R): Specifier; +export interface WithStaticLayout> extends ComponentManager { + getLayout(definition: Definition, resolver: R): Specifier; } -export interface WithAttributeHook extends ComponentManager { +export interface WithAttributeHook extends ComponentManager { didSplatAttributes(component: Component, element: Component, operations: ElementOperations): void; } -export interface WithElementHook extends ComponentManager { +export interface WithElementHook extends ComponentManager { // The `didCreateElement` hook is run for non-tagless components after the // element as been created, but before it has been appended ("flushed") to // the DOM. This hook allows the manager to save off the element, as well as @@ -86,11 +107,12 @@ export interface WithElementHook extends ComponentManager didCreateElement(component: Component, element: Simple.Element, operations: ElementOperations): void; } -export function hasStaticLayout(definition: ComponentDefinition, _: ComponentManager): _ is WithStaticLayout, Resolver>> { - return definition.capabilities.dynamicLayout === false; +/** @internal */ +export function hasStaticLayout(definition: Definition, manager: ComponentManager): manager is WithStaticLayout, Resolver>> { + return manager.getCapabilities(definition).dynamicLayout === false; } -export interface WithDynamicLayout> extends ComponentManager { +export interface WithDynamicLayout> extends ComponentManager { // Return the compiled layout to use for this component. This is called // *after* the component instance has been created, because you might // want to return a different layout per-instance for optimization reasons @@ -98,17 +120,22 @@ export interface WithDynamicLayout(definition: ComponentDefinition, _: ComponentManager): _ is WithDynamicLayout, Resolver>> { - return definition.capabilities.dynamicLayout === true; +/** @internal */ +export function hasDynamicLayout(definition: ComponentDefinition, manager: ComponentManager): manager is WithDynamicLayout, Resolver>> { + return manager.getCapabilities(definition).dynamicLayout === true; } -const COMPONENT_DEFINITION_BRAND = 'COMPONENT DEFINITION [id=e59c754e-61eb-4392-8c4a-2c0ac72bfcd4]'; +export interface WithStaticDefinitions extends ComponentManager { + getComponentDefinition(definition: ComponentDefinition, handle: Handle): ComponentDefinition; +} -export function isComponentDefinition>(obj: Opaque): obj is ComponentDefinition { - return typeof obj === 'object' && obj !== null && obj[COMPONENT_DEFINITION_BRAND]; +/** @internal */ +export function hasStaticDefinitions(definition: ComponentDefinition, manager: ComponentManager): manager is WithStaticDefinitions { + return manager.getCapabilities(definition).staticDefinitions === true; } -const ALL_CAPABILITIES: ComponentCapabilities = { +export const DEFAULT_CAPABILITIES: ComponentCapabilities = { + staticDefinitions: false, dynamicLayout: true, dynamicTag: true, prepareArgs: true, @@ -117,53 +144,51 @@ const ALL_CAPABILITIES: ComponentCapabilities = { elementHook: false }; -export abstract class ComponentDefinition { - public name: string; // for debugging - public manager: ComponentManager; - public capabilities: ComponentCapabilities = ALL_CAPABILITIES; +const CURRIED_COMPONENT_DEFINITION_BRAND = 'CURRIED COMPONENT DEFINITION [id=6f00feb9-a0ef-4547-99ea-ac328f80acea]'; - constructor(name: string, manager: ComponentManager) { - this[COMPONENT_DEFINITION_BRAND] = true; - this.name = name; - this.manager = manager; - } +export function isCurriedComponentDefinition(definition: Opaque): definition is CurriedComponentDefinition { + return definition && definition[CURRIED_COMPONENT_DEFINITION_BRAND]; } -const CURRIED_COMPONENT_DEFINITION_BRAND = 'CURRIED COMPONENT DEFINITION [id=6f00feb9-a0ef-4547-99ea-ac328f80acea]'; - -export function isCurriedComponentDefinition(definition: ComponentDefinition): definition is CurriedComponentDefinition { - return definition[CURRIED_COMPONENT_DEFINITION_BRAND]; +export function isComponentDefinition(definition: Opaque): definition is CurriedComponentDefinition { + return definition && definition[CURRIED_COMPONENT_DEFINITION_BRAND]; } -export class CurriedComponentDefinition extends ComponentDefinition { - constructor(protected inner: ComponentDefinition, protected args: ICapturedArguments) { - super(CURRIED_COMPONENT_DEFINITION_BRAND, null as any); +export class CurriedComponentDefinition { + /** @internal */ + constructor(protected inner: ComponentSpec | CurriedComponentDefinition, protected args: Option) { this[CURRIED_COMPONENT_DEFINITION_BRAND] = true; } - unwrap(args: Arguments): ComponentDefinition { + unwrap(args: Arguments): ComponentSpec { args.realloc(this.offset); - let definition: ComponentDefinition = this; + let definition: CurriedComponentDefinition = this; + + while (true) { + let { args: curriedArgs, inner } = definition; - while (isCurriedComponentDefinition(definition)) { - let { args: { positional, named }, inner } = definition; + if (curriedArgs) { + args.positional.prepend(curriedArgs.positional); + args.named.merge(curriedArgs.named); + } - args.positional.prepend(positional); - args.named.merge(named); + if (!isCurriedComponentDefinition(inner)) { + return inner; + } definition = inner; } - - return definition; } - protected get offset(): number { - let { inner, args: { positional: { length } } } = this; + /** @internal */ + get offset(): number { + let { inner, args } = this; + let length = args ? args.length : 0; return isCurriedComponentDefinition(inner) ? length + inner.offset : length; } } +export type BrandedComponentDefinition = CurriedComponentDefinition; export type InternalComponent = Unique<'Component'>; -export type InternalComponentManager = ComponentManager; -export type InternalComponentDefinition = ComponentDefinition; +export type InternalComponentManager = ComponentManager; diff --git a/packages/@glimmer/runtime/lib/environment/lookup.ts b/packages/@glimmer/runtime/lib/environment/lookup.ts index 52ed670174..1737828b6c 100644 --- a/packages/@glimmer/runtime/lib/environment/lookup.ts +++ b/packages/@glimmer/runtime/lib/environment/lookup.ts @@ -1,29 +1,36 @@ import { CompileTimeLookup, ComponentCapabilities } from "@glimmer/opcode-compiler"; import { Resolver, Opaque, ProgramSymbolTable, Unique, Option } from "@glimmer/interfaces"; import { TemplateMeta } from "@glimmer/wire-format"; -import { ComponentDefinition, WithStaticLayout } from '../component/interfaces'; +import { WithStaticLayout, ComponentSpec } from '../component/interfaces'; +import { assert } from "@glimmer/util"; export class Lookup implements CompileTimeLookup { constructor(private resolver: Resolver) { } - getCapabilities(name: string, meta: TemplateMeta): ComponentCapabilities { + private getComponentSpec(name: string, meta: TemplateMeta): ComponentSpec { let specifier = this.resolver.lookupComponent(name, meta); - let definition = this.resolver.resolve>(specifier); + let spec = this.resolver.resolve>(specifier); + + assert(!!spec, `Couldn't find a template named ${name}`); + + return spec!; + } - return definition.capabilities; + getCapabilities(name: string, meta: TemplateMeta): ComponentCapabilities { + let { manager, definition } = this.getComponentSpec(name, meta); + return manager.getCapabilities(definition); } getLayout(name: string, meta: TemplateMeta): Option<{ symbolTable: ProgramSymbolTable; handle: Unique<"Handle">; }> { - let specifier = this.resolver.lookupComponent(name, meta); - let definition = this.resolver.resolve>(specifier); - let capabilities = definition.capabilities; + let { manager, definition } = this.getComponentSpec(name, meta); + let capabilities = manager.getCapabilities(definition); if (capabilities.dynamicLayout === true) { return null; } - let layoutSpecifier = (definition.manager as WithStaticLayout).getLayout(definition, this.resolver); + let layoutSpecifier = (manager as WithStaticLayout).getLayout(definition, this.resolver); return this.resolver.resolve<{ symbolTable: ProgramSymbolTable, handle: Unique<'Handle'> }>(layoutSpecifier); } diff --git a/packages/@glimmer/runtime/lib/internal-interfaces.d.ts b/packages/@glimmer/runtime/lib/internal-interfaces.d.ts index 47e2290766..59234d6a39 100644 --- a/packages/@glimmer/runtime/lib/internal-interfaces.d.ts +++ b/packages/@glimmer/runtime/lib/internal-interfaces.d.ts @@ -8,6 +8,6 @@ export type CompilationOptions = ICompilationOptions>>; + ): VersionedPathReference>; } diff --git a/packages/@glimmer/runtime/test/ember-component-test.ts b/packages/@glimmer/runtime/test/ember-component-test.ts index cd3f689f5b..2361583720 100644 --- a/packages/@glimmer/runtime/test/ember-component-test.ts +++ b/packages/@glimmer/runtime/test/ember-component-test.ts @@ -2032,7 +2032,7 @@ QUnit.test('it does not work on optimized appends', () => { env.registerEmberishCurlyComponent('foo-bar', FooBar, 'foo bar'); - let definition = env.resolveComponentDefinition('foo-bar', {}); + let definition = env.componentHelper('foo-bar', {}); appendViewFor('{{foo}}', { foo: definition }); @@ -2056,7 +2056,7 @@ QUnit.test('it works on unoptimized appends (dot paths)', () => { env.registerEmberishCurlyComponent('foo-bar', FooBar, 'foo bar'); - let definition = env.resolveComponentDefinition('foo-bar', {}); + let definition = env.componentHelper('foo-bar', {}); appendViewFor('{{foo.bar}}', { foo: { bar: definition } }); @@ -2088,7 +2088,7 @@ QUnit.test('it works on unoptimized appends (this paths)', () => { env.registerEmberishCurlyComponent('foo-bar', FooBar, 'foo bar'); - let definition = env.resolveComponentDefinition('foo-bar', {}); + let definition = env.componentHelper('foo-bar', {}); appendViewFor('{{this.foo}}', { foo: definition }); @@ -2120,7 +2120,7 @@ QUnit.test('it works on unoptimized appends when initially not a component (dot env.registerEmberishCurlyComponent('foo-bar', FooBar, 'foo bar'); - let definition = env.resolveComponentDefinition('foo-bar', {}); + let definition = env.componentHelper('foo-bar', {}); appendViewFor('{{foo.bar}}', { foo: { bar: 'lol' } }); @@ -2148,7 +2148,7 @@ QUnit.test('it works on unoptimized appends when initially not a component (this env.registerEmberishCurlyComponent('foo-bar', FooBar, 'foo bar'); - let definition = env.resolveComponentDefinition('foo-bar', {}); + let definition = env.componentHelper('foo-bar', {}); appendViewFor('{{this.foo}}', { foo: 'lol' }); diff --git a/packages/@glimmer/test-helpers/lib/environment.ts b/packages/@glimmer/test-helpers/lib/environment.ts index 42be2a43a1..7c14ef837b 100644 --- a/packages/@glimmer/test-helpers/lib/environment.ts +++ b/packages/@glimmer/test-helpers/lib/environment.ts @@ -27,7 +27,6 @@ import { // Components ComponentManager, - ComponentDefinition, PreparedArguments, WithStaticLayout, @@ -53,7 +52,10 @@ import { WithDynamicLayout, CompilationOptions, Handle, - ScannableTemplate + ScannableTemplate, + ComponentSpec, + curry, + CurriedDefinition } from "@glimmer/runtime"; import { @@ -301,7 +303,13 @@ export class EmberishGlimmerComponent extends GlimmerObject { didRender() { } } -class BasicComponentManager implements WithStaticLayout { +class GenericComponentManager { + getCapabilities(definition: GenericComponentDefinition): ComponentCapabilities { + return definition.capabilities; + } +} + +class BasicComponentManager extends GenericComponentManager implements WithStaticLayout { prepareArgs(): null { throw unreachable(); } @@ -376,7 +384,7 @@ export interface EmberishGlimmerStateBucket { component: EmberishGlimmerComponent; } -class EmberishGlimmerComponentManager implements ComponentManager, WithStaticLayout { +class EmberishGlimmerComponentManager extends GenericComponentManager implements ComponentManager, WithStaticLayout { prepareArgs(): null { return null; } @@ -460,7 +468,7 @@ const EMBERISH_GLIMMER_COMPONENT_MANAGER = new EmberishGlimmerComponentManager() const BaseEmberishCurlyComponent = EmberishCurlyComponent.extend() as typeof EmberishCurlyComponent; -class EmberishCurlyComponentManager implements WithDynamicTagName, WithDynamicLayout { +class EmberishCurlyComponentManager extends GenericComponentManager implements WithDynamicTagName, WithDynamicLayout { prepareArgs(definition: EmberishCurlyComponentDefinition, args: Arguments): Option { const { positionalParams } = definition.ComponentClass || BaseEmberishCurlyComponent; @@ -754,7 +762,7 @@ export interface Lookup { helper: GlimmerHelper; modifier: ModifierManager; partial: PartialDefinition; - component: ComponentDefinition; + component: ComponentSpec; template: { compile(): Handle }; 'template-source': string; } @@ -792,7 +800,7 @@ export class TestResolver implements Resolver { helper: new TypedRegistry(), modifier: new TypedRegistry(), partial: new TypedRegistry(), - component: new TypedRegistry>(), + component: new TypedRegistry(), template: new TypedRegistry<{ compile(): Handle }>(), 'template-source': new TypedRegistry() }; @@ -954,8 +962,8 @@ export class TestEnvironment extends Environment { return definition; } - private registerComponent(name: string, definition: ComponentDefinition) { - this.resolver.register('component', name, definition); + private registerComponent(name: string, definition: GenericComponentDefinition) { + this.resolver.register('component', name, { definition, manager: definition.manager }); return definition; } @@ -963,7 +971,7 @@ export class TestEnvironment extends Environment { return this.resolver.register("template-source", name, source); } - registerBasicComponent(name: string, Component: BasicComponentFactory, layoutSource: string): ComponentDefinition { + registerBasicComponent(name: string, Component: BasicComponentFactory, layoutSource: string): void { if (name.indexOf('-') !== -1) { throw new Error("DEPRECATED: dasherized components"); } @@ -971,17 +979,17 @@ export class TestEnvironment extends Environment { let layout = this.registerTemplate(name, layoutSource); let definition = new BasicComponentDefinition(name, BASIC_COMPONENT_MANAGER, Component, layout); - return this.registerComponent(name, definition); + this.registerComponent(name, definition); } - registerStaticTaglessComponent(name: string, Component: BasicComponentFactory, layoutSource: string): ComponentDefinition { + registerStaticTaglessComponent(name: string, Component: BasicComponentFactory, layoutSource: string): void { let layout = this.registerTemplate(name, layoutSource); let definition = new StaticTaglessComponentDefinition(name, STATIC_TAGLESS_COMPONENT_MANAGER, Component, layout); - return this.registerComponent(name, definition); + this.registerComponent(name, definition); } - registerEmberishCurlyComponent(name: string, Component: Option, layoutSource: Option): ComponentDefinition { + registerEmberishCurlyComponent(name: string, Component: Option, layoutSource: Option): void { let layout: Option = null; if (layoutSource !== null) { @@ -989,10 +997,10 @@ export class TestEnvironment extends Environment { } let definition = new EmberishCurlyComponentDefinition(name, EMBERISH_CURLY_COMPONENT_MANAGER, Component || EmberishCurlyComponent, layout); - return this.registerComponent(name, definition); + this.registerComponent(name, definition); } - registerEmberishGlimmerComponent(name: string, Component: Option, layoutSource: string): ComponentDefinition { + registerEmberishGlimmerComponent(name: string, Component: Option, layoutSource: string): void { if (name.indexOf('-') !== -1) { throw new Error("DEPRECATED: dasherized components"); } @@ -1000,7 +1008,7 @@ export class TestEnvironment extends Environment { let layout = this.registerTemplate(name, layoutSource); let definition = new EmberishGlimmerComponentDefinition(name, EMBERISH_GLIMMER_COMPONENT_MANAGER, Component || EmberishGlimmerComponent, layout); - return this.registerComponent(name, definition); + this.registerComponent(name, definition); } toConditionalReference(reference: Reference): Reference { @@ -1021,9 +1029,13 @@ export class TestEnvironment extends Environment { return specifier && this.resolver.resolve(specifier); } - resolveComponentDefinition(name: string, meta: TemplateMeta): Option> { + componentHelper(name: string, meta: TemplateMeta): Option { let specifier = this.resolver.lookupComponent(name, meta); - return specifier && this.resolver.resolve>(specifier); + + if (!specifier) return null; + + let spec = this.resolver.resolve(specifier); + return curry(spec); } resolveModifier(modifierName: string, meta: TemplateMeta): Option { @@ -1095,9 +1107,10 @@ export interface BasicComponentFactory { new (): BasicComponent; } -export abstract class GenericComponentDefinition extends ComponentDefinition { - constructor(name: string, manager: ComponentManager, public ComponentClass: any, public layout: Option) { - super(name, manager); +export abstract class GenericComponentDefinition { + abstract capabilities: ComponentCapabilities; + + constructor(public name: string, public manager: ComponentManager>, public ComponentClass: any, public layout: Option) { } toJSON() { @@ -1106,8 +1119,10 @@ export abstract class GenericComponentDefinition extends ComponentDefinition< } export class BasicComponentDefinition extends GenericComponentDefinition { + public name: string; public ComponentClass: BasicComponentFactory; public capabilities: ComponentCapabilities = { + staticDefinitions: false, dynamicLayout: false, dynamicTag: false, prepareArgs: false, @@ -1120,6 +1135,7 @@ export class BasicComponentDefinition extends GenericComponentDefinition { public ComponentClass: BasicComponentFactory; public capabilities: ComponentCapabilities = { + staticDefinitions: false, dynamicLayout: false, dynamicTag: false, prepareArgs: false, @@ -1135,6 +1151,7 @@ export interface EmberishCurlyComponentFactory { } const CURLY_CAPABILITIES: ComponentCapabilities = { + staticDefinitions: false, dynamicLayout: true, dynamicTag: true, prepareArgs: true, @@ -1156,7 +1173,8 @@ export interface EmberishGlimmerComponentFactory { export class EmberishGlimmerComponentDefinition extends GenericComponentDefinition { public ComponentClass: EmberishGlimmerComponentFactory; - public capabilities: ComponentCapabilities = { + public capabilities: ComponentCapabilities = { + staticDefinitions: false, dynamicLayout: false, dynamicTag: true, prepareArgs: false, From 1008d715a2bdaebb49abfa0f72b243ddfd065f99 Mon Sep 17 00:00:00 2001 From: Yehuda Katz Date: Wed, 9 Aug 2017 21:23:17 -0700 Subject: [PATCH 07/42] [WIP] More passing tests Only one to go! --- .../runtime/lib/compiled/opcodes/component.ts | 131 +++++------------- .../runtime/lib/compiled/opcodes/content.ts | 4 +- .../runtime/lib/component/interfaces.ts | 2 +- .../test-helpers/lib/abstract-test-case.ts | 2 +- 4 files changed, 37 insertions(+), 102 deletions(-) diff --git a/packages/@glimmer/runtime/lib/compiled/opcodes/component.ts b/packages/@glimmer/runtime/lib/compiled/opcodes/component.ts index 73c01a7824..7515fa9472 100644 --- a/packages/@glimmer/runtime/lib/compiled/opcodes/component.ts +++ b/packages/@glimmer/runtime/lib/compiled/opcodes/component.ts @@ -42,120 +42,66 @@ function resolveComponent(resolver: Resolver, name: string, meta: TemplateMeta): return resolver.resolve(specifier!); } -export const PUBLIC_CURRIED_BRAND = "[CURRIED COMPONENT] 607561bf-57fd-43b2-bf18-fb53d6f9bfb9"; - -export const enum CurriedComponentType { - Spec, - Definition, - Null -} - -export interface CurriedValue { - "[CURRIED COMPONENT] 607561bf-57fd-43b2-bf18-fb53d6f9bfb9": true; - type: CurriedComponentType; - value: Option; -} - -export interface CurriedSpec extends CurriedValue { - type: CurriedComponentType.Spec; - value: ComponentSpec; -} - -export interface CurriedDefinition extends CurriedValue { - type: CurriedComponentType.Definition; - value: CurriedComponentDefinition; -} - -export interface CurriedNull extends CurriedValue{ - type: CurriedComponentType.Null; - value: null; -} - -class BrandedCurriedValue implements CurriedValue { - public "[CURRIED COMPONENT] 607561bf-57fd-43b2-bf18-fb53d6f9bfb9": true = true; - constructor(public type: CurriedComponentType, public value: Option) {} -} - -export function isCurriedValue(value: Opaque): value is CurriedValue { - return value && value[PUBLIC_CURRIED_BRAND]; -} - -export function isCurriedNull(value: CurriedValue): value is CurriedNull { - return value.type === CurriedComponentType.Null; +export function curry(spec: PublicComponentSpec, args: Option = null): CurriedComponentDefinition { + return new CurriedComponentDefinition(spec as ComponentSpec, args); } -export function isCurriedSpec(value: CurriedValue): value is CurriedSpec { - return value.type === CurriedComponentType.Spec; -} - -export function isPublicCurriedDefinition(value: CurriedValue): value is CurriedDefinition { - return value.type === CurriedComponentType.Definition; -} - -export type InnerCurriedComponent = CurriedComponentDefinition | ComponentSpec; - -export function curry(spec: PublicComponentSpec, args: Option = null): CurriedDefinition { - return new BrandedCurriedValue(CurriedComponentType.Definition, new CurriedComponentDefinition(spec as ComponentSpec, args)) as CurriedDefinition; -} - -class CurryComponentReference implements VersionedPathReference { +class CurryComponentReference implements VersionedPathReference> { public tag: Tag; - private lastValue: Opaque = null; - private lastReturn: Option = null; + private lastValue: Opaque; + private lastDefinition: Option; constructor( - private innerRef: VersionedReference, + private inner: VersionedReference, private resolver: Resolver, private meta: TemplateMeta, private args: Option ) { - this.tag = innerRef.tag; + this.tag = inner.tag; + this.lastValue = null; + this.lastDefinition = null; } - value(): CurriedValue { - let { innerRef, lastValue } = this; + value(): Option { + let { inner, lastValue } = this; - let value = innerRef.value(); + let value = inner.value(); if (value === lastValue) { - return this.lastReturn!; + return this.lastDefinition; } - let next: Option = null; - let type: CurriedComponentType; + let definition: Option = null; if (isCurriedComponentDefinition(value)) { - next = value; - type = CurriedComponentType.Definition; + definition = value; } else if (typeof value === 'string' && value) { let { resolver, meta } = this; - next = resolveComponent(resolver, value, meta)!; - type = CurriedComponentType.Spec; - } else if (value === null) { - next = value; - type = CurriedComponentType.Null; - } else { - throw new Error('Passed an illegal value to (component)'); + definition = resolveComponent(resolver, value, meta); } - next = this.curry(next); + definition = this.curry(definition); this.lastValue = value; - return this.lastReturn = new BrandedCurriedValue(type, next); + this.lastDefinition = definition; + + return definition; } get(): VersionedPathReference { return UNDEFINED_REFERENCE; } - private curry(inner: Option): Option { + private curry(definition: Option): Option { let { args } = this; - if (!inner || !args) { - return inner; + if (!args && isCurriedComponentDefinition(definition)) { + return definition; + } else if (!definition) { + return null; + } else { + return new CurriedComponentDefinition(definition, args); } - - return new CurriedComponentDefinition(inner, args); } } @@ -197,7 +143,7 @@ APPEND_OPCODES.add(Op.PushComponentManager, (vm, { op1: specifier }) => { APPEND_OPCODES.add(Op.PushDynamicComponentManager, (vm, { op1: _meta }) => { let stack = vm.stack; - let component = stack.pop>().value(); + let component = stack.pop>().value(); let definition: ComponentSpec['definition'] | CurriedComponentDefinition; let manager: Option = null; @@ -210,22 +156,10 @@ APPEND_OPCODES.add(Op.PushDynamicComponentManager, (vm, { op1: _meta }) => { definition = spec!.definition; manager = spec!.manager; - } else if (isCurriedValue(component)) { - debugger; - if (isPublicCurriedDefinition(component)) { - definition = component.value; - } else if (isCurriedSpec(component)) { - let { value: { definition: def, manager: mgr } } = component; - definition = def; - manager = mgr; - } else if (isCurriedNull(component)) { - throw new Error('not implemented (component) resolving to null'); - } else { - assert(typeof component === 'string', `Could not find a component named "${String(component)}"`); - throw unreachable(); - } + } else if (isCurriedComponentDefinition(component)) { + definition = component; } else { - throw new Error('unexpected') + throw unreachable(); } stack.push({ definition, manager, component: null }); @@ -268,8 +202,9 @@ APPEND_OPCODES.add(Op.PrepareArgs, (vm, { op1: _state }) => { args = stack.pop(); - let { manager: curriedManager } = definition.unwrap(args); + let { manager: curriedManager, definition: curriedDefinition } = definition.unwrap(args); state.manager = manager = curriedManager as ComponentManager; + state.definition = definition = curriedDefinition; } else if (manager && manager.getCapabilities(definition).prepareArgs !== true) { return; } else if (manager) { diff --git a/packages/@glimmer/runtime/lib/compiled/opcodes/content.ts b/packages/@glimmer/runtime/lib/compiled/opcodes/content.ts index 50be01c820..44fa3f518a 100644 --- a/packages/@glimmer/runtime/lib/compiled/opcodes/content.ts +++ b/packages/@glimmer/runtime/lib/compiled/opcodes/content.ts @@ -1,3 +1,4 @@ +import { isCurriedComponentDefinition } from '../../component/interfaces'; import { Opaque } from '@glimmer/interfaces'; import { isConst, Reference, VersionedPathReference, Tag, VersionedReference } from '@glimmer/reference'; import { Op } from '@glimmer/vm'; @@ -5,7 +6,6 @@ import { DynamicContentWrapper } from '../../vm/content/dynamic'; import { APPEND_OPCODES, UpdatingOpcode } from '../../opcodes'; import { ConditionalReference } from '../../references'; import { UpdatingVM } from '../../vm'; -import { isPublicCurriedDefinition, isCurriedValue } from './component'; export class IsCurriedComponentDefinitionReference extends ConditionalReference { static create(inner: Reference): IsCurriedComponentDefinitionReference { @@ -13,7 +13,7 @@ export class IsCurriedComponentDefinitionReference extends ConditionalReference } toBool(value: Opaque): boolean { - return isCurriedValue(value) && isPublicCurriedDefinition(value); + return isCurriedComponentDefinition(value); } } diff --git a/packages/@glimmer/runtime/lib/component/interfaces.ts b/packages/@glimmer/runtime/lib/component/interfaces.ts index 46a6d58f0d..f4cfc49d56 100644 --- a/packages/@glimmer/runtime/lib/component/interfaces.ts +++ b/packages/@glimmer/runtime/lib/component/interfaces.ts @@ -147,7 +147,7 @@ export const DEFAULT_CAPABILITIES: ComponentCapabilities = { const CURRIED_COMPONENT_DEFINITION_BRAND = 'CURRIED COMPONENT DEFINITION [id=6f00feb9-a0ef-4547-99ea-ac328f80acea]'; export function isCurriedComponentDefinition(definition: Opaque): definition is CurriedComponentDefinition { - return definition && definition[CURRIED_COMPONENT_DEFINITION_BRAND]; + return !!(definition && definition[CURRIED_COMPONENT_DEFINITION_BRAND]); } export function isComponentDefinition(definition: Opaque): definition is CurriedComponentDefinition { diff --git a/packages/@glimmer/test-helpers/lib/abstract-test-case.ts b/packages/@glimmer/test-helpers/lib/abstract-test-case.ts index 6ee8d4ce85..5d5fffb73c 100644 --- a/packages/@glimmer/test-helpers/lib/abstract-test-case.ts +++ b/packages/@glimmer/test-helpers/lib/abstract-test-case.ts @@ -493,7 +493,7 @@ export abstract class AbstractRenderTest { } protected assertHTML(html: string) { - equalTokens(this.element, html); + equalTokens(this.element, html, html); this.takeSnapshot(); } From 4659065aec4c265184eb4d3edd4b05c46a55aad8 Mon Sep 17 00:00:00 2001 From: Yehuda Katz Date: Thu, 10 Aug 2017 12:30:38 -0700 Subject: [PATCH 08/42] Fix curried component bug --- .../runtime/lib/compiled/opcodes/component.ts | 13 ++++++------- .../@glimmer/runtime/lib/component/interfaces.ts | 2 +- .../@glimmer/runtime/test/ember-component-test.ts | 1 + 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/packages/@glimmer/runtime/lib/compiled/opcodes/component.ts b/packages/@glimmer/runtime/lib/compiled/opcodes/component.ts index 7515fa9472..669932cee8 100644 --- a/packages/@glimmer/runtime/lib/compiled/opcodes/component.ts +++ b/packages/@glimmer/runtime/lib/compiled/opcodes/component.ts @@ -205,17 +205,16 @@ APPEND_OPCODES.add(Op.PrepareArgs, (vm, { op1: _state }) => { let { manager: curriedManager, definition: curriedDefinition } = definition.unwrap(args); state.manager = manager = curriedManager as ComponentManager; state.definition = definition = curriedDefinition; - } else if (manager && manager.getCapabilities(definition).prepareArgs !== true) { - return; - } else if (manager) { - args = stack.pop(); } else { - throw unreachable(); + args = stack.pop(); } - // After this point, the component state is a PopulatedComponentState for sure + if (manager!.getCapabilities(definition).prepareArgs !== true) { + stack.push(args); + return; + } - let preparedArgs = manager.prepareArgs(definition, args); + let preparedArgs = manager!.prepareArgs(definition, args); if (preparedArgs) { args.clear(); diff --git a/packages/@glimmer/runtime/lib/component/interfaces.ts b/packages/@glimmer/runtime/lib/component/interfaces.ts index f4cfc49d56..6ac0f976da 100644 --- a/packages/@glimmer/runtime/lib/component/interfaces.ts +++ b/packages/@glimmer/runtime/lib/component/interfaces.ts @@ -184,7 +184,7 @@ export class CurriedComponentDefinition { /** @internal */ get offset(): number { let { inner, args } = this; - let length = args ? args.length : 0; + let length = args ? args.positional.length : 0; return isCurriedComponentDefinition(inner) ? length + inner.offset : length; } } diff --git a/packages/@glimmer/runtime/test/ember-component-test.ts b/packages/@glimmer/runtime/test/ember-component-test.ts index 2361583720..608e6a5c94 100644 --- a/packages/@glimmer/runtime/test/ember-component-test.ts +++ b/packages/@glimmer/runtime/test/ember-component-test.ts @@ -1120,6 +1120,7 @@ QUnit.test('component helper can curry arguments', () => { ` ); + assertText(stripTight` 1. [outer 1] 2. [outer 2] From 6ad283ab7dacc55c237955eb82e6365e0d8104da Mon Sep 17 00:00:00 2001 From: Yehuda Katz Date: Thu, 10 Aug 2017 15:59:16 -0700 Subject: [PATCH 09/42] WIP tom mode --- packages/@glimmer/bundle-compiler/index.ts | 4 +- .../@glimmer/bundle-compiler/lib/compiler.ts | 26 --- .../@glimmer/bundle-compiler/lib/templates.ts | 144 ++++++++++++--- packages/@glimmer/compiler/lib/compiler.ts | 13 +- .../compiler/lib/javascript-compiler.ts | 29 ++- .../compiler/lib/template-compiler.ts | 16 +- packages/@glimmer/interfaces/index.d.ts | 5 +- packages/@glimmer/interfaces/lib/di.d.ts | 12 +- packages/@glimmer/interfaces/lib/program.d.ts | 7 + .../lib/compilable-template.ts | 19 +- .../@glimmer/opcode-compiler/lib/debug.ts | 6 +- .../opcode-compiler/lib/interfaces.ts | 26 +-- .../opcode-compiler/lib/opcode-builder.ts | 65 +++---- .../@glimmer/opcode-compiler/lib/syntax.ts | 14 +- packages/@glimmer/program/index.ts | 3 + packages/@glimmer/program/lib/constants.ts | 118 +++++++++++++ packages/@glimmer/program/lib/internal.ts | 5 + packages/@glimmer/program/lib/opcode.ts | 22 +++ packages/@glimmer/program/lib/program.ts | 141 +++++++++++++++ packages/@glimmer/program/package.json | 12 ++ packages/@glimmer/runtime/index.ts | 3 - .../runtime/lib/compiled/opcodes/component.ts | 28 +-- .../lib/compiled/opcodes/expressions.ts | 3 +- .../runtime/lib/compiled/opcodes/vm.ts | 10 +- .../runtime/lib/component/interfaces.ts | 8 +- packages/@glimmer/runtime/lib/environment.ts | 166 +----------------- .../runtime/lib/environment/constants.ts | 118 ------------- .../runtime/lib/environment/lookup.ts | 23 +-- .../runtime/lib/internal-interfaces.d.ts | 4 - packages/@glimmer/runtime/lib/opcodes.ts | 10 +- packages/@glimmer/runtime/lib/partial.ts | 4 +- .../@glimmer/runtime/lib/syntax/interfaces.ts | 2 +- packages/@glimmer/runtime/lib/template.ts | 21 +-- packages/@glimmer/runtime/lib/vm/append.ts | 26 ++- .../@glimmer/runtime/lib/vm/render-result.ts | 7 +- packages/@glimmer/runtime/lib/vm/update.ts | 25 +-- packages/@glimmer/runtime/package.json | 3 +- .../@glimmer/runtime/test/template-test.ts | 1 - .../@glimmer/test-helpers/lib/environment.ts | 12 +- packages/@glimmer/test-helpers/lib/helpers.ts | 4 +- 40 files changed, 626 insertions(+), 539 deletions(-) delete mode 100644 packages/@glimmer/bundle-compiler/lib/compiler.ts create mode 100644 packages/@glimmer/interfaces/lib/program.d.ts create mode 100644 packages/@glimmer/program/index.ts create mode 100644 packages/@glimmer/program/lib/constants.ts create mode 100644 packages/@glimmer/program/lib/internal.ts create mode 100644 packages/@glimmer/program/lib/opcode.ts create mode 100644 packages/@glimmer/program/lib/program.ts create mode 100644 packages/@glimmer/program/package.json delete mode 100644 packages/@glimmer/runtime/lib/environment/constants.ts diff --git a/packages/@glimmer/bundle-compiler/index.ts b/packages/@glimmer/bundle-compiler/index.ts index fb006fd192..8094f0d65f 100644 --- a/packages/@glimmer/bundle-compiler/index.ts +++ b/packages/@glimmer/bundle-compiler/index.ts @@ -1,3 +1 @@ -export { - Templates -} from './lib/templates'; +export {} from './lib/templates'; diff --git a/packages/@glimmer/bundle-compiler/lib/compiler.ts b/packages/@glimmer/bundle-compiler/lib/compiler.ts deleted file mode 100644 index f2953dee0f..0000000000 --- a/packages/@glimmer/bundle-compiler/lib/compiler.ts +++ /dev/null @@ -1,26 +0,0 @@ -// import { CompilationOptions, Program } from '@glimmer/runtime'; -// import { Resolver } from "@glimmer/interfaces"; -// import { TemplateMeta } from "@glimmer/wire-format"; -// import { preprocess, ASTPluginBuilder } from "@glimmer/syntax"; - -// export interface EagerCompilerOptions { -// meta: T; -// plugins: ASTPluginBuilder[]; -// id: string; -// } - -// export class EagerCompiler> { -// private program: Program; -// private resolver: R; - -// constructor(private options: CompilationOptions) { -// this.program = options.program; -// this.resolver = options.resolver; -// } - -// compile(input: string, options: EagerCompilerOptions) { -// let ast = preprocess(input, options); -// let { block, meta } = TemplateCompiler.compile(options, ast); -// let { id } = options; -// } -// } diff --git a/packages/@glimmer/bundle-compiler/lib/templates.ts b/packages/@glimmer/bundle-compiler/lib/templates.ts index 73f9114c1d..f11d52a02d 100644 --- a/packages/@glimmer/bundle-compiler/lib/templates.ts +++ b/packages/@glimmer/bundle-compiler/lib/templates.ts @@ -1,45 +1,131 @@ import { ASTPluginBuilder, preprocess } from "@glimmer/syntax"; -import { TemplateMeta, SerializedTemplateBlock, TemplateJavascript, SerializedTemplateWithLazyBlock } from "@glimmer/wire-format"; -import { TemplateCompiler, defaultId } from "@glimmer/compiler"; -import { dict } from "@glimmer/util"; +import { TemplateCompiler } from "@glimmer/compiler"; +import { CompilableTemplate, Macros, OpcodeBuilderConstructor, ComponentCapabilities, CompileTimeLookup, CompileOptions, Handle } from "@glimmer/opcode-compiler"; +import { WriteOnlyProgram, WriteOnlyConstants } from "@glimmer/program"; +import { Option, ProgramSymbolTable, Recast } from "@glimmer/interfaces"; -export interface EagerCompilerOptions { - meta: T; +export interface BundleCompileOptions { plugins: ASTPluginBuilder[]; - lookup: string; } -export interface TemplateJSON { - lookup: string; - block: SerializedTemplateBlock; - meta: T; +export type ModuleName = string; +export type NamedExport = string; + +export interface Specifier { + import: NamedExport; + from: ModuleName; +} + +export class SpecifierMap { + public helpers = new Map(); + public modifiers = new Map(); + public components = new Map(); } -export class Templates { - private templates: TemplateJSON[] = []; - private dict = dict>(); +export class BundleCompiler { + private program = new WriteOnlyProgram(new WriteOnlyConstants()); + public specifiers = new SpecifierMap(); + + constructor( + protected plugins: ASTPluginBuilder[] = [], + protected macros: Macros, + protected Builder: OpcodeBuilderConstructor, + protected delegate: CompilerDelegate + ) {} + + compile(input: string, specifier: Specifier, delegate: CompilerDelegate): { symbolTable: ProgramSymbolTable, handle: Handle } { + let ast = preprocess(input, { plugins: { ast: this.plugins } }); + let template = TemplateCompiler.compile({ meta: null }, ast); + let block = template.toJSON(); + + let { program, macros, Builder } = this; + let lookup = new BundlingLookup(delegate); + + let options: CompileOptions = { + program, + macros, + Builder, + lookup, + asPartial: false + }; - compile(input: string, options: EagerCompilerOptions) { - let ast = preprocess(input, { plugins: { ast: options.plugins } }); - let template = TemplateCompiler.compile(options, ast); - let { lookup } = options; - let { block, meta } = template.toJSON(); + let compilable = CompilableTemplate.topLevel(block, options); - let value = Object.freeze({ lookup, block, meta }); - this.templates.push(value); - this.dict[lookup] = value; + let handle = compilable.compile(); + + this.specifiers.components.set(handle as Recast, specifier); + + return { handle, symbolTable: compilable.symbolTable }; } +} + +export interface CompilerDelegate { + /** + * During compilation, the compiler will ask the delegate about each component + * invocation found in the passed template. If the component exists in scope, + * the delegate should return `true`. If the component does not exist in + * scope, return `false`. (Note that returning `false` will cause the + * compilation process to fail.) + */ + hasComponentInScope(componentName: string, referrer: Specifier): boolean; - eachTemplate(callback: (template: TemplateJavascript) => void) { - let templates = this.templates; - for (let i=0; i = { id, block, meta }; - callback(JSON.stringify(templateJSONObject)); + hasHelperInScope(helperName: string, referer: Specifier): boolean; + resolveHelperSpecifier(helperName: string, referer: Specifier): Specifier; + + hasModifierInScope(modifierName: string, referer: Specifier): boolean; + resolveModifierSpecifier(modifierName: string, referer: Specifier): Specifier; + + hasPartialInScope(partialName: string, referer: Specifier): boolean; + resolvePartialSpecifier(partialName: string, referer: Specifier): Specifier; +} + +class BundlingLookup implements CompileTimeLookup { + constructor(private delegate: CompilerDelegate) {} + + getCapabilities(meta: Specifier): ComponentCapabilities { + return this.delegate.getComponentCapabilities(meta); + } + + getLayout(name: string, meta: Specifier): Option<{ symbolTable: ProgramSymbolTable; handle: Handle }> { + throw new Error("Method not implemented."); + } + + lookupHelper(name: string, referer: Specifier): Option { + if (this.delegate.hasHelperInScope(name, referer)) { + return this.delegate.resolveHelperSpecifier(name, referer); + } else { + return null; } } + + lookupModifier(name: string, referer: Specifier): Option { + if (this.delegate.hasModifierInScope(name, referer)) { + return this.delegate.resolveModifierSpecifier(name, referer); + } else { + return null; + } + } + + lookupComponent(name: string, meta: Specifier): Option { + throw new Error("Method not implemented."); + } + + lookupPartial(name: string, meta: Specifier): Option { + throw new Error("Method not implemented."); + } } diff --git a/packages/@glimmer/compiler/lib/compiler.ts b/packages/@glimmer/compiler/lib/compiler.ts index b92684dfb0..d1bb6a94af 100644 --- a/packages/@glimmer/compiler/lib/compiler.ts +++ b/packages/@glimmer/compiler/lib/compiler.ts @@ -8,7 +8,7 @@ export interface TemplateIdFn { (src: string): Option; } -export interface PrecompileOptions extends CompileOptions, PreprocessOptions { +export interface PrecompileOptions extends CompileOptions, PreprocessOptions { id?: TemplateIdFn; } @@ -38,7 +38,7 @@ export const defaultId: TemplateIdFn = (() => { return function idFn() { return null; }; })(); -const defaultOptions: PrecompileOptions = { +const defaultOptions: PrecompileOptions = { id: defaultId, meta: {} }; @@ -57,16 +57,17 @@ const defaultOptions: PrecompileOptions = { * @param {string} string a Glimmer template string * @return {string} a template javascript string */ -export function precompile(string: string, options?: PrecompileOptions): TemplateJavascript; -export function precompile(string: string, options: PrecompileOptions = defaultOptions): TemplateJavascript { +export function precompile(string: string, options?: PrecompileOptions): TemplateJavascript; +export function precompile(string: string, options: PrecompileOptions = defaultOptions): TemplateJavascript { let ast = preprocess(string, options); - let { block, meta } = TemplateCompiler.compile(options, ast); + let { meta } = options; + let { block } = TemplateCompiler.compile(options, ast); let idFn = options.id || defaultId; let blockJSON = JSON.stringify(block.toJSON()); let templateJSONObject: SerializedTemplateWithLazyBlock = { id: idFn(JSON.stringify(meta) + blockJSON), block: blockJSON, - meta + meta: meta as TemplateMeta }; // JSON is javascript diff --git a/packages/@glimmer/compiler/lib/javascript-compiler.ts b/packages/@glimmer/compiler/lib/javascript-compiler.ts index e0c7d1d849..260ee2ff64 100644 --- a/packages/@glimmer/compiler/lib/javascript-compiler.ts +++ b/packages/@glimmer/compiler/lib/javascript-compiler.ts @@ -5,9 +5,7 @@ import { AST } from '@glimmer/syntax'; import { BlockSymbolTable, ProgramSymbolTable } from './template-visitor'; import { - TemplateMeta, SerializedTemplateBlock, - SerializedTemplate, Core, Statement, Statements, @@ -113,42 +111,39 @@ export class ComponentBlock extends Block { } } -export class Template { +export class Template { public block: TemplateBlock; - constructor(symbols: ProgramSymbolTable, public meta: T) { + constructor(symbols: ProgramSymbolTable) { this.block = new TemplateBlock(symbols); } - toJSON(): SerializedTemplate { - return { - block: this.block.toJSON(), - meta: this.meta - }; + toJSON(): SerializedTemplateBlock { + return this.block.toJSON(); } } -export default class JavaScriptCompiler { - static process(opcodes: any[], symbols: ProgramSymbolTable, meta: T): Template { - let compiler = new JavaScriptCompiler(opcodes, symbols, meta); +export default class JavaScriptCompiler { + static process(opcodes: any[], symbols: ProgramSymbolTable): Template { + let compiler = new JavaScriptCompiler(opcodes, symbols); return compiler.process(); } - private template: Template; + private template: Template; private blocks = new Stack(); private opcodes: any[]; private values: StackValue[] = []; - constructor(opcodes: any[], symbols: ProgramSymbolTable, meta: T) { + constructor(opcodes: any[], symbols: ProgramSymbolTable) { this.opcodes = opcodes; - this.template = new Template(symbols, meta); + this.template = new Template(symbols); } get currentBlock(): Block { return expect(this.blocks.current, 'Expected a block on the stack'); } - process(): Template { + process(): Template { this.opcodes.forEach(([opcode, ...args]) => { if (!this[opcode]) { throw new Error(`unimplemented ${opcode} on JavaScriptCompiler`); } this[opcode](...args); @@ -396,4 +391,4 @@ function isComponent(tag: string): boolean { let open = tag.charAt(0); return open === open.toUpperCase(); -} \ No newline at end of file +} diff --git a/packages/@glimmer/compiler/lib/template-compiler.ts b/packages/@glimmer/compiler/lib/template-compiler.ts index 1d04eb07ae..7e0fb1d616 100644 --- a/packages/@glimmer/compiler/lib/template-compiler.ts +++ b/packages/@glimmer/compiler/lib/template-compiler.ts @@ -2,36 +2,36 @@ import TemplateVisitor, { SymbolTable, Action } from "./template-visitor"; import JavaScriptCompiler, { Template } from "./javascript-compiler"; import { Stack } from "@glimmer/util"; import { assert, expect } from "@glimmer/util"; -import { TemplateMeta } from "@glimmer/wire-format"; import { AST, isLiteral, SyntaxError } from '@glimmer/syntax'; import { getAttrNamespace } from './utils'; +import { Opaque } from "@glimmer/interfaces"; -export interface CompileOptions { - meta: T; +export interface CompileOptions { + meta: Opaque; } function isTrustedValue(value: any) { return value.escaped !== undefined && !value.escaped; } -export default class TemplateCompiler { - static compile(options: CompileOptions, ast: AST.Program): Template { +export default class TemplateCompiler { + static compile(options: CompileOptions, ast: AST.Program): Template { let templateVisitor = new TemplateVisitor(); templateVisitor.visit(ast); let compiler = new TemplateCompiler(options); let opcodes = compiler.process(templateVisitor.actions); - return JavaScriptCompiler.process(opcodes, ast['symbols'], options.meta); + return JavaScriptCompiler.process(opcodes, ast['symbols']); } - private options: CompileOptions; + private options: CompileOptions; private templateId = 0; private templateIds: number[] = []; private symbolStack = new Stack(); private opcodes: any[] = []; private includeMeta = false; - constructor(options: CompileOptions) { + constructor(options: CompileOptions) { this.options = options || {}; } diff --git a/packages/@glimmer/interfaces/index.d.ts b/packages/@glimmer/interfaces/index.d.ts index 9dc2ad364b..b1e2f62833 100644 --- a/packages/@glimmer/interfaces/index.d.ts +++ b/packages/@glimmer/interfaces/index.d.ts @@ -1,6 +1,7 @@ -export * from './lib/tier1/symbol-table'; -export * from './lib/di'; export * from './lib/core'; +export * from './lib/di'; +export * from './lib/program'; +export * from './lib/tier1/symbol-table'; import * as Simple from './lib/dom/simple'; export { Simple }; diff --git a/packages/@glimmer/interfaces/lib/di.d.ts b/packages/@glimmer/interfaces/lib/di.d.ts index a63c4c176a..629be5d7e5 100644 --- a/packages/@glimmer/interfaces/lib/di.d.ts +++ b/packages/@glimmer/interfaces/lib/di.d.ts @@ -1,11 +1,11 @@ import { TemplateMeta } from '@glimmer/wire-format'; import { Opaque, Option, Unique } from './core'; -export interface Resolver { - lookupHelper(name: string, meta: T): Option; - lookupModifier(name: string, meta: T): Option; - lookupComponent(name: string, meta: T): Option; // ComponentSpec - lookupPartial(name: string, meta: T): Option; +export interface Resolver { + lookupHelper(name: string, meta: Specifier): Option; + lookupModifier(name: string, meta: Specifier): Option; + lookupComponent(name: string, meta: Specifier): Option; // ComponentSpec + lookupPartial(name: string, meta: Specifier): Option; - resolve(specifier: Specifier): U; + resolve(specifier: Handle): U; } diff --git a/packages/@glimmer/interfaces/lib/program.d.ts b/packages/@glimmer/interfaces/lib/program.d.ts new file mode 100644 index 0000000000..65d9a8b678 --- /dev/null +++ b/packages/@glimmer/interfaces/lib/program.d.ts @@ -0,0 +1,7 @@ +export interface Opcode { + offset: number; + type: number; + op1: number; + op2: number; + op3: number; +} diff --git a/packages/@glimmer/opcode-compiler/lib/compilable-template.ts b/packages/@glimmer/opcode-compiler/lib/compilable-template.ts index 06740c1c42..6e0b1ac3ff 100644 --- a/packages/@glimmer/opcode-compiler/lib/compilable-template.ts +++ b/packages/@glimmer/opcode-compiler/lib/compilable-template.ts @@ -1,21 +1,32 @@ import { Option, - SymbolTable + SymbolTable, + ProgramSymbolTable, + Opaque } from '@glimmer/interfaces'; -import { Statement } from '@glimmer/wire-format'; +import { Statement, SerializedTemplateBlock } from '@glimmer/wire-format'; +import { OpcodeBuilder } from "@glimmer/opcode-compiler"; import { DEBUG } from '@glimmer/local-debug-flags'; import { debugSlice } from './debug'; import { Handle } from './interfaces'; import { CompilableTemplate as ICompilableTemplate, ParsedLayout } from './interfaces'; import { CompileOptions, compileStatement } from './syntax'; -import { OpcodeBuilder } from "@glimmer/opcode-compiler"; export { ICompilableTemplate }; export default class CompilableTemplate implements ICompilableTemplate { + static topLevel(block: SerializedTemplateBlock, options: CompileOptions): ICompilableTemplate { + return new CompilableTemplate( + block.statements, + { block, meta: {} }, + options, + { meta: {}, hasEval: block.hasEval, symbols: block.symbols } + ); + } + private compiled: Option = null; - constructor(private statements: Statement[], private containingLayout: ParsedLayout, private options: CompileOptions, public symbolTable: S) {} + constructor(private statements: Statement[], private containingLayout: ParsedLayout, private options: CompileOptions, public symbolTable: S) {} compile(): Handle { let { compiled } = this; diff --git a/packages/@glimmer/opcode-compiler/lib/debug.ts b/packages/@glimmer/opcode-compiler/lib/debug.ts index 886ae4ff6a..820f50ca87 100644 --- a/packages/@glimmer/opcode-compiler/lib/debug.ts +++ b/packages/@glimmer/opcode-compiler/lib/debug.ts @@ -1,4 +1,4 @@ -import { Program, Constants } from './interfaces'; +import { CompileTimeProgram, CompileTimeConstants } from './interfaces'; import { Option, Opaque, SymbolTable, Recast } from '@glimmer/interfaces'; import { Op, Register } from '@glimmer/vm'; import { DEBUG, CI } from '@glimmer/local-debug-flags'; @@ -17,7 +17,7 @@ interface LazyDebugConstants { getOther(s: number): T; } -export function debugSlice(program: Program, start: number, end: number) { +export function debugSlice(program: CompileTimeProgram, start: number, end: number) { if (!CI && DEBUG) { /* tslint:disable:no-console */ let { constants } = program; @@ -32,7 +32,7 @@ export function debugSlice(program: Program, start: number, end: number) { for (let i=start; i, type, op1, op2, op3); + let [name, params] = debug(constants as Recast, type, op1, op2, op3); console.log(`${i}. ${logOpcode(name, params)}`); } diff --git a/packages/@glimmer/opcode-compiler/lib/interfaces.ts b/packages/@glimmer/opcode-compiler/lib/interfaces.ts index d06bbb2d32..e952df90dd 100644 --- a/packages/@glimmer/opcode-compiler/lib/interfaces.ts +++ b/packages/@glimmer/opcode-compiler/lib/interfaces.ts @@ -1,10 +1,10 @@ -import { Unique, Opaque, SymbolTable, Option, BlockSymbolTable } from "@glimmer/interfaces"; +import { Unique, Opaque, SymbolTable, Option, BlockSymbolTable, Opcode } from "@glimmer/interfaces"; import { Core, SerializedTemplateBlock, TemplateMeta } from "@glimmer/wire-format"; import { Macros } from './syntax'; export type Handle = Unique<"Handle">; -export interface Heap { +export interface CompileTimeHeap { push(name: /* TODO: Op */ number, op1?: number, op2?: number, op3?: number): void; malloc(): Handle; finishMalloc(handle: Handle): void; @@ -30,7 +30,7 @@ export interface EagerResolver { export interface EagerCompilationOptions> { resolver: R; - program: Program; + program: CompileTimeProgram; macros: Macros; } @@ -41,35 +41,27 @@ export interface CompilableTemplate { export type CompilableBlock = CompilableTemplate; -export interface Program { +export interface CompileTimeProgram { [key: number]: never; - constants: Constants; - heap: Heap; + constants: CompileTimeConstants; + heap: CompileTimeHeap; opcode(offset: number): Opcode; } -export interface Opcode { - offset: number; - type: number; - op1: number; - op2: number; - op3: number; -} - export type Primitive = undefined | null | boolean | number | string; -export interface Constants { +export interface CompileTimeConstants { string(value: string): number; stringArray(strings: string[]): number; array(values: number[]): number; table(t: SymbolTable): number; - specifier(specifier: Opaque): number; + handle(specifier: Opaque): number; serializable(value: Opaque): number; } -export interface LazyConstants extends Constants { +export interface CompileTimeLazyConstants extends CompileTimeConstants { other(value: Opaque): number; } diff --git a/packages/@glimmer/opcode-compiler/lib/opcode-builder.ts b/packages/@glimmer/opcode-compiler/lib/opcode-builder.ts index 9be287d03f..55378d9800 100644 --- a/packages/@glimmer/opcode-compiler/lib/opcode-builder.ts +++ b/packages/@glimmer/opcode-compiler/lib/opcode-builder.ts @@ -1,18 +1,17 @@ -import { Opaque, Option, ProgramSymbolTable, SymbolTable, Present } from '@glimmer/interfaces'; +import { Opaque, Option, ProgramSymbolTable, SymbolTable } from '@glimmer/interfaces'; import { dict, EMPTY_ARRAY, expect, fillNulls, Stack, typePos, unreachable } from '@glimmer/util'; import { Op, Register } from '@glimmer/vm'; import * as WireFormat from '@glimmer/wire-format'; import { TemplateMeta, SerializedInlineBlock } from "@glimmer/wire-format"; import { - Handle, - Heap, - LazyConstants, + Handle as VMHandle, + CompileTimeHeap, + CompileTimeLazyConstants, Primitive, CompilableBlock, - Constants, - Specifier, - Program, + CompileTimeConstants, + CompileTimeProgram, ComponentCapabilities, ParsedLayout } from './interfaces'; @@ -29,10 +28,6 @@ import { ComponentBuilder } from './wrapped-component'; -export interface CompilesInto { - compile(builder: OpcodeBuilder): E; -} - export type Label = string; type TargetOpcode = Op.Jump | Op.JumpIf | Op.JumpUnless | Op.EnterList | Op.Iterate | Op.ReturnTo; @@ -63,32 +58,32 @@ export interface AbstractTemplate { symbolTable: S; } -export interface CompileTimeLookup { - getCapabilities(name: string, meta: TemplateMeta): ComponentCapabilities; - getLayout(name: string, meta: TemplateMeta): Option<{ symbolTable: ProgramSymbolTable, handle: Handle }>; +export interface CompileTimeLookup { + getCapabilities(handle: Handle): ComponentCapabilities; + getLayout(name: string, referer: Specifier): Option<{ symbolTable: ProgramSymbolTable, handle: VMHandle }>; // This interface produces specifiers (and indicates if a name is present), but does not // produce any actual objects. The main use-case for producing objects is handled above, // with getCapabilities and getLayout, which drastically shrinks the size of the object // that the core interface is forced to reify. - lookupHelper(name: string, meta: TemplateMeta): Option; - lookupModifier(name: string, meta: TemplateMeta): Option; - lookupComponent(name: string, meta: TemplateMeta): Option; - lookupPartial(name: string, meta: TemplateMeta): Option; + lookupHelper(name: string, referer: Specifier): Option; + lookupModifier(name: string, referer: Specifier): Option; + lookupComponent(name: string, referer: Specifier): Option; + lookupPartial(name: string, referer: Specifier): Option; } -export interface OpcodeBuilderConstructor { - new(program: Program, - lookup: CompileTimeLookup, +export interface OpcodeBuilderConstructor { + new(program: CompileTimeProgram, + lookup: CompileTimeLookup, meta: TemplateMeta, macros: Macros, containingLayout: ParsedLayout, asPartial: boolean, - Builder: OpcodeBuilderConstructor): { commit(heap: Heap): Handle }; + Builder: OpcodeBuilderConstructor): { commit(heap: CompileTimeHeap): VMHandle }; } -export abstract class OpcodeBuilder = AbstractTemplate> { - public constants: Constants; +export abstract class OpcodeBuilder = AbstractTemplate, Specifier = Opaque, Handle = Opaque> { + public constants: CompileTimeConstants; private buffer: number[] = []; private labelsStack = new Stack(); @@ -96,13 +91,13 @@ export abstract class OpcodeBuilder, public meta: TemplateMeta, public macros: Macros, public containingLayout: ParsedLayout, public asPartial: boolean, - public Builder: OpcodeBuilderConstructor + public Builder: OpcodeBuilderConstructor ) { this.constants = program.constants; } @@ -131,7 +126,7 @@ export abstract class OpcodeBuilder> { - public constants: LazyConstants; +export class LazyOpcodeBuilder extends OpcodeBuilder, Specifier, Handle> { + public constants: CompileTimeLazyConstants; pushSymbolTable(symbolTable: Option) { if (symbolTable) { @@ -902,5 +897,3 @@ export class LazyOpcodeBuilder extends OpcodeBuilder void; diff --git a/packages/@glimmer/opcode-compiler/lib/syntax.ts b/packages/@glimmer/opcode-compiler/lib/syntax.ts index f15ab47750..afc10ca937 100644 --- a/packages/@glimmer/opcode-compiler/lib/syntax.ts +++ b/packages/@glimmer/opcode-compiler/lib/syntax.ts @@ -4,7 +4,7 @@ import { Register } from '@glimmer/vm'; import * as WireFormat from '@glimmer/wire-format'; import * as ClientSide from './client-side'; import OpcodeBuilder, { CompileTimeLookup, OpcodeBuilderConstructor } from "./opcode-builder"; -import { CompilableBlock, Program } from './interfaces'; +import { CompilableBlock, CompileTimeProgram } from './interfaces'; import Ops = WireFormat.Ops; @@ -168,7 +168,7 @@ STATEMENTS.add(Ops.Component, (sexp: S.Component, builder: OpcodeBuilder) => { let specifier = lookup.lookupComponent(tag, builder.meta); if (specifier) { - let capabilities = lookup.getCapabilities(tag, meta); + let capabilities = lookup.getCapabilities(specifier); let attrs: WireFormat.Statement[] = [ [Ops.ClientSideStatement, ClientSide.Ops.SetComponentAttrs, true], @@ -786,16 +786,16 @@ export function compileStatement(statement: WireFormat.Statement, builder: Opcod STATEMENTS.compile(statement, builder); } -export interface TemplateOptions { +export interface TemplateOptions { // already in compilation options - program: Program; + program: CompileTimeProgram; macros: Macros; - Builder: OpcodeBuilderConstructor; + Builder: OpcodeBuilderConstructor; // a subset of the resolver w/ a couple of small tweaks - lookup: CompileTimeLookup; + lookup: CompileTimeLookup; } -export interface CompileOptions extends TemplateOptions { +export interface CompileOptions extends TemplateOptions { asPartial: boolean; } diff --git a/packages/@glimmer/program/index.ts b/packages/@glimmer/program/index.ts new file mode 100644 index 0000000000..222e1d1ab8 --- /dev/null +++ b/packages/@glimmer/program/index.ts @@ -0,0 +1,3 @@ +export * from './lib/constants'; +export * from './lib/program'; +export * from './lib/opcode'; diff --git a/packages/@glimmer/program/lib/constants.ts b/packages/@glimmer/program/lib/constants.ts new file mode 100644 index 0000000000..94bc43c7ca --- /dev/null +++ b/packages/@glimmer/program/lib/constants.ts @@ -0,0 +1,118 @@ +import { Opaque, SymbolTable, Resolver } from "@glimmer/interfaces"; +import { CompileTimeConstants } from "@glimmer/opcode-compiler"; + +const UNRESOLVED = {}; + +export class WriteOnlyConstants implements CompileTimeConstants { + // `0` means NULL + + protected strings: string[] = []; + protected arrays: number[][] = []; + protected tables: SymbolTable[] = []; + protected handles: Handle[] = []; + protected serializables: Opaque[] = []; + protected resolved: Opaque[] = []; + + string(value: string): number { + let index = this.strings.length; + this.strings.push(value); + return index + 1; + } + + stringArray(strings: string[]): number { + let _strings: number[] = new Array(strings.length); + + for (let i = 0; i < strings.length; i++) { + _strings[i] = this.string(strings[i]); + } + + return this.array(_strings); + } + + array(values: number[]): number { + let index = this.arrays.length; + this.arrays.push(values); + return index + 1; + } + + table(t: SymbolTable): number { + let index = this.tables.length; + this.tables.push(t); + return index + 1; + } + + handle(specifier: Handle): number { + let index = this.handles.length; + this.handles.push(specifier); + this.resolved.push(UNRESOLVED); + return index + 1; + } + + serializable(value: Opaque): number { + let index = this.serializables.length; + this.serializables.push(value); + return index + 1; + } +} + +export class Constants extends WriteOnlyConstants { + constructor(public resolver: Resolver) { + super(); + } + + // `0` means NULL + + getString(value: number): string { + return this.strings[value - 1]; + } + + getStringArray(value: number): string[] { + let names = this.getArray(value); + let _names: string[] = new Array(names.length); + + for (let i = 0; i < names.length; i++) { + let n = names[i]; + _names[i] = this.getString(n); + } + + return _names; + } + + getArray(value: number): number[] { + return this.arrays[value - 1]; + } + + getSymbolTable(value: number): T { + return this.tables[value - 1] as T; + } + + resolveSpecifier(s: number): T { + let index = s - 1; + let resolved = this.resolved[index]; + + if (resolved === UNRESOLVED) { + let handle = this.handles[index]; + resolved = this.resolved[index] = this.resolver.resolve(handle); + } + + return resolved as T; + } + + getSerializable(s: number): T { + return this.serializables[s - 1] as T; + } +} + +export class LazyConstants extends Constants { + private others: Opaque[] = []; + + getOther(value: number): T { + return this.others[value - 1] as T; + } + + other(other: Opaque): number { + let index = this.others.length; + this.others.push(other); + return index + 1; + } +} diff --git a/packages/@glimmer/program/lib/internal.ts b/packages/@glimmer/program/lib/internal.ts new file mode 100644 index 0000000000..a4434155ae --- /dev/null +++ b/packages/@glimmer/program/lib/internal.ts @@ -0,0 +1,5 @@ +import { Unique, Resolver as IResolver } from '@glimmer/interfaces'; + +export type Specifier = Unique<'Specifier'>; +export type Referer = Unique<'Referer'>; +export type Resolver = IResolver; diff --git a/packages/@glimmer/program/lib/opcode.ts b/packages/@glimmer/program/lib/opcode.ts new file mode 100644 index 0000000000..546bd09767 --- /dev/null +++ b/packages/@glimmer/program/lib/opcode.ts @@ -0,0 +1,22 @@ +import { Heap } from './program'; + +export class Opcode { + public offset = 0; + constructor(private heap: Heap) {} + + get type() { + return this.heap.getbyaddr(this.offset); + } + + get op1() { + return this.heap.getbyaddr(this.offset + 1); + } + + get op2() { + return this.heap.getbyaddr(this.offset + 2); + } + + get op3() { + return this.heap.getbyaddr(this.offset + 3); + } +} diff --git a/packages/@glimmer/program/lib/program.ts b/packages/@glimmer/program/lib/program.ts new file mode 100644 index 0000000000..f3bf035326 --- /dev/null +++ b/packages/@glimmer/program/lib/program.ts @@ -0,0 +1,141 @@ + +import { Recast } from "@glimmer/interfaces"; +import { DEBUG } from "@glimmer/local-debug-flags"; +import { Constants, WriteOnlyConstants } from './constants'; +import { Opcode } from './opcode'; +import { Handle, CompileTimeProgram } from "@glimmer/opcode-compiler"; + +enum TableSlotState { + Allocated, + Freed, + Purged, + Pointer +} + +export class Heap { + private heap: number[] = []; + private offset = 0; + private handle = 0; + + /** + * layout: + * + * - pointer into heap + * - size + * - freed (0 or 1) + */ + private table: number[] = []; + + push(item: number): void { + this.heap[this.offset++] = item; + } + + getbyaddr(address: number): number { + return this.heap[address]; + } + + setbyaddr(address: number, value: number) { + this.heap[address] = value; + } + + reserve(): Handle { + this.table.push(0, 0, 0); + let handle = this.handle; + this.handle += 3; + return handle as Recast; + } + + malloc(): Handle { + this.table.push(this.offset, 0, 0); + let handle = this.handle; + this.handle += 3; + return handle as Recast; + } + + finishMalloc(handle: Handle): void { + let start = this.table[handle as Recast]; + let finish = this.offset; + this.table[(handle as Recast) + 1] = finish - start; + } + + size(): number { + return this.offset; + } + + // It is illegal to close over this address, as compaction + // may move it. However, it is legal to use this address + // multiple times between compactions. + getaddr(handle: Handle): number { + return this.table[handle as Recast]; + } + + gethandle(address: number): Handle { + this.table.push(address, 0, TableSlotState.Pointer); + let handle = this.handle; + this.handle += 3; + return handle as Recast; + } + + sizeof(handle: Handle): number { + if (DEBUG) { + return this.table[(handle as Recast) + 1]; + } + return -1; + } + + free(handle: Handle): void { + this.table[(handle as Recast) + 2] = 1; + } + + compact(): void { + let compactedSize = 0; + let { table, table: { length }, heap } = this; + + for (let i=0; i implements CompileTimeProgram { + [key: number]: never; + + private _opcode: Opcode; + public heap: Heap; + + constructor(public constants: WriteOnlyConstants) { + this.heap = new Heap(); + this._opcode = new Opcode(this.heap); + } + + opcode(offset: number): Opcode { + this._opcode.offset = offset; + return this._opcode; + } +} + +export class Program extends WriteOnlyProgram { + public constants: Constants; +} diff --git a/packages/@glimmer/program/package.json b/packages/@glimmer/program/package.json new file mode 100644 index 0000000000..e904891c6b --- /dev/null +++ b/packages/@glimmer/program/package.json @@ -0,0 +1,12 @@ +{ + "name": "@glimmer/program", + "version": "0.26.2", + "repository": "https://github.com/glimmerjs/glimmer-vm/tree/master/packages/@glimmer/program", + "dependencies": { + "@glimmer/util": "^0.26.2", + "@glimmer/interfaces": "^0.26.2" + }, + "devDependencies": { + "typescript": "^2.2.0" + } +} diff --git a/packages/@glimmer/runtime/index.ts b/packages/@glimmer/runtime/index.ts index 1e9e9a2a08..897da90979 100644 --- a/packages/@glimmer/runtime/index.ts +++ b/packages/@glimmer/runtime/index.ts @@ -41,11 +41,9 @@ export { SafeString } from './lib/upsert'; export { Scope, - Handle, default as Environment, Helper, DynamicScope, - Program, CompilationOptions } from './lib/environment'; @@ -68,7 +66,6 @@ export { } from './lib/component/interfaces'; export { - CurriedDefinition, curry } from './lib/compiled/opcodes/component'; diff --git a/packages/@glimmer/runtime/lib/compiled/opcodes/component.ts b/packages/@glimmer/runtime/lib/compiled/opcodes/component.ts index 669932cee8..b4b6e1c71b 100644 --- a/packages/@glimmer/runtime/lib/compiled/opcodes/component.ts +++ b/packages/@glimmer/runtime/lib/compiled/opcodes/component.ts @@ -1,4 +1,4 @@ -import { Opaque, Option, Dict, BlockSymbolTable, ProgramSymbolTable, Recast } from '@glimmer/interfaces'; +import { Opaque, Option, Dict, BlockSymbolTable, ProgramSymbolTable, Recast, Resolver } from '@glimmer/interfaces'; import { combineTagged, CONSTANT_TAG, @@ -21,22 +21,22 @@ import { PublicComponentSpec } from '../../component/interfaces'; import { normalizeStringValue } from '../../dom/normalize'; -import { DynamicScope, Handle, ScopeBlock, ScopeSlot } from '../../environment'; +import { DynamicScope, ScopeBlock, ScopeSlot } from '../../environment'; import { APPEND_OPCODES, UpdatingOpcode } from '../../opcodes'; import { UNDEFINED_REFERENCE } from '../../references'; import { UpdatingVM, VM } from '../../vm'; import { Arguments, IArguments, ICapturedArguments } from '../../vm/arguments'; import { IsCurriedComponentDefinitionReference } from './content'; import { UpdateDynamicAttributeOpcode } from './dom'; -import { Resolver, Specifier, ComponentDefinition, ComponentManager, Component } from '../../internal-interfaces'; +import { ComponentDefinition, ComponentManager, Component } from '../../internal-interfaces'; import { dict, assert, unreachable } from "@glimmer/util"; import { Op, Register } from '@glimmer/vm'; import { TemplateMeta } from "@glimmer/wire-format"; -import { AbstractTemplate, ATTRS_BLOCK } from '@glimmer/opcode-compiler'; +import { AbstractTemplate, ATTRS_BLOCK, Handle } from '@glimmer/opcode-compiler'; const ARGS = new Arguments(); -function resolveComponent(resolver: Resolver, name: string, meta: TemplateMeta): Option { +function resolveComponent(resolver: Resolver, name: string, meta: Specifier): Option { let specifier = resolver.lookupComponent(name, meta); assert(specifier, `Could not find a component named "${name}"`); return resolver.resolve(specifier!); @@ -46,15 +46,15 @@ export function curry(spec: PublicComponentSpec, args: Option> { +class CurryComponentReference implements VersionedPathReference> { public tag: Tag; private lastValue: Opaque; private lastDefinition: Option; constructor( private inner: VersionedReference, - private resolver: Resolver, - private meta: TemplateMeta, + private resolver: Resolver, + private meta: Specifier, private args: Option ) { this.tag = inner.tag; @@ -310,7 +310,7 @@ export class ComponentElementOperations { this.attributes[name] = deferred; } - flush(vm: VM) { + flush(vm: VM) { for (let name in this.attributes) { let attr = this.attributes[name]; let { value: reference, namespace, trusting } = attr; @@ -370,12 +370,12 @@ APPEND_OPCODES.add(Op.GetComponentTagName, (vm, { op1: _state }) => { APPEND_OPCODES.add(Op.GetComponentLayout, (vm, { op1: _state }) => { let { manager, definition, component } = vm.fetchValue(_state); let { constants: { resolver }, stack } = vm; - let specifier: Specifier; + let specifier: Opaque; if (hasStaticLayout(definition, manager)) { - specifier = manager.getLayout(definition, resolver) as Specifier; + specifier = manager.getLayout(definition, resolver) as Opaque; } else if (hasDynamicLayout(definition, manager)) { - specifier = manager.getLayout(component, resolver) as Specifier; + specifier = manager.getLayout(component, resolver) as Opaque; } else { throw unreachable(); } @@ -469,7 +469,7 @@ export class UpdateComponentOpcode extends UpdatingOpcode { super(); } - evaluate(_vm: UpdatingVM) { + evaluate(_vm: UpdatingVM) { let { component, manager, dynamicScope } = this; manager.update(component, dynamicScope); @@ -488,7 +488,7 @@ export class DidUpdateLayoutOpcode extends UpdatingOpcode { super(); } - evaluate(vm: UpdatingVM) { + evaluate(vm: UpdatingVM) { let { manager, component, bounds } = this; manager.didUpdateLayout(component, bounds); diff --git a/packages/@glimmer/runtime/lib/compiled/opcodes/expressions.ts b/packages/@glimmer/runtime/lib/compiled/opcodes/expressions.ts index 550033fff6..7840cdeb45 100644 --- a/packages/@glimmer/runtime/lib/compiled/opcodes/expressions.ts +++ b/packages/@glimmer/runtime/lib/compiled/opcodes/expressions.ts @@ -1,12 +1,13 @@ import { Opaque, Option, BlockSymbolTable } from '@glimmer/interfaces'; import { VersionedPathReference } from '@glimmer/reference'; import { Op } from '@glimmer/vm'; -import { Helper, Handle, ScopeBlock } from '../../environment'; +import { Helper, ScopeBlock } from '../../environment'; import { APPEND_OPCODES } from '../../opcodes'; import { FALSE_REFERENCE, TRUE_REFERENCE } from '../../references'; import { PublicVM } from '../../vm'; import { Arguments } from '../../vm/arguments'; import { ConcatReference } from '../expressions/concat'; +import { Handle } from "@glimmer/opcode-compiler"; export type FunctionExpression = (vm: PublicVM) => VersionedPathReference; diff --git a/packages/@glimmer/runtime/lib/compiled/opcodes/vm.ts b/packages/@glimmer/runtime/lib/compiled/opcodes/vm.ts index 0917d4bfb7..4bd147c603 100644 --- a/packages/@glimmer/runtime/lib/compiled/opcodes/vm.ts +++ b/packages/@glimmer/runtime/lib/compiled/opcodes/vm.ts @@ -11,13 +11,13 @@ import { Tag } from '@glimmer/reference'; import { initializeGuid } from '@glimmer/util'; -import { Handle } from '../../environment'; -import { LazyConstants } from '../../environment/constants'; -import { APPEND_OPCODES, UpdatingOpcode } from '../../opcodes'; +import { APPEND_OPCODES, OpcodeJSON, UpdatingOpcode } from '../../opcodes'; import { Primitive, PrimitiveReference, PrimitiveType } from '../../references'; import { CompilableTemplate } from '../../syntax/interfaces'; import { VM, UpdatingVM } from '../../vm'; import { Arguments } from '../../vm/arguments'; +import { LazyConstants } from "@glimmer/program"; +import { Handle } from "@glimmer/opcode-compiler"; APPEND_OPCODES.add(Op.ChildScope, vm => vm.pushChildScope()); @@ -27,7 +27,7 @@ APPEND_OPCODES.add(Op.PushDynamicScope, vm => vm.pushDynamicScope()); APPEND_OPCODES.add(Op.PopDynamicScope, vm => vm.popDynamicScope()); -APPEND_OPCODES.add(Op.Constant, (vm: VM & { constants: LazyConstants }, { op1: other }) => { +APPEND_OPCODES.add(Op.Constant, (vm: VM & { constants: LazyConstants }, { op1: other }) => { vm.stack.push(vm.constants.getOther(other)); }); @@ -189,7 +189,7 @@ export class Assert extends UpdatingOpcode { this.cache = cache; } - evaluate(vm: UpdatingVM) { + evaluate(vm: UpdatingVM) { let { cache } = this; if (isModified(cache.revalidate())) { diff --git a/packages/@glimmer/runtime/lib/component/interfaces.ts b/packages/@glimmer/runtime/lib/component/interfaces.ts index 6ac0f976da..9a9635e31e 100644 --- a/packages/@glimmer/runtime/lib/component/interfaces.ts +++ b/packages/@glimmer/runtime/lib/component/interfaces.ts @@ -88,7 +88,7 @@ export interface WithDynamicTagName extends ComponentManager; } -export interface WithStaticLayout> extends ComponentManager { +export interface WithStaticLayout> extends ComponentManager { getLayout(definition: Definition, resolver: R): Specifier; } @@ -108,11 +108,11 @@ export interface WithElementHook extends ComponentManager(definition: Definition, manager: ComponentManager): manager is WithStaticLayout, Resolver>> { +export function hasStaticLayout(definition: Definition, manager: ComponentManager): manager is WithStaticLayout, Resolver, Opaque>> { return manager.getCapabilities(definition).dynamicLayout === false; } -export interface WithDynamicLayout> extends ComponentManager { +export interface WithDynamicLayout> extends ComponentManager { // Return the compiled layout to use for this component. This is called // *after* the component instance has been created, because you might // want to return a different layout per-instance for optimization reasons @@ -121,7 +121,7 @@ export interface WithDynamicLayout(definition: ComponentDefinition, manager: ComponentManager): manager is WithDynamicLayout, Resolver>> { +export function hasDynamicLayout(definition: ComponentDefinition, manager: ComponentManager): manager is WithDynamicLayout, Resolver, Opaque>> { return manager.getCapabilities(definition).dynamicLayout === true; } diff --git a/packages/@glimmer/runtime/lib/environment.ts b/packages/@glimmer/runtime/lib/environment.ts index e6cbfaa749..cdf5cb667a 100644 --- a/packages/@glimmer/runtime/lib/environment.ts +++ b/packages/@glimmer/runtime/lib/environment.ts @@ -1,7 +1,5 @@ import { VersionedPathReference } from '@glimmer/reference'; -import { Constants, LazyConstants } from './environment/constants'; - import { DOMChanges, DOMTreeConstruction } from './dom/helper'; import { Reference, OpaqueIterable } from '@glimmer/reference'; import { UNDEFINED_REFERENCE, ConditionalReference } from './references'; @@ -24,12 +22,11 @@ import { import { PublicVM } from './vm/append'; -import { Macros, OpcodeBuilderConstructor } from "@glimmer/opcode-compiler"; +import { Macros, OpcodeBuilderConstructor, Handle } from "@glimmer/opcode-compiler"; import { IArguments } from './vm/arguments'; -import { DEBUG } from "@glimmer/local-debug-flags"; -import { Simple, Unique, Resolver, BlockSymbolTable, Recast } from "@glimmer/interfaces"; -import { Component, ComponentManager } from "./internal-interfaces"; -import { TemplateMeta } from "@glimmer/wire-format"; +import { Simple, Resolver, BlockSymbolTable } from "@glimmer/interfaces"; +import { Component, ComponentManager } from "@glimmer/runtime/lib/internal-interfaces"; +import { Program } from "@glimmer/program"; export type ScopeBlock = [Handle, BlockSymbolTable]; export type ScopeSlot = VersionedPathReference | Option; @@ -225,160 +222,11 @@ class Transaction { } } -export class Opcode { - public offset = 0; - constructor(private heap: Heap) {} - - get type() { - return this.heap.getbyaddr(this.offset); - } - - get op1() { - return this.heap.getbyaddr(this.offset + 1); - } - - get op2() { - return this.heap.getbyaddr(this.offset + 2); - } - - get op3() { - return this.heap.getbyaddr(this.offset + 3); - } -} - -export type Handle = Unique<"Handle">; - -enum TableSlotState { - Allocated, - Freed, - Purged, - Pointer -} - -export class Heap { - private heap: number[] = []; - private offset = 0; - private handle = 0; - - /** - * layout: - * - * - pointer into heap - * - size - * - freed (0 or 1) - */ - private table: number[] = []; - - push(item: number): void { - this.heap[this.offset++] = item; - } - - getbyaddr(address: number): number { - return this.heap[address]; - } - - setbyaddr(address: number, value: number) { - this.heap[address] = value; - } - - malloc(): Handle { - this.table.push(this.offset, 0, 0); - let handle = this.handle; - this.handle += 3; - return handle as Recast; - } - - finishMalloc(handle: Handle): void { - let start = this.table[handle as Recast]; - let finish = this.offset; - this.table[(handle as Recast) + 1] = finish - start; - } - - size(): number { - return this.offset; - } - - // It is illegal to close over this address, as compaction - // may move it. However, it is legal to use this address - // multiple times between compactions. - getaddr(handle: Handle): number { - return this.table[handle as Recast]; - } - - gethandle(address: number): Handle { - this.table.push(address, 0, TableSlotState.Pointer); - let handle = this.handle; - this.handle += 3; - return handle as Recast; - } - - sizeof(handle: Handle): number { - if (DEBUG) { - return this.table[(handle as Recast) + 1]; - } - return -1; - } - - free(handle: Handle): void { - this.table[(handle as Recast) + 2] = 1; - } - - compact(): void { - let compactedSize = 0; - let { table, table: { length }, heap } = this; - - for (let i=0; i) { - this.heap = new Heap(); - this._opcode = new Opcode(this.heap); - this.constants = new LazyConstants(resolver); - } - - opcode(offset: number): Opcode { - this._opcode.offset = offset; - return this._opcode; - } -} - -export interface CompilationOptions> { +export interface CompilationOptions> { resolver: R; - program: Program; + program: Program; macros: Macros; - Builder: OpcodeBuilderConstructor; + Builder: OpcodeBuilderConstructor; } export abstract class Environment { diff --git a/packages/@glimmer/runtime/lib/environment/constants.ts b/packages/@glimmer/runtime/lib/environment/constants.ts deleted file mode 100644 index 8a10225468..0000000000 --- a/packages/@glimmer/runtime/lib/environment/constants.ts +++ /dev/null @@ -1,118 +0,0 @@ -import { Opaque, SymbolTable } from "@glimmer/interfaces"; -import { Specifier, Resolver } from '../internal-interfaces'; - -export type ConstantString = number; -export type ConstantFloat = number; -export type ConstantExpression = number; -export type ConstantSlice = number; -export type ConstantBlock = number; -export type ConstantSymbolTable = number; -export type ConstantArray = number; -export type ConstantOther = number; - -const UNRESOLVED = {}; - -export class Constants { - constructor(public resolver: Resolver) {} - - // `0` means NULL - - private strings: string[] = []; - private floats: number[] = []; - private arrays: number[][] = []; - private tables: SymbolTable[] = []; - private specifiers: Specifier[] = []; - private serializables: Opaque[] = []; - private resolved: Opaque[] = []; - - getString(value: ConstantString): string { - return this.strings[value - 1]; - } - - getFloat(value: ConstantFloat): number { - return this.floats[value - 1]; - } - - float(value: number): ConstantFloat { - return this.floats.push(value); - } - - string(value: string): ConstantString { - return this.strings.push(value); - } - - getStringArray(value: ConstantArray): string[] { - let names = this.getArray(value); - let _names: string[] = new Array(names.length); - - for (let i = 0; i < names.length; i++) { - let n = names[i]; - _names[i] = this.getString(n); - } - - return _names; - } - - stringArray(strings: string[]): ConstantArray { - let _strings: ConstantString[] = new Array(strings.length); - - for (let i = 0; i < strings.length; i++) { - _strings[i] = this.string(strings[i]); - } - - return this.array(_strings); - } - - getArray(value: ConstantArray): number[] { - return this.arrays[value - 1]; - } - - array(values: number[]): ConstantArray { - return this.arrays.push(values); - } - - getSymbolTable(value: ConstantSymbolTable): T { - return this.tables[value - 1] as T; - } - - table(t: SymbolTable): ConstantSymbolTable { - return this.tables.push(t); - } - - resolveSpecifier(s: number): T { - let index = s - 1; - let resolved = this.resolved[index]; - - if (resolved === UNRESOLVED) { - let specifier = this.specifiers[index]; - resolved = this.resolved[index] = this.resolver.resolve(specifier); - } - - return resolved as T; - } - - specifier(specifier: Specifier): number { - this.resolved.push(UNRESOLVED); - return this.specifiers.push(specifier); - } - - getSerializable(s: number): T { - return this.serializable[s - 1] as T; - } - - serializable(value: Opaque): number { - return this.serializables.push(value); - } -} - -export class LazyConstants extends Constants { - private others: Opaque[] = []; - - getOther(value: ConstantOther): T { - return this.others[value - 1] as T; - } - - other(other: Opaque): ConstantOther { - return this.others.push(other); - } -} diff --git a/packages/@glimmer/runtime/lib/environment/lookup.ts b/packages/@glimmer/runtime/lib/environment/lookup.ts index 1737828b6c..fad6ec0c55 100644 --- a/packages/@glimmer/runtime/lib/environment/lookup.ts +++ b/packages/@glimmer/runtime/lib/environment/lookup.ts @@ -1,24 +1,27 @@ import { CompileTimeLookup, ComponentCapabilities } from "@glimmer/opcode-compiler"; -import { Resolver, Opaque, ProgramSymbolTable, Unique, Option } from "@glimmer/interfaces"; +import { Resolver, ProgramSymbolTable, Unique, Option } from "@glimmer/interfaces"; import { TemplateMeta } from "@glimmer/wire-format"; import { WithStaticLayout, ComponentSpec } from '../component/interfaces'; import { assert } from "@glimmer/util"; -export class Lookup implements CompileTimeLookup { - constructor(private resolver: Resolver) { +export type LookupHandle = Unique<'LookupHandle'>; + +export class Lookup implements CompileTimeLookup { + constructor(private resolver: Resolver) { } private getComponentSpec(name: string, meta: TemplateMeta): ComponentSpec { let specifier = this.resolver.lookupComponent(name, meta); - let spec = this.resolver.resolve>(specifier); + let spec = this.resolver.resolve>(specifier!); assert(!!spec, `Couldn't find a template named ${name}`); return spec!; } - getCapabilities(name: string, meta: TemplateMeta): ComponentCapabilities { - let { manager, definition } = this.getComponentSpec(name, meta); + getCapabilities(handle: LookupHandle): ComponentCapabilities { + let spec = this.resolver.resolve>(handle); + let { manager, definition } = spec!; return manager.getCapabilities(definition); } @@ -34,19 +37,19 @@ export class Lookup implements CompileTimeLookup { return this.resolver.resolve<{ symbolTable: ProgramSymbolTable, handle: Unique<'Handle'> }>(layoutSpecifier); } - lookupHelper(name: string, meta: TemplateMeta): Opaque { + lookupHelper(name: string, meta: TemplateMeta): Option { return this.resolver.lookupHelper(name, meta); } - lookupModifier(name: string, meta: TemplateMeta): Opaque { + lookupModifier(name: string, meta: TemplateMeta): Option { return this.resolver.lookupModifier(name, meta); } - lookupComponent(name: string, meta: TemplateMeta): Opaque { + lookupComponent(name: string, meta: TemplateMeta): Option { return this.resolver.lookupComponent(name, meta); } - lookupPartial(name: string, meta: TemplateMeta): Opaque { + lookupPartial(name: string, meta: TemplateMeta): Option { return this.resolver.lookupPartial(name, meta); } diff --git a/packages/@glimmer/runtime/lib/internal-interfaces.d.ts b/packages/@glimmer/runtime/lib/internal-interfaces.d.ts index 59234d6a39..2c58d60aab 100644 --- a/packages/@glimmer/runtime/lib/internal-interfaces.d.ts +++ b/packages/@glimmer/runtime/lib/internal-interfaces.d.ts @@ -2,10 +2,6 @@ import { Unique, Resolver as IResolver } from '@glimmer/interfaces'; import { TemplateMeta } from '@glimmer/wire-format'; import { CompilationOptions as ICompilationOptions } from './environment'; -export type Specifier = Unique<'Specifier'>; -export type Resolver = IResolver; -export type CompilationOptions = ICompilationOptions; - export { InternalComponent as Component, ComponentDefinition, diff --git a/packages/@glimmer/runtime/lib/opcodes.ts b/packages/@glimmer/runtime/lib/opcodes.ts index d6c085f3f6..ddc7ddb092 100644 --- a/packages/@glimmer/runtime/lib/opcodes.ts +++ b/packages/@glimmer/runtime/lib/opcodes.ts @@ -1,10 +1,10 @@ import { Option, Dict, Slice as ListSlice, initializeGuid, fillNulls, typePos } from '@glimmer/util'; import { Op } from '@glimmer/vm'; import { Tag } from '@glimmer/reference'; +import { debug, logOpcode } from "@glimmer/opcode-compiler"; +import { Opcode, Opaque } from "@glimmer/interfaces"; import { VM, UpdatingVM } from './vm'; -import { Opcode } from './environment'; import { DEBUG } from '@glimmer/local-debug-flags'; -import { debug, logOpcode } from "@glimmer/opcode-compiler"; export interface OpcodeJSON { type: number | string; @@ -19,7 +19,7 @@ export type Operand1 = number; export type Operand2 = number; export type Operand3 = number; -export type EvaluateOpcode = (vm: VM, opcode: Opcode) => void; +export type EvaluateOpcode = (vm: VM, opcode: Opcode) => void; export class AppendOpcodes { private evaluateOpcode: EvaluateOpcode[] = fillNulls(Op.Size).slice(); @@ -28,7 +28,7 @@ export class AppendOpcodes { this.evaluateOpcode[name as number] = evaluate; } - evaluate(vm: VM, opcode: Opcode, type: number) { + evaluate(vm: VM, opcode: Opcode, type: number) { let func = this.evaluateOpcode[type]; if (DEBUG) { /* tslint:disable */ @@ -68,7 +68,7 @@ export abstract class UpdatingOpcode extends AbstractOpcode { next: Option = null; prev: Option = null; - abstract evaluate(vm: UpdatingVM): void; + abstract evaluate(vm: UpdatingVM): void; } export type UpdatingOpSeq = ListSlice; diff --git a/packages/@glimmer/runtime/lib/partial.ts b/packages/@glimmer/runtime/lib/partial.ts index 57087d564f..e2376e8738 100644 --- a/packages/@glimmer/runtime/lib/partial.ts +++ b/packages/@glimmer/runtime/lib/partial.ts @@ -1,7 +1,7 @@ import { TemplateMeta } from '@glimmer/wire-format'; +import { ProgramSymbolTable } from '@glimmer/interfaces'; +import { Handle } from '@glimmer/opcode-compiler'; import { Template } from './template'; -import { ProgramSymbolTable } from "@glimmer/interfaces"; -import { Handle } from './environment'; export class PartialDefinition { constructor( diff --git a/packages/@glimmer/runtime/lib/syntax/interfaces.ts b/packages/@glimmer/runtime/lib/syntax/interfaces.ts index f7487c839f..8280d895ca 100644 --- a/packages/@glimmer/runtime/lib/syntax/interfaces.ts +++ b/packages/@glimmer/runtime/lib/syntax/interfaces.ts @@ -4,7 +4,7 @@ import { SymbolTable, } from '@glimmer/interfaces'; -import { Handle } from '../environment'; +import { Handle } from '@glimmer/opcode-compiler'; export interface CompilableTemplate { symbolTable: S; diff --git a/packages/@glimmer/runtime/lib/template.ts b/packages/@glimmer/runtime/lib/template.ts index a2669c0018..a61a43f142 100644 --- a/packages/@glimmer/runtime/lib/template.ts +++ b/packages/@glimmer/runtime/lib/template.ts @@ -11,7 +11,7 @@ import { import { NewElementBuilder } from './vm/element-builder'; import { RehydrateBuilder } from './vm/rehydrate-builder'; import { SerializeBuilder } from './vm/serialize-builder'; -import { DynamicScope, Environment, Program } from './environment'; +import { DynamicScope, Environment } from './environment'; import { TopLevelSyntax } from './syntax/interfaces'; import { IteratorResult, RenderResult, VM } from './vm'; import { EMPTY_ARGS, ICapturedArguments } from './vm/arguments'; @@ -20,6 +20,7 @@ import { ParsedLayout, TemplateOptions } from "@glimmer/opcode-compiler"; +import { Program } from "@glimmer/program"; export interface RenderLayoutOptions { env: Environment; @@ -33,7 +34,7 @@ export interface RenderLayoutOptions { /** * Environment specific template. */ -export interface Template { +export interface Template { /** * Template identifier, if precompiled will be the id of the * precompiled template. @@ -43,7 +44,7 @@ export interface Template { /** * Template meta (both compile time and environment specific). */ - meta: T; + meta: TemplateMeta; hasEval: boolean; @@ -77,7 +78,7 @@ export interface TemplateFactory { * * @param {Environment} env glimmer Environment */ - create(env: TemplateOptions): Template; + create(env: TemplateOptions): Template; /** * Used to create an environment specific singleton instance * of the template. @@ -85,11 +86,11 @@ export interface TemplateFactory { * @param {Environment} env glimmer Environment * @param {Object} meta environment specific injections into meta */ - create(env: TemplateOptions, meta: U): Template; + create(env: TemplateOptions, meta: U): Template; } export class TemplateIterator { - constructor(private vm: VM) {} + constructor(private vm: VM) {} next(): IteratorResult { return this.vm.next(); } @@ -107,7 +108,7 @@ export default function templateFactory(serializedTem export default function templateFactory({ id: templateId, meta, block }: SerializedTemplateWithLazyBlock): TemplateFactory<{}, {}> { let parsedBlock: SerializedTemplateBlock; let id = templateId || `client-${clientId++}`; - let create = (options: TemplateOptions, envMeta?: {}) => { + let create = (options: TemplateOptions, envMeta?: {}) => { let newMeta = envMeta ? assign({}, envMeta, meta) : meta; if (!parsedBlock) { parsedBlock = JSON.parse(block); @@ -126,7 +127,7 @@ export class ScannableTemplate implements Template { public meta: TemplateMeta; private statements: Statement[]; - constructor(private options: TemplateOptions, private parsedLayout: ParsedLayout) { + constructor(private options: TemplateOptions, private parsedLayout: ParsedLayout) { let { block } = parsedLayout; this.symbols = block.symbols; this.hasEval = block.hasEval; @@ -142,7 +143,7 @@ export class ScannableTemplate implements Template { let layout = this.asLayout(); let handle = layout.compile(); - let vm = VM.initial(this.options.program as any as Program, env, self, args, dynamicScope, builder, layout.symbolTable, handle); + let vm = VM.initial(this.options.program as any as Program, env, self, args, dynamicScope, builder, layout.symbolTable, handle); return new TemplateIterator(vm); } @@ -157,7 +158,7 @@ export class ScannableTemplate implements Template { } } -export function compilable(layout: ParsedLayout, options: TemplateOptions, asPartial: boolean) { +export function compilable(layout: ParsedLayout, options: TemplateOptions, asPartial: boolean) { let { block, meta } = layout; let { hasEval, symbols } = block; let compileOptions = { ...options, asPartial }; diff --git a/packages/@glimmer/runtime/lib/vm/append.ts b/packages/@glimmer/runtime/lib/vm/append.ts index f4a56ca680..38c31ee661 100644 --- a/packages/@glimmer/runtime/lib/vm/append.ts +++ b/packages/@glimmer/runtime/lib/vm/append.ts @@ -1,6 +1,6 @@ import { ICapturedArguments } from './arguments'; import { Register } from '@glimmer/vm'; -import { Scope, DynamicScope, Environment, Opcode, Handle, Heap, Program } from '../environment'; +import { Scope, DynamicScope, Environment } from '../environment'; import { ElementBuilder } from './element-builder'; import { Option, Destroyable, Stack, LinkedList, ListSlice, Opaque, expect, typePos, assert } from '@glimmer/util'; import { ReferenceIterator, PathReference, VersionedPathReference, combineSlice } from '@glimmer/reference'; @@ -14,11 +14,9 @@ import { UpdatingOpcode } from '../opcodes'; -import { - Constants, - ConstantString -} from '../environment/constants'; -import { ProgramSymbolTable } from "@glimmer/interfaces"; +import { ProgramSymbolTable, Opcode } from "@glimmer/interfaces"; +import { Constants, Heap, Program } from "@glimmer/program"; +import { Handle as VMHandle } from "@glimmer/opcode-compiler"; export interface PublicVM { env: Environment; @@ -97,13 +95,13 @@ export type IteratorResult = { value: T; }; -export default class VM implements PublicVM { +export default class VM implements PublicVM { private dynamicScopeStack = new Stack(); private scopeStack = new Stack(); public updatingOpcodeStack = new Stack>(); public cacheGroups = new Stack>(); public listBlockStack = new Stack(); - public constants: Constants; + public constants: Constants; public heap: Heap; public stack = EvaluationStack.empty(); @@ -183,7 +181,7 @@ export default class VM implements PublicVM { } // Save $pc into $ra, then jump to a new address in `program` (jal in MIPS) - call(handle: Handle) { + call(handle: VMHandle) { this.ra = this.pc; this.pc = this.heap.getaddr(handle); } @@ -198,15 +196,15 @@ export default class VM implements PublicVM { this.pc = this.ra; } - static initial( - program: Program, + static initial( + program: Program, env: Environment, self: PathReference, args: Option, dynamicScope: DynamicScope, elementStack: ElementBuilder, symbolTable: ProgramSymbolTable, - handle: Handle + handle: VMHandle ) { let scope = Scope.root(self, symbolTable.symbols.length); @@ -225,7 +223,7 @@ export default class VM implements PublicVM { } constructor( - private program: Program, + private program: Program, public env: Environment, scope: Scope, dynamicScope: DynamicScope, @@ -412,7 +410,7 @@ export default class VM implements PublicVM { /// EXECUTION - execute(start: Handle, initialize?: (vm: VM) => void): RenderResult { + execute(start: VMHandle, initialize?: (vm: VM) => void): RenderResult { this.pc = this.heap.getaddr(start); if (initialize) initialize(this); diff --git a/packages/@glimmer/runtime/lib/vm/render-result.ts b/packages/@glimmer/runtime/lib/vm/render-result.ts index d799bdf2d1..32596e9371 100644 --- a/packages/@glimmer/runtime/lib/vm/render-result.ts +++ b/packages/@glimmer/runtime/lib/vm/render-result.ts @@ -1,14 +1,15 @@ import { Option, LinkedList } from '@glimmer/util'; -import Environment, { Program } from '../environment'; +import Environment from '../environment'; import { DestroyableBounds, clear } from '../bounds'; import UpdatingVM, { ExceptionHandler } from './update'; import { UpdatingOpcode } from '../opcodes'; -import { Simple } from '@glimmer/interfaces'; +import { Simple, Opaque } from '@glimmer/interfaces'; +import { Program } from "@glimmer/program"; export default class RenderResult implements DestroyableBounds, ExceptionHandler { constructor( private env: Environment, - private program: Program, + private program: Program, private updating: LinkedList, private bounds: DestroyableBounds ) {} diff --git a/packages/@glimmer/runtime/lib/vm/update.ts b/packages/@glimmer/runtime/lib/vm/update.ts index f9dc52eb4a..e4aa1e6e6f 100644 --- a/packages/@glimmer/runtime/lib/vm/update.ts +++ b/packages/@glimmer/runtime/lib/vm/update.ts @@ -1,4 +1,4 @@ -import { Scope, DynamicScope, Environment, Handle, Program } from '../environment'; +import { Scope, DynamicScope, Environment } from '../environment'; import { DestroyableBounds, clear, move as moveBounds } from '../bounds'; import { NewElementBuilder, Tracker, UpdatableTracker } from './element-builder'; import { Option, Opaque, Stack, LinkedList, Dict, dict, expect } from '@glimmer/util'; @@ -18,22 +18,23 @@ import { INITIAL, Tag } from '@glimmer/reference'; -import { UpdatingOpcode, UpdatingOpSeq } from '../opcodes'; -import { Constants } from '../environment/constants'; +import { OpcodeJSON, UpdatingOpcode, UpdatingOpSeq } from '../opcodes'; import { DOMChanges } from '../dom/helper'; import { Simple } from '@glimmer/interfaces'; import VM, { CapturedStack, EvaluationStack } from './append'; +import { Constants, Program } from "@glimmer/program"; +import { Handle } from "@glimmer/opcode-compiler"; -export default class UpdatingVM { +export default class UpdatingVM { public env: Environment; public dom: DOMChanges; public alwaysRevalidate: boolean; - public constants: Constants; + public constants: Constants; private frameStack: Stack = new Stack(); - constructor(env: Environment, program: Program, { alwaysRevalidate = false }) { + constructor(env: Environment, program: Program, { alwaysRevalidate = false }) { this.env = env; this.constants = program.constants; this.dom = env.getDOM(); @@ -83,7 +84,7 @@ export interface ExceptionHandler { export interface VMState { env: Environment; - program: Program; + program: Program; scope: Scope; dynamicScope: DynamicScope; stack: CapturedStack; @@ -118,7 +119,7 @@ export abstract class BlockOpcode extends UpdatingOpcode implements DestroyableB return this.bounds.lastNode(); } - evaluate(vm: UpdatingVM) { + evaluate(vm: UpdatingVM) { vm.try(this.children, null); } @@ -149,7 +150,7 @@ export class TryOpcode extends BlockOpcode implements ExceptionHandler { this._tag.inner.update(combineSlice(this.children)); } - evaluate(vm: UpdatingVM) { + evaluate(vm: UpdatingVM) { vm.try(this.children, this); } @@ -280,7 +281,7 @@ export class ListBlockOpcode extends BlockOpcode { } } - evaluate(vm: UpdatingVM) { + evaluate(vm: UpdatingVM) { let { artifacts, lastIterated } = this; if (!artifacts.tag.validate(lastIterated)) { @@ -302,7 +303,7 @@ export class ListBlockOpcode extends BlockOpcode { super.evaluate(vm); } - vmForInsertion(nextSibling: Option): VM { + vmForInsertion(nextSibling: Option): VM { let { bounds, state } = this; let elementStack = NewElementBuilder.forInitialRender( @@ -317,7 +318,7 @@ export class ListBlockOpcode extends BlockOpcode { class UpdatingVMFrame { private current: Option; - constructor(private vm: UpdatingVM, private ops: UpdatingOpSeq, private exceptionHandler: Option) { + constructor(private vm: UpdatingVM, private ops: UpdatingOpSeq, private exceptionHandler: Option) { this.vm = vm; this.ops = ops; this.current = ops.head(); diff --git a/packages/@glimmer/runtime/package.json b/packages/@glimmer/runtime/package.json index 2b7645d54d..0db029b53f 100644 --- a/packages/@glimmer/runtime/package.json +++ b/packages/@glimmer/runtime/package.json @@ -11,7 +11,8 @@ "@glimmer/object-reference": "^0.27.0", "@glimmer/wire-format": "^0.27.0", "@glimmer/interfaces": "^0.27.0", - "@glimmer/opcode-compiler": "^0.27.0" + "@glimmer/opcode-compiler": "^0.27.0", + "@glimmer/program": "^0.27.0" }, "devDependencies": { "@types/qunit": "^2.0.31", diff --git a/packages/@glimmer/runtime/test/template-test.ts b/packages/@glimmer/runtime/test/template-test.ts index 00f5b611f3..a5940c40e3 100644 --- a/packages/@glimmer/runtime/test/template-test.ts +++ b/packages/@glimmer/runtime/test/template-test.ts @@ -1,6 +1,5 @@ import { TestEnvironment } from "@glimmer/test-helpers"; import { templateFactory } from "@glimmer/runtime"; -import { Templates } from "@glimmer/bundle-compiler"; import { SerializedTemplateWithLazyBlock, TemplateMeta } from "@glimmer/wire-format"; let env: TestEnvironment; diff --git a/packages/@glimmer/test-helpers/lib/environment.ts b/packages/@glimmer/test-helpers/lib/environment.ts index 7c14ef837b..6d753d78ab 100644 --- a/packages/@glimmer/test-helpers/lib/environment.ts +++ b/packages/@glimmer/test-helpers/lib/environment.ts @@ -5,7 +5,8 @@ import { ParsedLayout, WrappedBuilder, TemplateOptions, - LazyOpcodeBuilder + LazyOpcodeBuilder, + Handle } from "@glimmer/opcode-compiler"; import { @@ -47,15 +48,13 @@ import { Template, templateFactory, TopLevelSyntax, - Program, WithDynamicTagName, WithDynamicLayout, CompilationOptions, - Handle, ScannableTemplate, ComponentSpec, curry, - CurriedDefinition + CurriedComponentDefinition } from "@glimmer/runtime"; import { @@ -96,6 +95,7 @@ import * as WireFormat from '@glimmer/wire-format'; import { Simple, Resolver, Unique, ProgramSymbolTable, Recast } from "@glimmer/interfaces"; import { TemplateMeta, SerializedTemplateWithLazyBlock, SerializedTemplateBlock } from "@glimmer/wire-format"; import { precompile } from "@glimmer/compiler"; +import { Program, LazyConstants } from "@glimmer/program"; export type _ = Unique; @@ -901,7 +901,7 @@ class TestMacros extends Macros { export class TestEnvironment extends Environment { public resolver = new TestResolver(); - private program = new Program(this.resolver); + private program = new Program(new LazyConstants(this.resolver)); private uselessAnchor: HTMLAnchorElement; public compiledLayouts = dict(); private lookup: LookupResolver; @@ -1029,7 +1029,7 @@ export class TestEnvironment extends Environment { return specifier && this.resolver.resolve(specifier); } - componentHelper(name: string, meta: TemplateMeta): Option { + componentHelper(name: string, meta: TemplateMeta): Option { let specifier = this.resolver.lookupComponent(name, meta); if (!specifier) return null; diff --git a/packages/@glimmer/test-helpers/lib/helpers.ts b/packages/@glimmer/test-helpers/lib/helpers.ts index 0766e12135..3373357d25 100644 --- a/packages/@glimmer/test-helpers/lib/helpers.ts +++ b/packages/@glimmer/test-helpers/lib/helpers.ts @@ -58,11 +58,11 @@ function isMarker(node: Node) { return false; } -export interface TestCompileOptions extends PrecompileOptions { +export interface TestCompileOptions extends PrecompileOptions { env: Environment; } -export function precompile(string: string, options?: TestCompileOptions): WireFormat.SerializedTemplate { +export function precompile(string: string, options?: TestCompileOptions): WireFormat.SerializedTemplate { let wrapper = JSON.parse(rawPrecompile(string, options)); wrapper.block = JSON.parse(wrapper.block); return wrapper as WireFormat.SerializedTemplate; From 1910bc678ce483f1e00b2a693d550832178e10a2 Mon Sep 17 00:00:00 2001 From: Yehuda Katz Date: Fri, 11 Aug 2017 08:37:21 -0700 Subject: [PATCH 10/42] Get tests passing with handles --- .../@glimmer/bundle-compiler/lib/templates.ts | 51 +++-- packages/@glimmer/interfaces/lib/di.d.ts | 12 +- .../lib/compilable-template.ts | 20 +- .../@glimmer/opcode-compiler/lib/debug.ts | 6 +- .../opcode-compiler/lib/interfaces.ts | 14 +- .../opcode-compiler/lib/opcode-builder.ts | 40 ++-- .../@glimmer/opcode-compiler/lib/syntax.ts | 121 ++++++------ .../opcode-compiler/lib/wrapped-component.ts | 27 ++- packages/@glimmer/program/lib/constants.ts | 16 +- packages/@glimmer/program/lib/internal.ts | 2 +- packages/@glimmer/program/lib/program.ts | 40 ++-- .../runtime/lib/compiled/opcodes/component.ts | 30 +-- .../runtime/lib/compiled/opcodes/dom.ts | 8 +- .../lib/compiled/opcodes/expressions.ts | 8 +- .../runtime/lib/compiled/opcodes/vm.ts | 10 +- .../runtime/lib/component/interfaces.ts | 16 +- packages/@glimmer/runtime/lib/environment.ts | 10 +- .../runtime/lib/environment/lookup.ts | 42 ++-- .../@glimmer/runtime/lib/opcode-builder.ts | 6 +- packages/@glimmer/runtime/lib/opcodes.ts | 6 +- packages/@glimmer/runtime/lib/partial.ts | 9 +- .../@glimmer/runtime/lib/syntax/interfaces.ts | 4 +- packages/@glimmer/runtime/lib/template.ts | 22 +-- packages/@glimmer/runtime/lib/vm/append.ts | 14 +- .../@glimmer/runtime/lib/vm/render-result.ts | 2 +- packages/@glimmer/runtime/lib/vm/update.ts | 26 +-- .../runtime/test/ember-component-test.ts | 12 +- .../@glimmer/test-helpers/lib/environment.ts | 183 ++++++++++-------- packages/@glimmer/vm/lib/opcodes.ts | 4 +- 29 files changed, 390 insertions(+), 371 deletions(-) diff --git a/packages/@glimmer/bundle-compiler/lib/templates.ts b/packages/@glimmer/bundle-compiler/lib/templates.ts index f11d52a02d..f597ced679 100644 --- a/packages/@glimmer/bundle-compiler/lib/templates.ts +++ b/packages/@glimmer/bundle-compiler/lib/templates.ts @@ -1,6 +1,7 @@ +import { SerializedTemplateBlock } from '@glimmer/wire-format'; import { ASTPluginBuilder, preprocess } from "@glimmer/syntax"; import { TemplateCompiler } from "@glimmer/compiler"; -import { CompilableTemplate, Macros, OpcodeBuilderConstructor, ComponentCapabilities, CompileTimeLookup, CompileOptions, Handle } from "@glimmer/opcode-compiler"; +import { CompilableTemplate, Macros, OpcodeBuilderConstructor, ComponentCapabilities, CompileTimeLookup, CompileOptions, VMHandle, ICompilableTemplate } from "@glimmer/opcode-compiler"; import { WriteOnlyProgram, WriteOnlyConstants } from "@glimmer/program"; import { Option, ProgramSymbolTable, Recast } from "@glimmer/interfaces"; @@ -33,15 +34,15 @@ export class BundleCompiler { protected delegate: CompilerDelegate ) {} - compile(input: string, specifier: Specifier, delegate: CompilerDelegate): { symbolTable: ProgramSymbolTable, handle: Handle } { + compile(input: string, specifier: Specifier, delegate: CompilerDelegate): { symbolTable: ProgramSymbolTable, handle: number } { let ast = preprocess(input, { plugins: { ast: this.plugins } }); let template = TemplateCompiler.compile({ meta: null }, ast); let block = template.toJSON(); let { program, macros, Builder } = this; - let lookup = new BundlingLookup(delegate); + let lookup = new BundlingLookup(delegate, this.specifiers); - let options: CompileOptions = { + let options: CompileOptions = { program, macros, Builder, @@ -49,11 +50,13 @@ export class BundleCompiler { asPartial: false }; + lookup.options = options; + let compilable = CompilableTemplate.topLevel(block, options); - let handle = compilable.compile(); + let handle = compilable.compile() as Recast; - this.specifiers.components.set(handle as Recast, specifier); + this.specifiers.components.set(handle, specifier); return { handle, symbolTable: compilable.symbolTable }; } @@ -84,6 +87,8 @@ export interface CompilerDelegate { */ getComponentCapabilities(specifier: Specifier): ComponentCapabilities; + getComponentLayout(specifier: Specifier): SerializedTemplateBlock; + hasHelperInScope(helperName: string, referer: Specifier): boolean; resolveHelperSpecifier(helperName: string, referer: Specifier): Specifier; @@ -95,17 +100,31 @@ export interface CompilerDelegate { } class BundlingLookup implements CompileTimeLookup { - constructor(private delegate: CompilerDelegate) {} + public options: CompileOptions; - getCapabilities(meta: Specifier): ComponentCapabilities { - return this.delegate.getComponentCapabilities(meta); + constructor(private delegate: CompilerDelegate, private specifiers: SpecifierMap) {} + + lookupComponentSpec(name: string, referer: Specifier): Option { + if (this.delegate.hasComponentInScope(name, referer)) { + let specifier = this.delegate.resolveComponentSpecifier(name, referer); + } else { + return null; + } } - getLayout(name: string, meta: Specifier): Option<{ symbolTable: ProgramSymbolTable; handle: Handle }> { - throw new Error("Method not implemented."); + getCapabilities(handle: number): ComponentCapabilities { + let specifier = this.specifiers.components.get(handle)!; + return this.delegate.getComponentCapabilities(specifier); } - lookupHelper(name: string, referer: Specifier): Option { + getLayout(handle: number): Option> { + let specifier = this.specifiers.components.get(handle)!; + let block = this.delegate.getComponentLayout(specifier); + + return CompilableTemplate.topLevel(block, this.options); + } + + lookupHelper(name: string, referer: Specifier): Option { if (this.delegate.hasHelperInScope(name, referer)) { return this.delegate.resolveHelperSpecifier(name, referer); } else { @@ -113,7 +132,7 @@ class BundlingLookup implements CompileTimeLookup { } } - lookupModifier(name: string, referer: Specifier): Option { + lookupModifier(name: string, referer: Specifier): Option { if (this.delegate.hasModifierInScope(name, referer)) { return this.delegate.resolveModifierSpecifier(name, referer); } else { @@ -121,11 +140,7 @@ class BundlingLookup implements CompileTimeLookup { } } - lookupComponent(name: string, meta: Specifier): Option { - throw new Error("Method not implemented."); - } - - lookupPartial(name: string, meta: Specifier): Option { + lookupPartial(name: string, referer: Specifier): Option { throw new Error("Method not implemented."); } } diff --git a/packages/@glimmer/interfaces/lib/di.d.ts b/packages/@glimmer/interfaces/lib/di.d.ts index 629be5d7e5..e7a81fdc3c 100644 --- a/packages/@glimmer/interfaces/lib/di.d.ts +++ b/packages/@glimmer/interfaces/lib/di.d.ts @@ -1,11 +1,11 @@ import { TemplateMeta } from '@glimmer/wire-format'; import { Opaque, Option, Unique } from './core'; -export interface Resolver { - lookupHelper(name: string, meta: Specifier): Option; - lookupModifier(name: string, meta: Specifier): Option; - lookupComponent(name: string, meta: Specifier): Option; // ComponentSpec - lookupPartial(name: string, meta: Specifier): Option; +export interface Resolver { + lookupHelper(name: string, meta: Specifier): Option; + lookupModifier(name: string, meta: Specifier): Option; + lookupComponent(name: string, meta: Specifier): Option; // ComponentSpec + lookupPartial(name: string, meta: Specifier): Option; - resolve(specifier: Handle): U; + resolve(specifier: number): U; } diff --git a/packages/@glimmer/opcode-compiler/lib/compilable-template.ts b/packages/@glimmer/opcode-compiler/lib/compilable-template.ts index 6e0b1ac3ff..e12d20efdf 100644 --- a/packages/@glimmer/opcode-compiler/lib/compilable-template.ts +++ b/packages/@glimmer/opcode-compiler/lib/compilable-template.ts @@ -1,22 +1,20 @@ import { Option, SymbolTable, - ProgramSymbolTable, - Opaque + ProgramSymbolTable } from '@glimmer/interfaces'; import { Statement, SerializedTemplateBlock } from '@glimmer/wire-format'; -import { OpcodeBuilder } from "@glimmer/opcode-compiler"; import { DEBUG } from '@glimmer/local-debug-flags'; import { debugSlice } from './debug'; -import { Handle } from './interfaces'; +import { VMHandle } from './interfaces'; import { CompilableTemplate as ICompilableTemplate, ParsedLayout } from './interfaces'; import { CompileOptions, compileStatement } from './syntax'; export { ICompilableTemplate }; -export default class CompilableTemplate implements ICompilableTemplate { - static topLevel(block: SerializedTemplateBlock, options: CompileOptions): ICompilableTemplate { - return new CompilableTemplate( +export default class CompilableTemplate implements ICompilableTemplate { + static topLevel(block: SerializedTemplateBlock, options: CompileOptions): ICompilableTemplate { + return new CompilableTemplate( block.statements, { block, meta: {} }, options, @@ -24,11 +22,11 @@ export default class CompilableTemplate implements ICompi ); } - private compiled: Option = null; + private compiled: Option = null; - constructor(private statements: Statement[], private containingLayout: ParsedLayout, private options: CompileOptions, public symbolTable: S) {} + constructor(private statements: Statement[], private containingLayout: ParsedLayout, private options: CompileOptions, public symbolTable: S) {} - compile(): Handle { + compile(): VMHandle { let { compiled } = this; if (compiled !== null) return compiled; @@ -39,7 +37,7 @@ export default class CompilableTemplate implements ICompi let builder = new Builder(program, lookup, meta, macros, containingLayout, asPartial, Builder); for (let i = 0; i < statements.length; i++) { - compileStatement(statements[i], builder as OpcodeBuilder); + compileStatement(statements[i], builder); } let handle = builder.commit(program.heap); diff --git a/packages/@glimmer/opcode-compiler/lib/debug.ts b/packages/@glimmer/opcode-compiler/lib/debug.ts index 820f50ca87..6834fbccb1 100644 --- a/packages/@glimmer/opcode-compiler/lib/debug.ts +++ b/packages/@glimmer/opcode-compiler/lib/debug.ts @@ -10,7 +10,7 @@ export interface DebugConstants { getArray(value: number): number[]; getSymbolTable(value: number): T; getSerializable(s: number): T; - resolveSpecifier(s: number): T; + resolveHandle(s: number): T; } interface LazyDebugConstants { @@ -82,7 +82,7 @@ export function debug(c: DebugConstants, op: Op, op1: number, op2: number, op3: switch (op) { case Op.Bug: throw unreachable(); - case Op.Helper: return ['Helper', { helper: c.resolveSpecifier(op1) }]; + case Op.Helper: return ['Helper', { helper: c.resolveHandle(op1) }]; case Op.SetVariable: return ['SetVariable', { symbol: op1 }]; case Op.SetBlock: return ['SetBlock', { symbol: op1 }]; case Op.GetVariable: return ['GetVariable', { symbol: op1 }]; @@ -153,7 +153,7 @@ export function debug(c: DebugConstants, op: Op, op1: number, op2: number, op3: /// COMPONENTS case Op.IsComponent: return ['IsComponent', {}]; case Op.CurryComponent: return ['CurryComponent', { meta: c.getSerializable(op1) }]; - case Op.PushComponentManager: return ['PushComponentManager', { definition: c.resolveSpecifier(op1) }]; + case Op.PushComponentSpec: return ['PushComponentSpec', { definition: c.resolveHandle(op1) }]; case Op.PushDynamicComponentManager: return ['PushDynamicComponentManager', { meta: c.getSerializable(op1) }]; case Op.PushArgs: return ['PushArgs', { names: c.getStringArray(op1), positionals: op2, synthetic: !!op3 }]; case Op.PrepareArgs: return ['PrepareArgs', { state: Register[op1] }]; diff --git a/packages/@glimmer/opcode-compiler/lib/interfaces.ts b/packages/@glimmer/opcode-compiler/lib/interfaces.ts index e952df90dd..469157da0c 100644 --- a/packages/@glimmer/opcode-compiler/lib/interfaces.ts +++ b/packages/@glimmer/opcode-compiler/lib/interfaces.ts @@ -2,16 +2,16 @@ import { Unique, Opaque, SymbolTable, Option, BlockSymbolTable, Opcode } from "@ import { Core, SerializedTemplateBlock, TemplateMeta } from "@glimmer/wire-format"; import { Macros } from './syntax'; -export type Handle = Unique<"Handle">; +export type VMHandle = Unique<"Handle">; export interface CompileTimeHeap { push(name: /* TODO: Op */ number, op1?: number, op2?: number, op3?: number): void; - malloc(): Handle; - finishMalloc(handle: Handle): void; + malloc(): VMHandle; + finishMalloc(handle: VMHandle): void; // for debugging - getaddr(handle: Handle): number; - sizeof(handle: Handle): number; + getaddr(handle: VMHandle): number; + sizeof(handle: VMHandle): number; } export interface ComponentCapabilities { @@ -36,7 +36,7 @@ export interface EagerCompilationOptions { symbolTable: S; - compile(): Handle; + compile(): VMHandle; } export type CompilableBlock = CompilableTemplate; @@ -69,7 +69,7 @@ export type ComponentArgs = [Core.Params, Core.Hash, Option, Op export type Specifier = Opaque; export interface ComponentBuilder { - static(definition: Specifier, args: ComponentArgs): void; + static(definition: number, args: ComponentArgs): void; } export interface ParsedLayout { diff --git a/packages/@glimmer/opcode-compiler/lib/opcode-builder.ts b/packages/@glimmer/opcode-compiler/lib/opcode-builder.ts index 55378d9800..5f2d613ce9 100644 --- a/packages/@glimmer/opcode-compiler/lib/opcode-builder.ts +++ b/packages/@glimmer/opcode-compiler/lib/opcode-builder.ts @@ -5,7 +5,7 @@ import * as WireFormat from '@glimmer/wire-format'; import { TemplateMeta, SerializedInlineBlock } from "@glimmer/wire-format"; import { - Handle as VMHandle, + VMHandle as VMHandle, CompileTimeHeap, CompileTimeLazyConstants, Primitive, @@ -22,7 +22,7 @@ import { expr } from './syntax'; -import CompilableTemplate from './compilable-template'; +import CompilableTemplate, { ICompilableTemplate } from './compilable-template'; import { ComponentBuilder @@ -58,46 +58,46 @@ export interface AbstractTemplate { symbolTable: S; } -export interface CompileTimeLookup { - getCapabilities(handle: Handle): ComponentCapabilities; - getLayout(name: string, referer: Specifier): Option<{ symbolTable: ProgramSymbolTable, handle: VMHandle }>; +export interface CompileTimeLookup { + getCapabilities(handle: number): ComponentCapabilities; + getLayout(handle: number): Option>; // This interface produces specifiers (and indicates if a name is present), but does not // produce any actual objects. The main use-case for producing objects is handled above, // with getCapabilities and getLayout, which drastically shrinks the size of the object // that the core interface is forced to reify. - lookupHelper(name: string, referer: Specifier): Option; - lookupModifier(name: string, referer: Specifier): Option; - lookupComponent(name: string, referer: Specifier): Option; - lookupPartial(name: string, referer: Specifier): Option; + lookupHelper(name: string, referer: Specifier): Option; + lookupModifier(name: string, referer: Specifier): Option; + lookupComponentSpec(name: string, referer: Specifier): Option; + lookupPartial(name: string, referer: Specifier): Option; } -export interface OpcodeBuilderConstructor { +export interface OpcodeBuilderConstructor { new(program: CompileTimeProgram, - lookup: CompileTimeLookup, + lookup: CompileTimeLookup, meta: TemplateMeta, macros: Macros, containingLayout: ParsedLayout, asPartial: boolean, - Builder: OpcodeBuilderConstructor): { commit(heap: CompileTimeHeap): VMHandle }; + Builder: OpcodeBuilderConstructor): OpcodeBuilder; } -export abstract class OpcodeBuilder = AbstractTemplate, Specifier = Opaque, Handle = Opaque> { +export abstract class OpcodeBuilder = AbstractTemplate> { public constants: CompileTimeConstants; private buffer: number[] = []; private labelsStack = new Stack(); private isComponentAttrs = false; - public component: ComponentBuilder = new ComponentBuilder(this); + public component: ComponentBuilder = new ComponentBuilder(this); constructor( public program: CompileTimeProgram, - public lookup: CompileTimeLookup, + public lookup: CompileTimeLookup, public meta: TemplateMeta, public macros: Macros, public containingLayout: ParsedLayout, public asPartial: boolean, - public Builder: OpcodeBuilderConstructor + public Builder: OpcodeBuilderConstructor ) { this.constants = program.constants; } @@ -171,8 +171,8 @@ export abstract class OpcodeBuilder extends OpcodeBuilder, Specifier, Handle> { +export class LazyOpcodeBuilder extends OpcodeBuilder> { public constants: CompileTimeLazyConstants; pushSymbolTable(symbolTable: Option) { @@ -874,7 +874,7 @@ export class LazyOpcodeBuilder extends OpcodeBuilder>) { + pushLayout(layout: Option>) { if (layout) { this.pushOther(layout); } else { diff --git a/packages/@glimmer/opcode-compiler/lib/syntax.ts b/packages/@glimmer/opcode-compiler/lib/syntax.ts index afc10ca937..8624391bfc 100644 --- a/packages/@glimmer/opcode-compiler/lib/syntax.ts +++ b/packages/@glimmer/opcode-compiler/lib/syntax.ts @@ -1,4 +1,4 @@ -import { Option } from '@glimmer/interfaces'; +import { Option, Opaque } from '@glimmer/interfaces'; import { assert, dict, unwrap, EMPTY_ARRAY } from '@glimmer/util'; import { Register } from '@glimmer/vm'; import * as WireFormat from '@glimmer/wire-format'; @@ -9,22 +9,22 @@ import { CompilableBlock, CompileTimeProgram } from './interfaces'; import Ops = WireFormat.Ops; export type TupleSyntax = WireFormat.Statement | WireFormat.TupleExpression; -export type CompilerFunction = ((sexp: T, builder: OpcodeBuilder) => void); +export type CompilerFunction = ((sexp: T, builder: OpcodeBuilder) => void); export const ATTRS_BLOCK = '&attrs'; class Compilers { private names = dict(); - private funcs: CompilerFunction[] = []; + private funcs: CompilerFunction[] = []; constructor(private offset = 0) {} - add(name: number, func: CompilerFunction): void { + add(name: number, func: CompilerFunction): void { this.funcs.push(func); this.names[name] = this.funcs.length - 1; } - compile(sexp: T, builder: OpcodeBuilder): void { + compile(sexp: T, builder: OpcodeBuilder): void { let name: number = sexp[this.offset]; let index = this.names[name]; let func = this.funcs[index]; @@ -38,23 +38,23 @@ import S = WireFormat.Statements; const STATEMENTS = new Compilers(); const CLIENT_SIDE = new Compilers(1); -STATEMENTS.add(Ops.Text, (sexp: S.Text, builder: OpcodeBuilder) => { +STATEMENTS.add(Ops.Text, (sexp: S.Text, builder) => { builder.text(sexp[1]); }); -STATEMENTS.add(Ops.Comment, (sexp: S.Comment, builder: OpcodeBuilder) => { +STATEMENTS.add(Ops.Comment, (sexp: S.Comment, builder) => { builder.comment(sexp[1]); }); -STATEMENTS.add(Ops.CloseElement, (_sexp: S.CloseElement, builder: OpcodeBuilder) => { +STATEMENTS.add(Ops.CloseElement, (_sexp: S.CloseElement, builder) => { builder.closeElement(); }); -STATEMENTS.add(Ops.FlushElement, (_sexp: S.FlushElement, builder: OpcodeBuilder) => { +STATEMENTS.add(Ops.FlushElement, (_sexp: S.FlushElement, builder) => { builder.flushElement(); }); -STATEMENTS.add(Ops.Modifier, (sexp: S.Modifier, builder: OpcodeBuilder) => { +STATEMENTS.add(Ops.Modifier, (sexp: S.Modifier, builder) => { let { lookup, meta } = builder; let [, name, params, hash] = sexp; @@ -68,20 +68,20 @@ STATEMENTS.add(Ops.Modifier, (sexp: S.Modifier, builder: OpcodeBuilder) => { } }); -STATEMENTS.add(Ops.StaticAttr, (sexp: S.StaticAttr, builder: OpcodeBuilder) => { +STATEMENTS.add(Ops.StaticAttr, (sexp: S.StaticAttr, builder) => { let [, name, value, namespace] = sexp; builder.staticAttr(name, namespace, value as string); }); -STATEMENTS.add(Ops.DynamicAttr, (sexp: S.DynamicAttr, builder: OpcodeBuilder) => { +STATEMENTS.add(Ops.DynamicAttr, (sexp: S.DynamicAttr, builder) => { dynamicAttr(sexp, false, builder); }); -STATEMENTS.add(Ops.TrustingAttr, (sexp: S.DynamicAttr, builder: OpcodeBuilder) => { +STATEMENTS.add(Ops.TrustingAttr, (sexp: S.DynamicAttr, builder) => { dynamicAttr(sexp, true, builder); }); -function dynamicAttr(sexp: S.DynamicAttr | S.TrustingAttr, trusting: boolean, builder: OpcodeBuilder) { +function dynamicAttr(sexp: S.DynamicAttr | S.TrustingAttr, trusting: boolean, builder: OpcodeBuilder) { let [, name, value, namespace] = sexp; expr(value, builder); @@ -93,7 +93,7 @@ function dynamicAttr(sexp: S.DynamicAttr | S.TrustingAttr, trusting: boolean, bu } } -STATEMENTS.add(Ops.OpenElement, (sexp: S.OpenElement, builder: OpcodeBuilder) => { +STATEMENTS.add(Ops.OpenElement, (sexp: S.OpenElement, builder) => { builder.openPrimitiveElement(sexp[1]); }); @@ -103,12 +103,12 @@ STATEMENTS.add(Ops.OpenSplattedElement, (sexp: S.SplatElement, builder) => { builder.openElementWithOperations(sexp[1]); }); -CLIENT_SIDE.add(ClientSide.Ops.OpenComponentElement, (sexp: ClientSide.OpenComponentElement, builder: OpcodeBuilder) => { +CLIENT_SIDE.add(ClientSide.Ops.OpenComponentElement, (sexp: ClientSide.OpenComponentElement, builder) => { builder.putComponentOperations(); builder.openElementWithOperations(sexp[2]); }); -CLIENT_SIDE.add(ClientSide.Ops.DidCreateElement, (_sexp: ClientSide.DidCreateElement, builder: OpcodeBuilder) => { +CLIENT_SIDE.add(ClientSide.Ops.DidCreateElement, (_sexp: ClientSide.DidCreateElement, builder) => { builder.didCreateElement(Register.s0); }); @@ -121,11 +121,11 @@ CLIENT_SIDE.add(ClientSide.Ops.Debugger, () => { debugger; }); -CLIENT_SIDE.add(ClientSide.Ops.DidRenderLayout, (_sexp: ClientSide.DidRenderLayout, builder: OpcodeBuilder) => { +CLIENT_SIDE.add(ClientSide.Ops.DidRenderLayout, (_sexp: ClientSide.DidRenderLayout, builder) => { builder.didRenderLayout(Register.s0); }); -STATEMENTS.add(Ops.Append, (sexp: S.Append, builder: OpcodeBuilder) => { +STATEMENTS.add(Ops.Append, (sexp: S.Append, builder) => { let [, value, trusting] = sexp; let { inlines } = builder.macros; @@ -148,7 +148,7 @@ STATEMENTS.add(Ops.Append, (sexp: S.Append, builder: OpcodeBuilder) => { } }); -STATEMENTS.add(Ops.Block, (sexp: S.Block, builder: OpcodeBuilder) => { +STATEMENTS.add(Ops.Block, (sexp: S.Block, builder) => { let [, name, params, hash, _template, _inverse] = sexp; let template = builder.template(_template); let inverse = builder.template(_inverse); @@ -160,15 +160,14 @@ STATEMENTS.add(Ops.Block, (sexp: S.Block, builder: OpcodeBuilder) => { blocks.compile(name, params, hash, templateBlock, inverseBlock, builder); }); -STATEMENTS.add(Ops.Component, (sexp: S.Component, builder: OpcodeBuilder) => { +STATEMENTS.add(Ops.Component, (sexp: S.Component, builder) => { let [, tag, _attrs, args, block] = sexp; - let lookup = builder.lookup; - let meta = builder.meta; - let specifier = lookup.lookupComponent(tag, builder.meta); + let { lookup, meta } = builder; + let handle = lookup.lookupComponentSpec(tag, meta); - if (specifier) { - let capabilities = lookup.getCapabilities(specifier); + if (handle) { + let capabilities = lookup.getCapabilities(handle); let attrs: WireFormat.Statement[] = [ [Ops.ClientSideStatement, ClientSide.Ops.SetComponentAttrs, true], @@ -179,12 +178,12 @@ STATEMENTS.add(Ops.Component, (sexp: S.Component, builder: OpcodeBuilder) => { let child = builder.template(block); if (capabilities.dynamicLayout === false) { - let layout = lookup.getLayout(tag, meta)!; + let layout = lookup.getLayout(handle)!; - builder.pushComponentManager(specifier); + builder.pushComponentSpec(handle); builder.invokeStaticComponent(capabilities, layout, attrsBlock, null, args, false, child && child); } else { - builder.pushComponentManager(specifier); + builder.pushComponentSpec(handle); builder.invokeComponent(attrsBlock, null, args, false, child && child); } } else { @@ -192,7 +191,7 @@ STATEMENTS.add(Ops.Component, (sexp: S.Component, builder: OpcodeBuilder) => { } }); -STATEMENTS.add(Ops.Partial, (sexp: S.Partial, builder: OpcodeBuilder) => { +STATEMENTS.add(Ops.Partial, (sexp: S.Partial, builder) => { let [, name, evalInfo] = sexp; let { meta } = builder; @@ -225,13 +224,13 @@ STATEMENTS.add(Ops.Partial, (sexp: S.Partial, builder: OpcodeBuilder) => { builder.stopLabels(); }); -STATEMENTS.add(Ops.Yield, (sexp: WireFormat.Statements.Yield, builder: OpcodeBuilder) => { +STATEMENTS.add(Ops.Yield, (sexp: WireFormat.Statements.Yield, builder) => { let [, to, params] = sexp; builder.yield(to, params); }); -STATEMENTS.add(Ops.AttrSplat, (sexp: WireFormat.Statements.AttrSplat, builder: OpcodeBuilder) => { +STATEMENTS.add(Ops.AttrSplat, (sexp: WireFormat.Statements.AttrSplat, builder) => { let [, to] = sexp; builder.yield(to, []); @@ -239,13 +238,13 @@ STATEMENTS.add(Ops.AttrSplat, (sexp: WireFormat.Statements.AttrSplat, builder: O builder.setComponentAttrs(false); }); -STATEMENTS.add(Ops.Debugger, (sexp: WireFormat.Statements.Debugger, builder: OpcodeBuilder) => { +STATEMENTS.add(Ops.Debugger, (sexp: WireFormat.Statements.Debugger, builder) => { let [, evalInfo] = sexp; builder.debugger(builder.evalSymbols()!, evalInfo); }); -STATEMENTS.add(Ops.ClientSideStatement, (sexp: WireFormat.Statements.ClientSide, builder: OpcodeBuilder) => { +STATEMENTS.add(Ops.ClientSideStatement, (sexp: WireFormat.Statements.ClientSide, builder) => { CLIENT_SIDE.compile(sexp as ClientSide.ClientSideStatement, builder); }); @@ -254,7 +253,7 @@ const EXPRESSIONS = new Compilers(); import E = WireFormat.Expressions; import C = WireFormat.Core; -export function expr(expression: WireFormat.Expression, builder: OpcodeBuilder): void { +export function expr(expression: WireFormat.Expression, builder: OpcodeBuilder): void { if (Array.isArray(expression)) { EXPRESSIONS.compile(expression, builder); } else { @@ -262,7 +261,7 @@ export function expr(expression: WireFormat.Expression, builder: OpcodeBuilder): } } -EXPRESSIONS.add(Ops.Unknown, (sexp: E.Unknown, builder: OpcodeBuilder) => { +EXPRESSIONS.add(Ops.Unknown, (sexp: E.Unknown, builder) => { let { lookup, asPartial, meta } = builder; let name = sexp[1]; @@ -279,15 +278,15 @@ EXPRESSIONS.add(Ops.Unknown, (sexp: E.Unknown, builder: OpcodeBuilder) => { } }); -EXPRESSIONS.add(Ops.Concat, ((sexp: E.Concat, builder: OpcodeBuilder) => { +EXPRESSIONS.add(Ops.Concat, (sexp: E.Concat, builder) => { let parts = sexp[1]; for (let i = 0; i < parts.length; i++) { expr(parts[i], builder); } builder.concat(parts.length); -}) as any); +}); -EXPRESSIONS.add(Ops.Helper, (sexp: E.Helper, builder: OpcodeBuilder) => { +EXPRESSIONS.add(Ops.Helper, (sexp: E.Helper, builder) => { let { lookup, meta } = builder; let [, name, params, hash] = sexp; @@ -310,7 +309,7 @@ EXPRESSIONS.add(Ops.Helper, (sexp: E.Helper, builder: OpcodeBuilder) => { } }); -EXPRESSIONS.add(Ops.Get, (sexp: E.Get, builder: OpcodeBuilder) => { +EXPRESSIONS.add(Ops.Get, (sexp: E.Get, builder) => { let [, head, path] = sexp; builder.getVariable(head); for (let i = 0; i < path.length; i++) { @@ -318,7 +317,7 @@ EXPRESSIONS.add(Ops.Get, (sexp: E.Get, builder: OpcodeBuilder) => { } }); -EXPRESSIONS.add(Ops.MaybeLocal, (sexp: E.MaybeLocal, builder: OpcodeBuilder) => { +EXPRESSIONS.add(Ops.MaybeLocal, (sexp: E.MaybeLocal, builder) => { let [, path] = sexp; if (builder.asPartial) { @@ -339,11 +338,11 @@ EXPRESSIONS.add(Ops.Undefined, (_sexp, builder) => { return builder.pushPrimitiveReference(undefined); }); -EXPRESSIONS.add(Ops.HasBlock, (sexp: E.HasBlock, builder: OpcodeBuilder) => { +EXPRESSIONS.add(Ops.HasBlock, (sexp: E.HasBlock, builder) => { builder.hasBlock(sexp[1]); }); -EXPRESSIONS.add(Ops.HasBlockParams, (sexp: E.HasBlockParams, builder: OpcodeBuilder) => { +EXPRESSIONS.add(Ops.HasBlockParams, (sexp: E.HasBlockParams, builder) => { builder.hasBlockParams(sexp[1]); }); @@ -358,24 +357,24 @@ export class Macros { } } -export type BlockMacro = (params: C.Params, hash: C.Hash, template: Option, inverse: Option, builder: OpcodeBuilder) => void; -export type MissingBlockMacro = (name: string, params: C.Params, hash: C.Hash, template: Option, inverse: Option, builder: OpcodeBuilder) => void; +export type BlockMacro = (params: C.Params, hash: C.Hash, template: Option, inverse: Option, builder: OpcodeBuilder) => void; +export type MissingBlockMacro = (name: string, params: C.Params, hash: C.Hash, template: Option, inverse: Option, builder: OpcodeBuilder) => void; export class Blocks { private names = dict(); - private funcs: BlockMacro[] = []; - private missing: MissingBlockMacro; + private funcs: BlockMacro[] = []; + private missing: MissingBlockMacro; - add(name: string, func: BlockMacro) { + add(name: string, func: BlockMacro) { this.funcs.push(func); this.names[name] = this.funcs.length - 1; } - addMissing(func: MissingBlockMacro) { + addMissing(func: MissingBlockMacro) { this.missing = func; } - compile(name: string, params: C.Params, hash: C.Hash, template: Option, inverse: Option, builder: OpcodeBuilder): void { + compile(name: string, params: C.Params, hash: C.Hash, template: Option, inverse: Option, builder: OpcodeBuilder): void { let index = this.names[name]; if (index === undefined) { @@ -393,23 +392,23 @@ export class Blocks { export const BLOCKS = new Blocks(); export type AppendSyntax = S.Append; -export type AppendMacro = (name: string, params: Option, hash: Option, builder: OpcodeBuilder) => ['expr', WireFormat.Expression] | true | false; +export type AppendMacro = (name: string, params: Option, hash: Option, builder: OpcodeBuilder) => ['expr', WireFormat.Expression] | true | false; export class Inlines { private names = dict(); - private funcs: AppendMacro[] = []; - private missing: AppendMacro; + private funcs: AppendMacro[] = []; + private missing: AppendMacro; - add(name: string, func: AppendMacro) { + add(name: string, func: AppendMacro) { this.funcs.push(func); this.names[name] = this.funcs.length - 1; } - addMissing(func: AppendMacro) { + addMissing(func: AppendMacro) { this.missing = func; } - compile(sexp: AppendSyntax, builder: OpcodeBuilder): ['expr', WireFormat.Expression] | true { + compile(sexp: AppendSyntax, builder: OpcodeBuilder): ['expr', WireFormat.Expression] | true { let value = sexp[1]; // TODO: Fix this so that expression macros can return @@ -782,20 +781,20 @@ export function populateBuiltins(blocks: Blocks = new Blocks(), inlines: Inlines return { blocks, inlines }; } -export function compileStatement(statement: WireFormat.Statement, builder: OpcodeBuilder) { +export function compileStatement(statement: WireFormat.Statement, builder: OpcodeBuilder) { STATEMENTS.compile(statement, builder); } -export interface TemplateOptions { +export interface TemplateOptions { // already in compilation options program: CompileTimeProgram; macros: Macros; - Builder: OpcodeBuilderConstructor; + Builder: OpcodeBuilderConstructor; // a subset of the resolver w/ a couple of small tweaks - lookup: CompileTimeLookup; + lookup: CompileTimeLookup; } -export interface CompileOptions extends TemplateOptions { +export interface CompileOptions extends TemplateOptions { asPartial: boolean; } diff --git a/packages/@glimmer/opcode-compiler/lib/wrapped-component.ts b/packages/@glimmer/opcode-compiler/lib/wrapped-component.ts index e8d1c54f6d..a47ee96f7a 100644 --- a/packages/@glimmer/opcode-compiler/lib/wrapped-component.ts +++ b/packages/@glimmer/opcode-compiler/lib/wrapped-component.ts @@ -6,15 +6,14 @@ import { debugSlice } from '@glimmer/opcode-compiler'; import { Register } from '@glimmer/vm'; -import { ProgramSymbolTable, Opaque, BlockSymbolTable } from '@glimmer/interfaces'; +import { ProgramSymbolTable, BlockSymbolTable } from '@glimmer/interfaces'; import { TemplateMeta } from '@glimmer/wire-format'; import { - Handle, + VMHandle, ComponentArgs, ComponentBuilder as IComponentBuilder, - ParsedLayout, - Specifier + ParsedLayout } from './interfaces'; import { CompileOptions } from './syntax'; @@ -23,11 +22,11 @@ import CompilableTemplate from './compilable-template'; import { DEBUG } from "@glimmer/local-debug-flags"; import { EMPTY_ARRAY } from "@glimmer/util"; -export class WrappedBuilder implements ICompilableTemplate { +export class WrappedBuilder implements ICompilableTemplate { public symbolTable: ProgramSymbolTable; private meta: TemplateMeta; - constructor(public options: CompileOptions, private layout: ParsedLayout, private capabilities: ComponentCapabilities) { + constructor(public options: CompileOptions, private layout: ParsedLayout, private capabilities: ComponentCapabilities) { let { block } = layout; this.meta = layout.meta; let meta = this.meta = { templateMeta: layout.meta, symbols: block.symbols, asPartial: false }; @@ -39,7 +38,7 @@ export class WrappedBuilder implements ICompilableTemplate { }; } - compile(): Handle { + compile(): VMHandle { //========DYNAMIC // PutValue(TagExpr) // Test @@ -72,7 +71,7 @@ export class WrappedBuilder implements ICompilableTemplate { let { program, lookup, macros, asPartial } = options; let { Builder } = options; - let b = new Builder(program, lookup, meta, macros, layout, asPartial, options.Builder) as OpcodeBuilder; + let b = new Builder(program, lookup, meta, macros, layout, asPartial, options.Builder); b.startLabels(); @@ -122,20 +121,20 @@ export class WrappedBuilder implements ICompilableTemplate { } } -function blockFor(layout: ParsedLayout, options: CompileOptions): CompilableTemplate { +function blockFor(layout: ParsedLayout, options: CompileOptions): CompilableTemplate { let { block, meta } = layout; - return new CompilableTemplate(block.statements, layout, options, { meta, parameters: EMPTY_ARRAY }); + return new CompilableTemplate(block.statements, layout, options, { meta, parameters: EMPTY_ARRAY }); } -export class ComponentBuilder implements IComponentBuilder { - constructor(private builder: OpcodeBuilder) {} +export class ComponentBuilder implements IComponentBuilder { + constructor(private builder: OpcodeBuilder) {} - static(definition: Opaque, args: ComponentArgs) { + static(definition: number, args: ComponentArgs) { let [params, hash, _default, inverse] = args; let { builder } = this; - builder.pushComponentManager(definition as Specifier); + builder.pushComponentSpec(definition); builder.invokeComponent(null, params, hash, false, _default, inverse); } } diff --git a/packages/@glimmer/program/lib/constants.ts b/packages/@glimmer/program/lib/constants.ts index 94bc43c7ca..584162a925 100644 --- a/packages/@glimmer/program/lib/constants.ts +++ b/packages/@glimmer/program/lib/constants.ts @@ -3,13 +3,13 @@ import { CompileTimeConstants } from "@glimmer/opcode-compiler"; const UNRESOLVED = {}; -export class WriteOnlyConstants implements CompileTimeConstants { +export class WriteOnlyConstants implements CompileTimeConstants { // `0` means NULL protected strings: string[] = []; protected arrays: number[][] = []; protected tables: SymbolTable[] = []; - protected handles: Handle[] = []; + protected handles: number[] = []; protected serializables: Opaque[] = []; protected resolved: Opaque[] = []; @@ -41,9 +41,9 @@ export class WriteOnlyConstants implements CompileTimeConstants { return index + 1; } - handle(specifier: Handle): number { + handle(handle: number): number { let index = this.handles.length; - this.handles.push(specifier); + this.handles.push(handle); this.resolved.push(UNRESOLVED); return index + 1; } @@ -55,8 +55,8 @@ export class WriteOnlyConstants implements CompileTimeConstants { } } -export class Constants extends WriteOnlyConstants { - constructor(public resolver: Resolver) { +export class Constants extends WriteOnlyConstants { + constructor(public resolver: Resolver) { super(); } @@ -86,7 +86,7 @@ export class Constants extends WriteOnlyConstants { return this.tables[value - 1] as T; } - resolveSpecifier(s: number): T { + resolveHandle(s: number): T { let index = s - 1; let resolved = this.resolved[index]; @@ -103,7 +103,7 @@ export class Constants extends WriteOnlyConstants { } } -export class LazyConstants extends Constants { +export class LazyConstants extends Constants { private others: Opaque[] = []; getOther(value: number): T { diff --git a/packages/@glimmer/program/lib/internal.ts b/packages/@glimmer/program/lib/internal.ts index a4434155ae..3847ea00d5 100644 --- a/packages/@glimmer/program/lib/internal.ts +++ b/packages/@glimmer/program/lib/internal.ts @@ -2,4 +2,4 @@ import { Unique, Resolver as IResolver } from '@glimmer/interfaces'; export type Specifier = Unique<'Specifier'>; export type Referer = Unique<'Referer'>; -export type Resolver = IResolver; +export type Resolver = IResolver; diff --git a/packages/@glimmer/program/lib/program.ts b/packages/@glimmer/program/lib/program.ts index f3bf035326..62977d17ef 100644 --- a/packages/@glimmer/program/lib/program.ts +++ b/packages/@glimmer/program/lib/program.ts @@ -3,7 +3,7 @@ import { Recast } from "@glimmer/interfaces"; import { DEBUG } from "@glimmer/local-debug-flags"; import { Constants, WriteOnlyConstants } from './constants'; import { Opcode } from './opcode'; -import { Handle, CompileTimeProgram } from "@glimmer/opcode-compiler"; +import { VMHandle, CompileTimeProgram } from "@glimmer/opcode-compiler"; enum TableSlotState { Allocated, @@ -38,24 +38,24 @@ export class Heap { this.heap[address] = value; } - reserve(): Handle { + reserve(): VMHandle { this.table.push(0, 0, 0); let handle = this.handle; this.handle += 3; - return handle as Recast; + return handle as Recast; } - malloc(): Handle { + malloc(): VMHandle { this.table.push(this.offset, 0, 0); let handle = this.handle; this.handle += 3; - return handle as Recast; + return handle as Recast; } - finishMalloc(handle: Handle): void { - let start = this.table[handle as Recast]; + finishMalloc(handle: VMHandle): void { + let start = this.table[handle as Recast]; let finish = this.offset; - this.table[(handle as Recast) + 1] = finish - start; + this.table[(handle as Recast) + 1] = finish - start; } size(): number { @@ -65,26 +65,26 @@ export class Heap { // It is illegal to close over this address, as compaction // may move it. However, it is legal to use this address // multiple times between compactions. - getaddr(handle: Handle): number { - return this.table[handle as Recast]; + getaddr(handle: VMHandle): number { + return this.table[handle as Recast]; } - gethandle(address: number): Handle { + gethandle(address: number): VMHandle { this.table.push(address, 0, TableSlotState.Pointer); let handle = this.handle; this.handle += 3; - return handle as Recast; + return handle as Recast; } - sizeof(handle: Handle): number { + sizeof(handle: VMHandle): number { if (DEBUG) { - return this.table[(handle as Recast) + 1]; + return this.table[(handle as Recast) + 1]; } return -1; } - free(handle: Handle): void { - this.table[(handle as Recast) + 2] = 1; + free(handle: VMHandle): void { + this.table[(handle as Recast) + 2] = 1; } compact(): void { @@ -119,13 +119,13 @@ export class Heap { } } -export class WriteOnlyProgram implements CompileTimeProgram { +export class WriteOnlyProgram implements CompileTimeProgram { [key: number]: never; private _opcode: Opcode; public heap: Heap; - constructor(public constants: WriteOnlyConstants) { + constructor(public constants: WriteOnlyConstants) { this.heap = new Heap(); this._opcode = new Opcode(this.heap); } @@ -136,6 +136,6 @@ export class WriteOnlyProgram implements CompileTimeProgram { } } -export class Program extends WriteOnlyProgram { - public constants: Constants; +export class Program extends WriteOnlyProgram { + public constants: Constants; } diff --git a/packages/@glimmer/runtime/lib/compiled/opcodes/component.ts b/packages/@glimmer/runtime/lib/compiled/opcodes/component.ts index b4b6e1c71b..0513fcc296 100644 --- a/packages/@glimmer/runtime/lib/compiled/opcodes/component.ts +++ b/packages/@glimmer/runtime/lib/compiled/opcodes/component.ts @@ -32,11 +32,11 @@ import { ComponentDefinition, ComponentManager, Component } from '../../internal import { dict, assert, unreachable } from "@glimmer/util"; import { Op, Register } from '@glimmer/vm'; import { TemplateMeta } from "@glimmer/wire-format"; -import { AbstractTemplate, ATTRS_BLOCK, Handle } from '@glimmer/opcode-compiler'; +import { AbstractTemplate, ATTRS_BLOCK, VMHandle } from '@glimmer/opcode-compiler'; const ARGS = new Arguments(); -function resolveComponent(resolver: Resolver, name: string, meta: Specifier): Option { +function resolveComponent(resolver: Resolver, name: string, meta: Specifier): Option { let specifier = resolver.lookupComponent(name, meta); assert(specifier, `Could not find a component named "${name}"`); return resolver.resolve(specifier!); @@ -46,14 +46,14 @@ export function curry(spec: PublicComponentSpec, args: Option implements VersionedPathReference> { +class CurryComponentReference implements VersionedPathReference> { public tag: Tag; private lastValue: Opaque; private lastDefinition: Option; constructor( private inner: VersionedReference, - private resolver: Resolver, + private resolver: Resolver, private meta: Specifier, private args: Option ) { @@ -129,10 +129,10 @@ APPEND_OPCODES.add(Op.CurryComponent, (vm, { op1: _meta }) => { stack.push(new CurryComponentReference(definition, resolver, meta, captured)); }); -APPEND_OPCODES.add(Op.PushComponentManager, (vm, { op1: specifier }) => { - let spec = vm.constants.resolveSpecifier(specifier); +APPEND_OPCODES.add(Op.PushComponentSpec, (vm, { op1: handle }) => { + let spec = vm.constants.resolveHandle(handle); - assert(!!spec, `Missing component for ${specifier}`); + assert(!!spec, `Missing component for ${handle} (TODO: env.specifierForHandle)`); let stack = vm.stack; @@ -310,7 +310,7 @@ export class ComponentElementOperations { this.attributes[name] = deferred; } - flush(vm: VM) { + flush(vm: VM) { for (let name in this.attributes) { let attr = this.attributes[name]; let { value: reference, namespace, trusting } = attr; @@ -370,12 +370,12 @@ APPEND_OPCODES.add(Op.GetComponentTagName, (vm, { op1: _state }) => { APPEND_OPCODES.add(Op.GetComponentLayout, (vm, { op1: _state }) => { let { manager, definition, component } = vm.fetchValue(_state); let { constants: { resolver }, stack } = vm; - let specifier: Opaque; + let specifier: number; if (hasStaticLayout(definition, manager)) { - specifier = manager.getLayout(definition, resolver) as Opaque; + specifier = manager.getLayout(definition, resolver); } else if (hasDynamicLayout(definition, manager)) { - specifier = manager.getLayout(component, resolver) as Opaque; + specifier = manager.getLayout(component, resolver); } else { throw unreachable(); } @@ -389,7 +389,7 @@ APPEND_OPCODES.add(Op.GetComponentLayout, (vm, { op1: _state }) => { APPEND_OPCODES.add(Op.InvokeComponentLayout, vm => { let { stack } = vm; - let handle = stack.pop(); + let handle = stack.pop(); let { symbols, hasEval } = stack.pop(); { @@ -421,7 +421,7 @@ APPEND_OPCODES.add(Op.InvokeComponentLayout, vm => { let bindBlock = (name: string) => { let symbol = symbols.indexOf(name); - let handle = stack.pop>(); + let handle = stack.pop>(); let table = stack.pop>(); let block: Option = table ? [handle!, table] : null; @@ -469,7 +469,7 @@ export class UpdateComponentOpcode extends UpdatingOpcode { super(); } - evaluate(_vm: UpdatingVM) { + evaluate(_vm: UpdatingVM) { let { component, manager, dynamicScope } = this; manager.update(component, dynamicScope); @@ -488,7 +488,7 @@ export class DidUpdateLayoutOpcode extends UpdatingOpcode { super(); } - evaluate(vm: UpdatingVM) { + evaluate(vm: UpdatingVM) { let { manager, component, bounds } = this; manager.didUpdateLayout(component, bounds); diff --git a/packages/@glimmer/runtime/lib/compiled/opcodes/dom.ts b/packages/@glimmer/runtime/lib/compiled/opcodes/dom.ts index dbc3a45aa0..47e559d820 100644 --- a/packages/@glimmer/runtime/lib/compiled/opcodes/dom.ts +++ b/packages/@glimmer/runtime/lib/compiled/opcodes/dom.ts @@ -45,7 +45,7 @@ APPEND_OPCODES.add(Op.PushRemoteElement, vm => { if (isConst(elementRef)) { element = elementRef.value(); } else { - let cache = new ReferenceCache(elementRef); + let cache = new ReferenceCache(elementRef); element = cache.peek(); vm.updateWith(new Assert(cache)); } @@ -53,7 +53,7 @@ APPEND_OPCODES.add(Op.PushRemoteElement, vm => { if (isConst(nextSiblingRef)) { nextSibling = nextSiblingRef.value(); } else { - let cache = new ReferenceCache(nextSiblingRef); + let cache = new ReferenceCache>(nextSiblingRef); nextSibling = cache.peek(); vm.updateWith(new Assert(cache)); } @@ -76,8 +76,8 @@ APPEND_OPCODES.add(Op.FlushElement, vm => { APPEND_OPCODES.add(Op.CloseElement, vm => vm.elements().closeElement()); -APPEND_OPCODES.add(Op.Modifier, (vm, { op1: specifier }) => { - let manager = vm.constants.resolveSpecifier(specifier); +APPEND_OPCODES.add(Op.Modifier, (vm, { op1: handle }) => { + let manager = vm.constants.resolveHandle(handle); let stack = vm.stack; let args = stack.pop(); let { constructing: element, updateOperations } = vm.elements(); diff --git a/packages/@glimmer/runtime/lib/compiled/opcodes/expressions.ts b/packages/@glimmer/runtime/lib/compiled/opcodes/expressions.ts index 7840cdeb45..95e076ba75 100644 --- a/packages/@glimmer/runtime/lib/compiled/opcodes/expressions.ts +++ b/packages/@glimmer/runtime/lib/compiled/opcodes/expressions.ts @@ -7,13 +7,13 @@ import { FALSE_REFERENCE, TRUE_REFERENCE } from '../../references'; import { PublicVM } from '../../vm'; import { Arguments } from '../../vm/arguments'; import { ConcatReference } from '../expressions/concat'; -import { Handle } from "@glimmer/opcode-compiler"; +import { VMHandle } from "@glimmer/opcode-compiler"; export type FunctionExpression = (vm: PublicVM) => VersionedPathReference; -APPEND_OPCODES.add(Op.Helper, (vm, { op1: specifier }) => { +APPEND_OPCODES.add(Op.Helper, (vm, { op1: handle }) => { let stack = vm.stack; - let helper = vm.constants.resolveSpecifier(specifier); + let helper = vm.constants.resolveHandle(handle); let args = stack.pop(); let value = helper(vm, args); @@ -33,7 +33,7 @@ APPEND_OPCODES.add(Op.SetVariable, (vm, { op1: symbol }) => { }); APPEND_OPCODES.add(Op.SetBlock, (vm, { op1: symbol }) => { - let handle = vm.stack.pop>(); + let handle = vm.stack.pop>(); let table = vm.stack.pop>(); let block: Option = table ? [handle!, table] : null; diff --git a/packages/@glimmer/runtime/lib/compiled/opcodes/vm.ts b/packages/@glimmer/runtime/lib/compiled/opcodes/vm.ts index 4bd147c603..f75dc458eb 100644 --- a/packages/@glimmer/runtime/lib/compiled/opcodes/vm.ts +++ b/packages/@glimmer/runtime/lib/compiled/opcodes/vm.ts @@ -17,7 +17,7 @@ import { CompilableTemplate } from '../../syntax/interfaces'; import { VM, UpdatingVM } from '../../vm'; import { Arguments } from '../../vm/arguments'; import { LazyConstants } from "@glimmer/program"; -import { Handle } from "@glimmer/opcode-compiler"; +import { VMHandle } from "@glimmer/opcode-compiler"; APPEND_OPCODES.add(Op.ChildScope, vm => vm.pushChildScope()); @@ -27,7 +27,7 @@ APPEND_OPCODES.add(Op.PushDynamicScope, vm => vm.pushDynamicScope()); APPEND_OPCODES.add(Op.PopDynamicScope, vm => vm.popDynamicScope()); -APPEND_OPCODES.add(Op.Constant, (vm: VM & { constants: LazyConstants }, { op1: other }) => { +APPEND_OPCODES.add(Op.Constant, (vm: VM & { constants: LazyConstants }, { op1: other }) => { vm.stack.push(vm.constants.getOther(other)); }); @@ -92,12 +92,12 @@ APPEND_OPCODES.add(Op.CompileBlock, vm => { stack.push(block ? block.compile() : null); }); -APPEND_OPCODES.add(Op.InvokeStatic, vm => vm.call(vm.stack.pop())); +APPEND_OPCODES.add(Op.InvokeStatic, vm => vm.call(vm.stack.pop())); APPEND_OPCODES.add(Op.InvokeYield, vm => { let { stack } = vm; - let handle = stack.pop>(); + let handle = stack.pop>(); let table = stack.pop>(); let args = stack.pop(); @@ -189,7 +189,7 @@ export class Assert extends UpdatingOpcode { this.cache = cache; } - evaluate(vm: UpdatingVM) { + evaluate(vm: UpdatingVM) { let { cache } = this; if (isModified(cache.revalidate())) { diff --git a/packages/@glimmer/runtime/lib/component/interfaces.ts b/packages/@glimmer/runtime/lib/component/interfaces.ts index 9a9635e31e..8fad958337 100644 --- a/packages/@glimmer/runtime/lib/component/interfaces.ts +++ b/packages/@glimmer/runtime/lib/component/interfaces.ts @@ -1,7 +1,7 @@ import { Simple, Dict, Opaque, Option, Resolver, Unique } from '@glimmer/interfaces'; import { Tag, VersionedPathReference } from '@glimmer/reference'; import { Destroyable } from '@glimmer/util'; -import { ComponentCapabilities, Handle } from '@glimmer/opcode-compiler'; +import { ComponentCapabilities, VMHandle } from '@glimmer/opcode-compiler'; import Bounds from '../bounds'; import { ElementOperations } from '../vm/element-builder'; import Environment, { DynamicScope } from '../environment'; @@ -88,8 +88,8 @@ export interface WithDynamicTagName extends ComponentManager; } -export interface WithStaticLayout> extends ComponentManager { - getLayout(definition: Definition, resolver: R): Specifier; +export interface WithStaticLayout> extends ComponentManager { + getLayout(definition: Definition, resolver: R): number; } export interface WithAttributeHook extends ComponentManager { @@ -108,25 +108,25 @@ export interface WithElementHook extends ComponentManager(definition: Definition, manager: ComponentManager): manager is WithStaticLayout, Resolver, Opaque>> { +export function hasStaticLayout(definition: Definition, manager: ComponentManager): manager is WithStaticLayout> { return manager.getCapabilities(definition).dynamicLayout === false; } -export interface WithDynamicLayout> extends ComponentManager { +export interface WithDynamicLayout> extends ComponentManager { // Return the compiled layout to use for this component. This is called // *after* the component instance has been created, because you might // want to return a different layout per-instance for optimization reasons // or to implement features like Ember's "late-bound" layouts. - getLayout(component: Component, resolver: R): Specifier; + getLayout(component: Component, resolver: R): number; } /** @internal */ -export function hasDynamicLayout(definition: ComponentDefinition, manager: ComponentManager): manager is WithDynamicLayout, Resolver, Opaque>> { +export function hasDynamicLayout(definition: ComponentDefinition, manager: ComponentManager): manager is WithDynamicLayout> { return manager.getCapabilities(definition).dynamicLayout === true; } export interface WithStaticDefinitions extends ComponentManager { - getComponentDefinition(definition: ComponentDefinition, handle: Handle): ComponentDefinition; + getComponentDefinition(definition: ComponentDefinition, handle: VMHandle): ComponentDefinition; } /** @internal */ diff --git a/packages/@glimmer/runtime/lib/environment.ts b/packages/@glimmer/runtime/lib/environment.ts index cdf5cb667a..61c33c304e 100644 --- a/packages/@glimmer/runtime/lib/environment.ts +++ b/packages/@glimmer/runtime/lib/environment.ts @@ -22,13 +22,13 @@ import { import { PublicVM } from './vm/append'; -import { Macros, OpcodeBuilderConstructor, Handle } from "@glimmer/opcode-compiler"; +import { Macros, OpcodeBuilderConstructor, VMHandle } from "@glimmer/opcode-compiler"; import { IArguments } from './vm/arguments'; import { Simple, Resolver, BlockSymbolTable } from "@glimmer/interfaces"; import { Component, ComponentManager } from "@glimmer/runtime/lib/internal-interfaces"; import { Program } from "@glimmer/program"; -export type ScopeBlock = [Handle, BlockSymbolTable]; +export type ScopeBlock = [VMHandle, BlockSymbolTable]; export type ScopeSlot = VersionedPathReference | Option; export interface DynamicScope { @@ -222,11 +222,11 @@ class Transaction { } } -export interface CompilationOptions> { +export interface CompilationOptions> { resolver: R; - program: Program; + program: Program; macros: Macros; - Builder: OpcodeBuilderConstructor; + Builder: OpcodeBuilderConstructor; } export abstract class Environment { diff --git a/packages/@glimmer/runtime/lib/environment/lookup.ts b/packages/@glimmer/runtime/lib/environment/lookup.ts index fad6ec0c55..a12f276c47 100644 --- a/packages/@glimmer/runtime/lib/environment/lookup.ts +++ b/packages/@glimmer/runtime/lib/environment/lookup.ts @@ -1,56 +1,52 @@ -import { CompileTimeLookup, ComponentCapabilities } from "@glimmer/opcode-compiler"; -import { Resolver, ProgramSymbolTable, Unique, Option } from "@glimmer/interfaces"; -import { TemplateMeta } from "@glimmer/wire-format"; +import { CompileTimeLookup, ComponentCapabilities, ICompilableTemplate } from "@glimmer/opcode-compiler"; +import { Resolver, ProgramSymbolTable, Option } from "@glimmer/interfaces"; import { WithStaticLayout, ComponentSpec } from '../component/interfaces'; import { assert } from "@glimmer/util"; -export type LookupHandle = Unique<'LookupHandle'>; - -export class Lookup implements CompileTimeLookup { - constructor(private resolver: Resolver) { +export class Lookup implements CompileTimeLookup { + constructor(private resolver: Resolver) { } - private getComponentSpec(name: string, meta: TemplateMeta): ComponentSpec { - let specifier = this.resolver.lookupComponent(name, meta); - let spec = this.resolver.resolve>(specifier!); + private getComponentSpec(handle: number): ComponentSpec { + let spec = this.resolver.resolve>(handle); assert(!!spec, `Couldn't find a template named ${name}`); return spec!; } - getCapabilities(handle: LookupHandle): ComponentCapabilities { + getCapabilities(handle: number): ComponentCapabilities { let spec = this.resolver.resolve>(handle); let { manager, definition } = spec!; return manager.getCapabilities(definition); } - getLayout(name: string, meta: TemplateMeta): Option<{ symbolTable: ProgramSymbolTable; handle: Unique<"Handle">; }> { - let { manager, definition } = this.getComponentSpec(name, meta); + getLayout(handle: number): Option> { + let { manager, definition } = this.getComponentSpec(handle); let capabilities = manager.getCapabilities(definition); if (capabilities.dynamicLayout === true) { return null; } - let layoutSpecifier = (manager as WithStaticLayout).getLayout(definition, this.resolver); - return this.resolver.resolve<{ symbolTable: ProgramSymbolTable, handle: Unique<'Handle'> }>(layoutSpecifier); + let layoutSpecifier = (manager as WithStaticLayout>).getLayout(definition, this.resolver); + return this.resolver.resolve>(layoutSpecifier); } - lookupHelper(name: string, meta: TemplateMeta): Option { - return this.resolver.lookupHelper(name, meta); + lookupHelper(name: string, referer: Specifier): Option { + return this.resolver.lookupHelper(name, referer); } - lookupModifier(name: string, meta: TemplateMeta): Option { - return this.resolver.lookupModifier(name, meta); + lookupModifier(name: string, referer: Specifier): Option { + return this.resolver.lookupModifier(name, referer); } - lookupComponent(name: string, meta: TemplateMeta): Option { - return this.resolver.lookupComponent(name, meta); + lookupComponentSpec(name: string, referer: Specifier): Option { + return this.resolver.lookupComponent(name, referer); } - lookupPartial(name: string, meta: TemplateMeta): Option { - return this.resolver.lookupPartial(name, meta); + lookupPartial(name: string, referer: Specifier): Option { + return this.resolver.lookupPartial(name, referer); } } diff --git a/packages/@glimmer/runtime/lib/opcode-builder.ts b/packages/@glimmer/runtime/lib/opcode-builder.ts index f1217e1355..2057d13800 100644 --- a/packages/@glimmer/runtime/lib/opcode-builder.ts +++ b/packages/@glimmer/runtime/lib/opcode-builder.ts @@ -9,13 +9,13 @@ import { import * as WireFormat from '@glimmer/wire-format'; import { PublicVM } from './vm/append'; -import { Resolver } from './internal-interfaces'; +import { Resolver } from "@glimmer/interfaces"; -export interface DynamicComponentDefinition { +export interface DynamicComponentDefinition { ( vm: PublicVM, args: IArguments, meta: WireFormat.TemplateMeta, - resolver: Resolver + resolver: Resolver ): VersionedPathReference>; } diff --git a/packages/@glimmer/runtime/lib/opcodes.ts b/packages/@glimmer/runtime/lib/opcodes.ts index ddc7ddb092..f31489a0d6 100644 --- a/packages/@glimmer/runtime/lib/opcodes.ts +++ b/packages/@glimmer/runtime/lib/opcodes.ts @@ -19,7 +19,7 @@ export type Operand1 = number; export type Operand2 = number; export type Operand3 = number; -export type EvaluateOpcode = (vm: VM, opcode: Opcode) => void; +export type EvaluateOpcode = (vm: VM, opcode: Opcode) => void; export class AppendOpcodes { private evaluateOpcode: EvaluateOpcode[] = fillNulls(Op.Size).slice(); @@ -28,7 +28,7 @@ export class AppendOpcodes { this.evaluateOpcode[name as number] = evaluate; } - evaluate(vm: VM, opcode: Opcode, type: number) { + evaluate(vm: VM, opcode: Opcode, type: number) { let func = this.evaluateOpcode[type]; if (DEBUG) { /* tslint:disable */ @@ -68,7 +68,7 @@ export abstract class UpdatingOpcode extends AbstractOpcode { next: Option = null; prev: Option = null; - abstract evaluate(vm: UpdatingVM): void; + abstract evaluate(vm: UpdatingVM): void; } export type UpdatingOpSeq = ListSlice; diff --git a/packages/@glimmer/runtime/lib/partial.ts b/packages/@glimmer/runtime/lib/partial.ts index e2376e8738..d599ed1cb5 100644 --- a/packages/@glimmer/runtime/lib/partial.ts +++ b/packages/@glimmer/runtime/lib/partial.ts @@ -1,16 +1,15 @@ -import { TemplateMeta } from '@glimmer/wire-format'; import { ProgramSymbolTable } from '@glimmer/interfaces'; -import { Handle } from '@glimmer/opcode-compiler'; +import { VMHandle } from '@glimmer/opcode-compiler'; import { Template } from './template'; -export class PartialDefinition { +export class PartialDefinition { constructor( public name: string, // for debugging - private template: Template + private template: Template ) { } - getPartial(): { symbolTable: ProgramSymbolTable, handle: Handle } { + getPartial(): { symbolTable: ProgramSymbolTable, handle: VMHandle } { let partial = this.template.asPartial(); let handle = partial.compile(); return { symbolTable: partial.symbolTable, handle }; diff --git a/packages/@glimmer/runtime/lib/syntax/interfaces.ts b/packages/@glimmer/runtime/lib/syntax/interfaces.ts index 8280d895ca..bbff81b2f7 100644 --- a/packages/@glimmer/runtime/lib/syntax/interfaces.ts +++ b/packages/@glimmer/runtime/lib/syntax/interfaces.ts @@ -4,11 +4,11 @@ import { SymbolTable, } from '@glimmer/interfaces'; -import { Handle } from '@glimmer/opcode-compiler'; +import { VMHandle } from '@glimmer/opcode-compiler'; export interface CompilableTemplate { symbolTable: S; - compile(): Handle; + compile(): VMHandle; } export type BlockSyntax = CompilableTemplate; diff --git a/packages/@glimmer/runtime/lib/template.ts b/packages/@glimmer/runtime/lib/template.ts index a61a43f142..840b3e8a4f 100644 --- a/packages/@glimmer/runtime/lib/template.ts +++ b/packages/@glimmer/runtime/lib/template.ts @@ -60,7 +60,7 @@ export interface Template { asPartial(): TopLevelSyntax; } -export interface TemplateFactory { +export interface TemplateFactory { /** * Template identifier, if precompiled will be the id of the * precompiled template. @@ -78,7 +78,7 @@ export interface TemplateFactory { * * @param {Environment} env glimmer Environment */ - create(env: TemplateOptions): Template; + create(env: TemplateOptions): Template; /** * Used to create an environment specific singleton instance * of the template. @@ -86,11 +86,11 @@ export interface TemplateFactory { * @param {Environment} env glimmer Environment * @param {Object} meta environment specific injections into meta */ - create(env: TemplateOptions, meta: U): Template; + create(env: TemplateOptions, meta: U): Template; } export class TemplateIterator { - constructor(private vm: VM) {} + constructor(private vm: VM) {} next(): IteratorResult { return this.vm.next(); } @@ -103,12 +103,12 @@ let clientId = 0; * that handles lazy parsing the template and to create per env singletons * of the template. */ -export default function templateFactory(serializedTemplate: SerializedTemplateWithLazyBlock): TemplateFactory; -export default function templateFactory(serializedTemplate: SerializedTemplateWithLazyBlock): TemplateFactory; -export default function templateFactory({ id: templateId, meta, block }: SerializedTemplateWithLazyBlock): TemplateFactory<{}, {}> { +export default function templateFactory(serializedTemplate: SerializedTemplateWithLazyBlock): TemplateFactory; +export default function templateFactory(serializedTemplate: SerializedTemplateWithLazyBlock): TemplateFactory; +export default function templateFactory({ id: templateId, meta, block }: SerializedTemplateWithLazyBlock): TemplateFactory<{}> { let parsedBlock: SerializedTemplateBlock; let id = templateId || `client-${clientId++}`; - let create = (options: TemplateOptions, envMeta?: {}) => { + let create = (options: TemplateOptions, envMeta?: {}) => { let newMeta = envMeta ? assign({}, envMeta, meta) : meta; if (!parsedBlock) { parsedBlock = JSON.parse(block); @@ -127,7 +127,7 @@ export class ScannableTemplate implements Template { public meta: TemplateMeta; private statements: Statement[]; - constructor(private options: TemplateOptions, private parsedLayout: ParsedLayout) { + constructor(private options: TemplateOptions, private parsedLayout: ParsedLayout) { let { block } = parsedLayout; this.symbols = block.symbols; this.hasEval = block.hasEval; @@ -143,7 +143,7 @@ export class ScannableTemplate implements Template { let layout = this.asLayout(); let handle = layout.compile(); - let vm = VM.initial(this.options.program as any as Program, env, self, args, dynamicScope, builder, layout.symbolTable, handle); + let vm = VM.initial(this.options.program as any as Program, env, self, args, dynamicScope, builder, layout.symbolTable, handle); return new TemplateIterator(vm); } @@ -158,7 +158,7 @@ export class ScannableTemplate implements Template { } } -export function compilable(layout: ParsedLayout, options: TemplateOptions, asPartial: boolean) { +export function compilable(layout: ParsedLayout, options: TemplateOptions, asPartial: boolean) { let { block, meta } = layout; let { hasEval, symbols } = block; let compileOptions = { ...options, asPartial }; diff --git a/packages/@glimmer/runtime/lib/vm/append.ts b/packages/@glimmer/runtime/lib/vm/append.ts index 38c31ee661..ae3f55297e 100644 --- a/packages/@glimmer/runtime/lib/vm/append.ts +++ b/packages/@glimmer/runtime/lib/vm/append.ts @@ -16,7 +16,7 @@ import { import { ProgramSymbolTable, Opcode } from "@glimmer/interfaces"; import { Constants, Heap, Program } from "@glimmer/program"; -import { Handle as VMHandle } from "@glimmer/opcode-compiler"; +import { VMHandle as VMHandle } from "@glimmer/opcode-compiler"; export interface PublicVM { env: Environment; @@ -95,13 +95,13 @@ export type IteratorResult = { value: T; }; -export default class VM implements PublicVM { +export default class VM implements PublicVM { private dynamicScopeStack = new Stack(); private scopeStack = new Stack(); public updatingOpcodeStack = new Stack>(); public cacheGroups = new Stack>(); public listBlockStack = new Stack(); - public constants: Constants; + public constants: Constants; public heap: Heap; public stack = EvaluationStack.empty(); @@ -196,8 +196,8 @@ export default class VM implements PublicVM { this.pc = this.ra; } - static initial( - program: Program, + static initial( + program: Program, env: Environment, self: PathReference, args: Option, @@ -223,7 +223,7 @@ export default class VM implements PublicVM { } constructor( - private program: Program, + private program: Program, public env: Environment, scope: Scope, dynamicScope: DynamicScope, @@ -410,7 +410,7 @@ export default class VM implements PublicVM { /// EXECUTION - execute(start: VMHandle, initialize?: (vm: VM) => void): RenderResult { + execute(start: VMHandle, initialize?: (vm: VM) => void): RenderResult { this.pc = this.heap.getaddr(start); if (initialize) initialize(this); diff --git a/packages/@glimmer/runtime/lib/vm/render-result.ts b/packages/@glimmer/runtime/lib/vm/render-result.ts index 32596e9371..67f33c98fd 100644 --- a/packages/@glimmer/runtime/lib/vm/render-result.ts +++ b/packages/@glimmer/runtime/lib/vm/render-result.ts @@ -9,7 +9,7 @@ import { Program } from "@glimmer/program"; export default class RenderResult implements DestroyableBounds, ExceptionHandler { constructor( private env: Environment, - private program: Program, + private program: Program, private updating: LinkedList, private bounds: DestroyableBounds ) {} diff --git a/packages/@glimmer/runtime/lib/vm/update.ts b/packages/@glimmer/runtime/lib/vm/update.ts index e4aa1e6e6f..811f957bd0 100644 --- a/packages/@glimmer/runtime/lib/vm/update.ts +++ b/packages/@glimmer/runtime/lib/vm/update.ts @@ -24,17 +24,17 @@ import { Simple } from '@glimmer/interfaces'; import VM, { CapturedStack, EvaluationStack } from './append'; import { Constants, Program } from "@glimmer/program"; -import { Handle } from "@glimmer/opcode-compiler"; +import { VMHandle } from "@glimmer/opcode-compiler"; -export default class UpdatingVM { +export default class UpdatingVM { public env: Environment; public dom: DOMChanges; public alwaysRevalidate: boolean; - public constants: Constants; + public constants: Constants; private frameStack: Stack = new Stack(); - constructor(env: Environment, program: Program, { alwaysRevalidate = false }) { + constructor(env: Environment, program: Program, { alwaysRevalidate = false }) { this.env = env; this.constants = program.constants; this.dom = env.getDOM(); @@ -84,7 +84,7 @@ export interface ExceptionHandler { export interface VMState { env: Environment; - program: Program; + program: Program; scope: Scope; dynamicScope: DynamicScope; stack: CapturedStack; @@ -98,7 +98,7 @@ export abstract class BlockOpcode extends UpdatingOpcode implements DestroyableB protected bounds: DestroyableBounds; - constructor(public start: Handle, protected state: VMState, bounds: DestroyableBounds, children: LinkedList) { + constructor(public start: VMHandle, protected state: VMState, bounds: DestroyableBounds, children: LinkedList) { super(); this.children = children; @@ -119,7 +119,7 @@ export abstract class BlockOpcode extends UpdatingOpcode implements DestroyableB return this.bounds.lastNode(); } - evaluate(vm: UpdatingVM) { + evaluate(vm: UpdatingVM) { vm.try(this.children, null); } @@ -141,7 +141,7 @@ export class TryOpcode extends BlockOpcode implements ExceptionHandler { protected bounds: UpdatableTracker; - constructor(start: Handle, state: VMState, bounds: UpdatableTracker, children: LinkedList) { + constructor(start: VMHandle, state: VMState, bounds: UpdatableTracker, children: LinkedList) { super(start, state, bounds, children); this.tag = this._tag = UpdatableTag.create(CONSTANT_TAG); } @@ -150,7 +150,7 @@ export class TryOpcode extends BlockOpcode implements ExceptionHandler { this._tag.inner.update(combineSlice(this.children)); } - evaluate(vm: UpdatingVM) { + evaluate(vm: UpdatingVM) { vm.try(this.children, this); } @@ -266,7 +266,7 @@ export class ListBlockOpcode extends BlockOpcode { private lastIterated: Revision = INITIAL; private _tag: TagWrapper; - constructor(start: Handle, state: VMState, bounds: Tracker, children: LinkedList, artifacts: IterationArtifacts) { + constructor(start: VMHandle, state: VMState, bounds: Tracker, children: LinkedList, artifacts: IterationArtifacts) { super(start, state, bounds, children); this.artifacts = artifacts; let _tag = this._tag = UpdatableTag.create(CONSTANT_TAG); @@ -281,7 +281,7 @@ export class ListBlockOpcode extends BlockOpcode { } } - evaluate(vm: UpdatingVM) { + evaluate(vm: UpdatingVM) { let { artifacts, lastIterated } = this; if (!artifacts.tag.validate(lastIterated)) { @@ -303,7 +303,7 @@ export class ListBlockOpcode extends BlockOpcode { super.evaluate(vm); } - vmForInsertion(nextSibling: Option): VM { + vmForInsertion(nextSibling: Option): VM { let { bounds, state } = this; let elementStack = NewElementBuilder.forInitialRender( @@ -318,7 +318,7 @@ export class ListBlockOpcode extends BlockOpcode { class UpdatingVMFrame { private current: Option; - constructor(private vm: UpdatingVM, private ops: UpdatingOpSeq, private exceptionHandler: Option) { + constructor(private vm: UpdatingVM, private ops: UpdatingOpSeq, private exceptionHandler: Option) { this.vm = vm; this.ops = ops; this.current = ops.head(); diff --git a/packages/@glimmer/runtime/test/ember-component-test.ts b/packages/@glimmer/runtime/test/ember-component-test.ts index 608e6a5c94..b81cbb1a81 100644 --- a/packages/@glimmer/runtime/test/ember-component-test.ts +++ b/packages/@glimmer/runtime/test/ember-component-test.ts @@ -31,7 +31,7 @@ export class EmberishRootView extends EmberObject { context?: Object ) { super(context); - this.template = env.compile(template); + this.template = env.compile(template, null); } appendTo(selector: string) { @@ -2033,7 +2033,7 @@ QUnit.test('it does not work on optimized appends', () => { env.registerEmberishCurlyComponent('foo-bar', FooBar, 'foo bar'); - let definition = env.componentHelper('foo-bar', {}); + let definition = env.componentHelper('foo-bar'); appendViewFor('{{foo}}', { foo: definition }); @@ -2057,7 +2057,7 @@ QUnit.test('it works on unoptimized appends (dot paths)', () => { env.registerEmberishCurlyComponent('foo-bar', FooBar, 'foo bar'); - let definition = env.componentHelper('foo-bar', {}); + let definition = env.componentHelper('foo-bar'); appendViewFor('{{foo.bar}}', { foo: { bar: definition } }); @@ -2089,7 +2089,7 @@ QUnit.test('it works on unoptimized appends (this paths)', () => { env.registerEmberishCurlyComponent('foo-bar', FooBar, 'foo bar'); - let definition = env.componentHelper('foo-bar', {}); + let definition = env.componentHelper('foo-bar'); appendViewFor('{{this.foo}}', { foo: definition }); @@ -2121,7 +2121,7 @@ QUnit.test('it works on unoptimized appends when initially not a component (dot env.registerEmberishCurlyComponent('foo-bar', FooBar, 'foo bar'); - let definition = env.componentHelper('foo-bar', {}); + let definition = env.componentHelper('foo-bar'); appendViewFor('{{foo.bar}}', { foo: { bar: 'lol' } }); @@ -2149,7 +2149,7 @@ QUnit.test('it works on unoptimized appends when initially not a component (this env.registerEmberishCurlyComponent('foo-bar', FooBar, 'foo bar'); - let definition = env.componentHelper('foo-bar', {}); + let definition = env.componentHelper('foo-bar'); appendViewFor('{{this.foo}}', { foo: 'lol' }); diff --git a/packages/@glimmer/test-helpers/lib/environment.ts b/packages/@glimmer/test-helpers/lib/environment.ts index 6d753d78ab..b81847d13c 100644 --- a/packages/@glimmer/test-helpers/lib/environment.ts +++ b/packages/@glimmer/test-helpers/lib/environment.ts @@ -1,12 +1,12 @@ import { - CompilableTemplate, ComponentCapabilities, Macros, ParsedLayout, WrappedBuilder, TemplateOptions, LazyOpcodeBuilder, - Handle + VMHandle, + ICompilableTemplate } from "@glimmer/opcode-compiler"; import { @@ -92,7 +92,7 @@ import { import * as WireFormat from '@glimmer/wire-format'; -import { Simple, Resolver, Unique, ProgramSymbolTable, Recast } from "@glimmer/interfaces"; +import { Simple, Resolver, Unique, ProgramSymbolTable } from "@glimmer/interfaces"; import { TemplateMeta, SerializedTemplateWithLazyBlock, SerializedTemplateBlock } from "@glimmer/wire-format"; import { precompile } from "@glimmer/compiler"; import { Program, LazyConstants } from "@glimmer/program"; @@ -248,7 +248,7 @@ export class EmberishCurlyComponent extends GlimmerObject { public static positionalParams: string[] | string; public dirtinessTag: TagWrapper = DirtyableTag.create(); - public layout: TestSpecifier; + public layout: { name: string, handle: number }; public name: string; public tagName: Option = null; public attributeBindings: Option = null; @@ -319,15 +319,15 @@ class BasicComponentManager extends GenericComponentManager implements WithStati return new klass(); } - getLayout({ name }: BasicComponentDefinition, resolver: TestResolver): TestSpecifier { - let compile = (source: string, options: TemplateOptions) => { + getLayout({ name }: BasicComponentDefinition, resolver: TestResolver): number { + let compile = (source: string, options: TemplateOptions) => { let layout = createTemplate(source); return new ScannableTemplate(options, layout).asLayout(); }; - let specifier = resolver.lookup('template-source', name, {})!; + let handle = resolver.lookup('template-source', name)!; - return resolver.compileTemplate(specifier, compile); + return resolver.compileTemplate(handle, name, compile); } getSelf(component: BasicComponent): PathReference { @@ -362,12 +362,12 @@ class BasicComponentManager extends GenericComponentManager implements WithStati const BASIC_COMPONENT_MANAGER = new BasicComponentManager(); class StaticTaglessComponentManager extends BasicComponentManager { - getLayout(definition: BasicComponentDefinition, resolver: TestResolver): TestSpecifier { + getLayout(definition: BasicComponentDefinition, resolver: TestResolver): number { let { name, capabilities } = definition; - let specifier = resolver.lookup('template-source', name, {})!; + let handle = resolver.lookup('template-source', name)!; - return resolver.compileTemplate(specifier, (source, options) => { + return resolver.compileTemplate(handle, name, (source, options) => { let template = createTemplate(source, {}); let compileOptions = { ...options, asPartial: false }; return new WrappedBuilder(compileOptions, template, capabilities); @@ -407,15 +407,15 @@ class EmberishGlimmerComponentManager extends GenericComponentManager implements return combine([tag, dirtinessTag]); } - getLayout({ name }: EmberishGlimmerComponentDefinition, resolver: TestResolver): TestSpecifier { - let compile = (source: string, options: TemplateOptions) => { + getLayout({ name }: EmberishGlimmerComponentDefinition, resolver: TestResolver): number { + let compile = (source: string, options: TemplateOptions) => { let layout = createTemplate(source); return new ScannableTemplate(options, layout).asLayout(); }; - let specifier = resolver.lookup('template-source', name, {})!; + let handle = resolver.lookup('template-source', name)!; - return resolver.compileTemplate(specifier, compile); + return resolver.compileTemplate(handle, name, compile); } getSelf({ component }: EmberishGlimmerStateBucket): PathReference { @@ -517,7 +517,7 @@ class EmberishCurlyComponentManager extends GenericComponentManager implements W component.args = args; if (definition.layout) { - component.layout = definition.layout; + component.layout = { name: component.name, handle: definition.layout }; } let dyn: Option = definition.ComponentClass ? definition.ComponentClass['fromDynamicScope'] : null; @@ -541,18 +541,18 @@ class EmberishCurlyComponentManager extends GenericComponentManager implements W return combine([tag, dirtinessTag]); } - getLayout({ layout }: EmberishCurlyComponent, resolver: TestResolver): TestSpecifier { + getLayout({ layout }: EmberishCurlyComponent, resolver: TestResolver): number { if (!layout) { throw new Error('BUG: missing dynamic layout'); } - let specifier = resolver.lookup('template-source', layout.name, {}); + let handle = resolver.lookup('template-source', layout.name); - if (!specifier) { + if (!handle) { throw new Error('BUG: missing dynamic layout'); } - return resolver.compileTemplate(specifier, (source, options) => { + return resolver.compileTemplate(handle, layout.name, (source, options) => { let template = createTemplate(source); return new WrappedBuilder({ ...options, asPartial: false }, template, CURLY_CAPABILITIES); }); @@ -763,7 +763,7 @@ export interface Lookup { modifier: ModifierManager; partial: PartialDefinition; component: ComponentSpec; - template: { compile(): Handle }; + template: { compile(): VMHandle }; 'template-source': string; } @@ -776,81 +776,94 @@ export interface TestSpecifier { } class TypedRegistry { - private inner = dict(); + private byName: { [key: string]: number } = dict(); + private byHandle: { [key: number]: T } = dict(); - has(name: string): boolean { - return name in this.inner; + hasName(name: string): boolean { + return name in this.byName; } - get(name: string): Option { - return this.inner[name]; + getHandle(name: string): Option { + return this.byName[name]; } - register(name: string, value: T): void { - this.inner[name] = value; + hasHandle(name: number): boolean { + return name in this.byHandle; + } + + getByHandle(handle: number): Option { + return this.byHandle[handle]; + } + + register(handle: number, name: string, value: T): void { + this.byHandle[handle] = value; + this.byName[name] = handle; } } -export type TestCompilationOptions = CompilationOptions; +export type TestCompilationOptions = CompilationOptions; -export type CompileTemplate = CompilableTemplate; +export class TestResolver implements Resolver { + private handleLookup: TypedRegistry[] = []; -export class TestResolver implements Resolver { private registry = { helper: new TypedRegistry(), modifier: new TypedRegistry(), partial: new TypedRegistry(), component: new TypedRegistry(), - template: new TypedRegistry<{ compile(): Handle }>(), + template: new TypedRegistry<{ compile(): VMHandle }>(), 'template-source': new TypedRegistry() }; - private options: TemplateOptions; + private options: TemplateOptions; - register(type: K, name: string, value: Lookup[K]): TestSpecifier { - (this.registry[type] as TypedRegistry).register(name, value); - return { type, name }; + register(type: K, name: string, value: Lookup[K]): number { + let registry = this.registry[type]; + let handle = this.handleLookup.length; + this.handleLookup.push(registry); + (this.registry[type] as TypedRegistry).register(handle, name, value); + return handle; } - lookup(type: LookupType, name: string, _meta: TemplateMeta): Option { - if (this.registry[type].has(name)) { - return { type, name }; + lookup(type: LookupType, name: string, _referer?: TestSpecifier): Option { + if (this.registry[type].hasName(name)) { + return this.registry[type].getHandle(name); } else { return null; } } - compileTemplate(sourceSpecifier: TestSpecifier, create: (source: string, options: TemplateOptions) => { compile(): Handle }): TestSpecifier { - let templateName = sourceSpecifier.name; - let specifier = this.lookup('template', templateName, {}); + compileTemplate(sourceHandle: number, templateName: string, create: (source: string, options: TemplateOptions) => ICompilableTemplate): number { + let handle = this.lookup('template', templateName); - if (specifier) { - return specifier; + if (handle) { + return handle; } - let source = this.resolve(sourceSpecifier); + let source = this.resolve(sourceHandle); return this.register('template', templateName, create(source, this.options)); } - lookupHelper(name: string, meta: TemplateMeta): Option { - return this.lookup('helper', name, meta); + lookupHelper(name: string, referer?: TestSpecifier): Option { + return this.lookup('helper', name, referer); } - lookupModifier(name: string, meta: TemplateMeta): Option { - return this.lookup('modifier', name, meta); + lookupModifier(name: string, referer?: TestSpecifier): Option { + return this.lookup('modifier', name, referer); } - lookupComponent(name: string, meta: TemplateMeta): Option { - return this.lookup('component', name, meta); + lookupComponent(name: string, referer?: TestSpecifier): Option { + return this.lookup('component', name, referer); } - lookupPartial(name: string, meta: TemplateMeta): Option { - return this.lookup('partial', name, meta); + lookupPartial(name: string, referer?: TestSpecifier): Option { + return this.lookup('partial', name, referer); } - resolve(specifier: TestSpecifier): T { - return this.registry[specifier.type].get(specifier.name) as Recast; + resolve(handle: number): T { + let registry = this.handleLookup[handle]; + return registry.getByHandle(handle) as T; } } @@ -875,7 +888,7 @@ class TestMacros extends Macros { let lookup = builder.lookup; - let specifier = lookup.lookupComponent(name, builder.meta); + let specifier = lookup.lookupComponentSpec(name, builder.meta); if (specifier) { builder.component.static(specifier, [params, hashToArgs(hash), template, inverse]); @@ -887,7 +900,7 @@ class TestMacros extends Macros { inlines.addMissing((name, params, hash, builder) => { let lookup = builder.lookup; - let specifier = lookup.lookupComponent(name, builder.meta); + let specifier = lookup.lookupComponentSpec(name, builder.meta); if (specifier) { builder.component.static(specifier, [params!, hashToArgs(hash), null, null]); @@ -903,10 +916,10 @@ export class TestEnvironment extends Environment { public resolver = new TestResolver(); private program = new Program(new LazyConstants(this.resolver)); private uselessAnchor: HTMLAnchorElement; - public compiledLayouts = dict(); - private lookup: LookupResolver; + public compiledLayouts = dict(); + private lookup: LookupResolver; - public compileOptions: TemplateOptions = { + public compileOptions: TemplateOptions = { lookup: new LookupResolver(this.resolver), program: this.program, macros: new TestMacros(), @@ -957,7 +970,7 @@ export class TestEnvironment extends Environment { } registerPartial(name: string, source: string): PartialDefinition { - let definition = new PartialDefinition(name, this.compile(source)); + let definition = new PartialDefinition(name, this.compile(source, null)); this.resolver.register('partial', name, definition); return definition; } @@ -967,8 +980,8 @@ export class TestEnvironment extends Environment { return definition; } - registerTemplate(name: string, source: string): TestSpecifier { - return this.resolver.register("template-source", name, source); + registerTemplate(name: string, source: string): { name: string, handle: number } { + return { name, handle: this.resolver.register("template-source", name, source) }; } registerBasicComponent(name: string, Component: BasicComponentFactory, layoutSource: string): void { @@ -978,25 +991,25 @@ export class TestEnvironment extends Environment { let layout = this.registerTemplate(name, layoutSource); - let definition = new BasicComponentDefinition(name, BASIC_COMPONENT_MANAGER, Component, layout); + let definition = new BasicComponentDefinition(name, BASIC_COMPONENT_MANAGER, Component, layout.handle); this.registerComponent(name, definition); } registerStaticTaglessComponent(name: string, Component: BasicComponentFactory, layoutSource: string): void { let layout = this.registerTemplate(name, layoutSource); - let definition = new StaticTaglessComponentDefinition(name, STATIC_TAGLESS_COMPONENT_MANAGER, Component, layout); + let definition = new StaticTaglessComponentDefinition(name, STATIC_TAGLESS_COMPONENT_MANAGER, Component, layout.handle); this.registerComponent(name, definition); } registerEmberishCurlyComponent(name: string, Component: Option, layoutSource: Option): void { - let layout: Option = null; + let layout: Option<{ name: string, handle: number }> = null; if (layoutSource !== null) { layout = this.registerTemplate(name, layoutSource); } - let definition = new EmberishCurlyComponentDefinition(name, EMBERISH_CURLY_COMPONENT_MANAGER, Component || EmberishCurlyComponent, layout); + let definition = new EmberishCurlyComponentDefinition(name, EMBERISH_CURLY_COMPONENT_MANAGER, Component || EmberishCurlyComponent, layout && layout.handle); this.registerComponent(name, definition); } @@ -1007,7 +1020,7 @@ export class TestEnvironment extends Environment { let layout = this.registerTemplate(name, layoutSource); - let definition = new EmberishGlimmerComponentDefinition(name, EMBERISH_GLIMMER_COMPONENT_MANAGER, Component || EmberishGlimmerComponent, layout); + let definition = new EmberishGlimmerComponentDefinition(name, EMBERISH_GLIMMER_COMPONENT_MANAGER, Component || EmberishGlimmerComponent, layout.handle); this.registerComponent(name, definition); } @@ -1019,34 +1032,34 @@ export class TestEnvironment extends Environment { return new EmberishConditionalReference(reference); } - resolveHelper(helperName: string, meta: TemplateMeta): Option { - let specifier = this.resolver.lookupHelper(helperName, meta); - return specifier && this.resolver.resolve(specifier); + resolveHelper(helperName: string): Option { + let handle = this.resolver.lookupHelper(helperName); + return typeof handle === 'number' ? this.resolver.resolve(handle) : null; } - resolvePartial(partialName: string, meta: TemplateMeta): Option { - let specifier = this.resolver.lookupPartial(partialName, meta); - return specifier && this.resolver.resolve(specifier); + resolvePartial(partialName: string): Option { + let handle = this.resolver.lookupPartial(partialName); + return typeof handle === 'number' ? this.resolver.resolve(handle) : null; } - componentHelper(name: string, meta: TemplateMeta): Option { - let specifier = this.resolver.lookupComponent(name, meta); + componentHelper(name: string): Option { + let handle = this.resolver.lookupComponent(name); - if (!specifier) return null; + if (handle === null) return null; - let spec = this.resolver.resolve(specifier); + let spec = this.resolver.resolve(handle); return curry(spec); } - resolveModifier(modifierName: string, meta: TemplateMeta): Option { - let specifier = this.resolver.lookupModifier(modifierName, meta); - return specifier && this.resolver.resolve(specifier); + resolveModifier(modifierName: string): Option { + let handle = this.resolver.lookupModifier(modifierName); + return handle === null ? null : this.resolver.resolve(handle); } - compile(template: string, meta: TemplateMeta = {}): Template { - let wrapper = JSON.parse(precompile(template, { meta })); + compile(template: string, meta: TemplateMeta): Template { + let wrapper = JSON.parse(precompile(template)); let factory = templateFactory(wrapper); - return factory.create(this.compileOptions); + return factory.create(this.compileOptions, meta); } iterableFor(ref: Reference, keyPath: string): OpaqueIterable { @@ -1110,7 +1123,7 @@ export interface BasicComponentFactory { export abstract class GenericComponentDefinition { abstract capabilities: ComponentCapabilities; - constructor(public name: string, public manager: ComponentManager>, public ComponentClass: any, public layout: Option) { + constructor(public name: string, public manager: ComponentManager>, public ComponentClass: any, public layout: Option) { } toJSON() { diff --git a/packages/@glimmer/vm/lib/opcodes.ts b/packages/@glimmer/vm/lib/opcodes.ts index ab96ba34bb..c7eebe3f61 100644 --- a/packages/@glimmer/vm/lib/opcodes.ts +++ b/packages/@glimmer/vm/lib/opcodes.ts @@ -636,12 +636,12 @@ export const enum Op { * Operation: Push an appropriate component manager onto the stack. * * Format: - * (PushComponentManager #ComponentDefinition) + * (PushComponentManager #ComponentSpec) * Operand Stack: * ... → * ..., { ComponentDefinition, ComponentManager } */ - PushComponentManager, + PushComponentSpec, /** * Operation: From 61c65a16f3fcad6df008fed2cb96cc81bb080152 Mon Sep 17 00:00:00 2001 From: Yehuda Katz Date: Mon, 14 Aug 2017 15:55:38 -0700 Subject: [PATCH 11/42] [WIP] Updates to the bundle compiler --- packages/@glimmer/bundle-compiler/index.ts | 2 +- .../@glimmer/bundle-compiler/lib/templates.ts | 71 +- .../@glimmer/bundle-compiler/package.json | 7 +- .../test/bundle-compiler-test.ts | 1 + .../test/initial-render-test.ts | 1153 +++++++++++++++++ .../@glimmer/bundle-compiler/test/support.ts | 34 + 6 files changed, 1258 insertions(+), 10 deletions(-) create mode 100644 packages/@glimmer/bundle-compiler/test/bundle-compiler-test.ts create mode 100644 packages/@glimmer/bundle-compiler/test/initial-render-test.ts create mode 100644 packages/@glimmer/bundle-compiler/test/support.ts diff --git a/packages/@glimmer/bundle-compiler/index.ts b/packages/@glimmer/bundle-compiler/index.ts index 8094f0d65f..5b62f3ee2e 100644 --- a/packages/@glimmer/bundle-compiler/index.ts +++ b/packages/@glimmer/bundle-compiler/index.ts @@ -1 +1 @@ -export {} from './lib/templates'; +export * from './lib/templates'; diff --git a/packages/@glimmer/bundle-compiler/lib/templates.ts b/packages/@glimmer/bundle-compiler/lib/templates.ts index f597ced679..22d66b0ef4 100644 --- a/packages/@glimmer/bundle-compiler/lib/templates.ts +++ b/packages/@glimmer/bundle-compiler/lib/templates.ts @@ -3,7 +3,8 @@ import { ASTPluginBuilder, preprocess } from "@glimmer/syntax"; import { TemplateCompiler } from "@glimmer/compiler"; import { CompilableTemplate, Macros, OpcodeBuilderConstructor, ComponentCapabilities, CompileTimeLookup, CompileOptions, VMHandle, ICompilableTemplate } from "@glimmer/opcode-compiler"; import { WriteOnlyProgram, WriteOnlyConstants } from "@glimmer/program"; -import { Option, ProgramSymbolTable, Recast } from "@glimmer/interfaces"; +import { Option, ProgramSymbolTable, Recast, Dict } from "@glimmer/interfaces"; +import { dict } from "@glimmer/util"; export interface BundleCompileOptions { plugins: ASTPluginBuilder[]; @@ -12,15 +13,68 @@ export interface BundleCompileOptions { export type ModuleName = string; export type NamedExport = string; -export interface Specifier { - import: NamedExport; - from: ModuleName; +export interface Specifier extends Readonly { + module: ModuleName; + name: NamedExport; +} + +class SpecifierValue implements Specifier { + private static values: Dict> = dict(); + + static for(name: NamedExport, module: ModuleName): Specifier { + let specifiers = this.values[name]; + + if (!specifiers) { + specifiers = this.values[module] = dict(); + } + + let specifier = specifiers[name]; + + if (!specifier) { + specifier = { name, module }; + } + + return Object.freeze(specifier); + } + + private constructor(public name: NamedExport, public module: ModuleName) {} +} + +export function specifier(name: NamedExport, module: ModuleName): Specifier { + return SpecifierValue.for(name, module); } export class SpecifierMap { public helpers = new Map(); public modifiers = new Map(); public components = new Map(); + + private helpersBySpecifier = new Map(); + private modifiersBySpecifier = new Map(); + private componentsBySpecifier = new Map(); + + private handleFor(handles: Map, specifier: Specifier): number { + let handle = handles.get(specifier); + + if (!handle) { + handle = handles.size; + handles.set(specifier, handle); + } + + return handle; + } + + handleForHelper(helper: Specifier): number { + return this.handleFor(this.helpersBySpecifier, helper); + } + + handleForModifier(modifier: Specifier): number { + return this.handleFor(this.modifiersBySpecifier, modifier); + } + + handleForComponent(component: Specifier): number { + return this.handleFor(this.componentsBySpecifier, component); + } } export class BundleCompiler { @@ -107,6 +161,7 @@ class BundlingLookup implements CompileTimeLookup { lookupComponentSpec(name: string, referer: Specifier): Option { if (this.delegate.hasComponentInScope(name, referer)) { let specifier = this.delegate.resolveComponentSpecifier(name, referer); + return this.specifiers.handleForComponent(specifier); } else { return null; } @@ -126,7 +181,8 @@ class BundlingLookup implements CompileTimeLookup { lookupHelper(name: string, referer: Specifier): Option { if (this.delegate.hasHelperInScope(name, referer)) { - return this.delegate.resolveHelperSpecifier(name, referer); + let specifier = this.delegate.resolveHelperSpecifier(name, referer); + return this.specifiers.handleForHelper(specifier); } else { return null; } @@ -134,13 +190,14 @@ class BundlingLookup implements CompileTimeLookup { lookupModifier(name: string, referer: Specifier): Option { if (this.delegate.hasModifierInScope(name, referer)) { - return this.delegate.resolveModifierSpecifier(name, referer); + let specifier = this.delegate.resolveHelperSpecifier(name, referer); + return this.specifiers.handleForHelper(specifier); } else { return null; } } - lookupPartial(name: string, referer: Specifier): Option { + lookupPartial(_name: string, _referer: Specifier): Option { throw new Error("Method not implemented."); } } diff --git a/packages/@glimmer/bundle-compiler/package.json b/packages/@glimmer/bundle-compiler/package.json index a9be747569..f76924f49c 100644 --- a/packages/@glimmer/bundle-compiler/package.json +++ b/packages/@glimmer/bundle-compiler/package.json @@ -5,12 +5,15 @@ "dependencies": { "@glimmer/syntax": "^0.26.2", "@glimmer/util": "^0.26.2", - "@glimmer/runtime": "^0.26.2", "@glimmer/wire-format": "^0.26.2", "@glimmer/interfaces": "^0.26.2", + "@glimmer/program": "^0.26.2", + "@glimmer/compiler": "^0.26.2", + "@glimmer/opcode-compiler": "^0.26.2", "simple-html-tokenizer": "^0.3.0" }, "devDependencies": { - "typescript": "^2.2.0" + "typescript": "^2.2.0", + "@glimmer/runtime": "^0.26.2" } } \ No newline at end of file diff --git a/packages/@glimmer/bundle-compiler/test/bundle-compiler-test.ts b/packages/@glimmer/bundle-compiler/test/bundle-compiler-test.ts new file mode 100644 index 0000000000..8cec2e9ced --- /dev/null +++ b/packages/@glimmer/bundle-compiler/test/bundle-compiler-test.ts @@ -0,0 +1 @@ +export {}; \ No newline at end of file diff --git a/packages/@glimmer/bundle-compiler/test/initial-render-test.ts b/packages/@glimmer/bundle-compiler/test/initial-render-test.ts new file mode 100644 index 0000000000..5765cc74cb --- /dev/null +++ b/packages/@glimmer/bundle-compiler/test/initial-render-test.ts @@ -0,0 +1,1153 @@ +import { Opaque, Dict } from "@glimmer/interfaces"; +import { SVG_NAMESPACE, RenderResult } from "@glimmer/runtime"; +import { + RenderTests, + module, + test, + strip, + assertNodeTagName, + EMPTY, + OPEN, + CLOSE, + renderTemplate, + TestEnvironment, + equalTokens, + Content, + TestDynamicScope, + content +} from "@glimmer/test-helpers"; +import * as SimpleDOM from "simple-dom"; +import { NodeDOMTreeConstruction } from "@glimmer/node"; +import { expect } from "@glimmer/util"; +import { UpdatableReference } from "@glimmer/object-reference"; + +const XHTML_NAMESPACE = 'http://www.w3.org/1999/xhtml'; + +const IE9_SELECT_QUIRK = (() => { + let select = document.createElement('select'); + let option1 = document.createElement('option'); + let option2 = document.createElement('option'); + option1.setAttribute('selected', ''); + select.appendChild(option1); + select.appendChild(option2); + document.body.appendChild(select); + let idx = select.selectedIndex; + option1.selected = false; + idx = select.selectedIndex; + let isSelectQuirk = idx === -1; + document.body.removeChild(select); + return isSelectQuirk; +})(); + +class RenderingTest extends RenderTests { + @test "HTML text content"() { + this.render("content"); + this.assertHTML("content"); + this.assertStableRerender(); + } + + @test "HTML tags"() { + this.render("

hello!

content
"); + this.assertHTML("

hello!

content
"); + this.assertStableRerender(); + } + + @test "HTML attributes"() { + this.render("
content
"); + this.assertHTML("
content
"); + this.assertStableRerender(); + } + + @test "HTML data attributes"() { + this.render("
content
"); + this.assertHTML("
content
"); + this.assertStableRerender(); + } + + @test "HTML checked attributes"() { + this.render(""); + this.assertHTML(""); + this.assertStableRerender(); + } + + @test "HTML selected options"() { + this.render(strip` + + `); + this.assertHTML(strip` + + `); + this.assertStableRerender(); + } + + @test "HTML multi-select options"() { + this.render(strip` + + `); + this.assertHTML(strip` + + `); + this.assertStableRerender(); + } + + @test "Void Elements"() { + let voidElements = "area base br col command embed hr img input keygen link meta param source track wbr"; + voidElements.split(" ").forEach((tagName) => this.shouldBeVoid(tagName)); + } + + @test "Nested HTML"() { + this.render("

hi!

 More content"); + this.assertHTML("

hi!

 More content"); + this.assertStableRerender(); + } + + @test "Custom Elements"() { + this.render(""); + this.assertHTML(""); + this.assertStableRerender(); + } + + @test "Nested Custom Elements"() { + this.render("Stuff
Here
"); + this.assertHTML("Stuff
Here
"); + this.assertStableRerender(); + } + + @test "Moar nested Custom Elements"() { + this.render("Here"); + this.assertHTML("Here"); + this.assertStableRerender(); + } + + @test "Custom Elements with dynamic attributes"() { + this.render("", { someDynamicBits: 'things' }); + this.assertHTML(""); + this.assertStableRerender(); + } + + @test "Custom Elements with dynamic content"() { + this.render("{{derp}}", { derp: 'stuff' }); + this.assertHTML("stuff"); + this.assertStableRerender(); + } + + @test "Dynamic content within single custom element"() { + this.render("{{#if derp}}Content Here{{/if}}", { derp: 'stuff' }); + this.assertHTML("Content Here"); + this.assertStableRerender(); + + this.rerender({ derp: false }); + this.assertHTML(""); + this.assertStableRerender(); + + this.rerender({ derp: true }); + this.assertHTML("Content Here"); + this.assertStableRerender(); + + this.rerender({ derp: 'stuff' }); + this.assertHTML("Content Here"); + this.assertStableRerender(); + } + + @test "Supports quotes"() { + this.render("
\"This is a title,\" we\'re on a boat
"); + this.assertHTML("
\"This is a title,\" we\'re on a boat
"); + this.assertStableRerender(); + } + + @test "Supports backslashes"() { + this.render("
This is a backslash: \\
"); + this.assertHTML("
This is a backslash: \\
"); + this.assertStableRerender(); + } + + @test "Supports new lines"() { + this.render("
common\n\nbro
"); + this.assertHTML("
common\n\nbro
"); + this.assertStableRerender(); + } + + @test "HTML tag with empty attribute"() { + this.render("
content
"); + this.assertHTML("
content
"); + this.assertStableRerender(); + } + + @test "Attributes containing a helper are treated like a block"() { + this.registerHelper('testing', (params) => { + this.assert.deepEqual(params, [123]); + return "example.com"; + }); + + this.render('linky'); + this.assertHTML('linky'); + this.assertStableRerender(); + } + + @test "HTML boolean attribute 'disabled'"() { + this.render(''); + this.assertHTML(""); + + // TODO: What is the point of this test? (Note that it wouldn't work with SimpleDOM) + // assertNodeProperty(root.firstChild, 'input', 'disabled', true); + + this.assertStableRerender(); + } + + @test "Quoted attribute null values do not disable"() { + this.render('', { isDisabled: null }); + this.assertHTML(''); + this.assertStableRerender(); + + // TODO: What is the point of this test? (Note that it wouldn't work with SimpleDOM) + // assertNodeProperty(root.firstChild, 'input', 'disabled', false); + + this.rerender({ isDisabled: true }); + this.assertHTML(''); + this.assertStableNodes(); + + // TODO: ?????????? + this.rerender({ isDisabled: false }); + this.assertHTML(''); + this.assertStableNodes(); + + this.rerender({ isDisabled: null }); + this.assertHTML(''); + this.assertStableNodes(); + } + + @test "Unquoted attribute null values do not disable"() { + this.render('', { isDisabled: null }); + this.assertHTML(''); + this.assertStableRerender(); + + // TODO: What is the point of this test? (Note that it wouldn't work with SimpleDOM) + // assertNodeProperty(root.firstChild, 'input', 'disabled', false); + + this.rerender({ isDisabled: true }); + this.assertHTML(''); + this.assertStableRerender(); + + this.rerender({ isDisabled: false }); + this.assertHTML(''); + this.assertStableRerender(); + + this.rerender({ isDisabled: null }); + this.assertHTML(''); + this.assertStableRerender(); + } + + @test "Quoted attribute string values"() { + this.render("", { src: 'image.png' }); + this.assertHTML(""); + this.assertStableRerender(); + + this.rerender({ src: 'newimage.png' }); + this.assertHTML(""); + this.assertStableNodes(); + + this.rerender({ src: '' }); + this.assertHTML(""); + this.assertStableNodes(); + + this.rerender({ src: 'image.png' }); + this.assertHTML(""); + this.assertStableNodes(); + } + + @test "Unquoted attribute string values"() { + this.render("", { src: 'image.png' }); + this.assertHTML(""); + this.assertStableRerender(); + + this.rerender({ src: 'newimage.png' }); + this.assertHTML(""); + this.assertStableNodes(); + + this.rerender({ src: '' }); + this.assertHTML(""); + this.assertStableNodes(); + + this.rerender({ src: 'image.png' }); + this.assertHTML(""); + this.assertStableNodes(); + } + + @test "Unquoted img src attribute is not rendered when set to `null`"() { + this.render("", { src: null }); + this.assertHTML(""); + this.assertStableRerender(); + + this.rerender({ src: 'newimage.png' }); + this.assertHTML(""); + this.assertStableNodes(); + + this.rerender({ src: '' }); + this.assertHTML(""); + this.assertStableNodes(); + + this.rerender({ src: null }); + this.assertHTML(""); + this.assertStableNodes(); + } + + @test "Unquoted img src attribute is not rendered when set to `undefined`"() { + this.render("", { src: undefined }); + this.assertHTML(""); + this.assertStableRerender(); + + this.rerender({ src: 'newimage.png' }); + this.assertHTML(""); + this.assertStableNodes(); + + this.rerender({ src: '' }); + this.assertHTML(""); + this.assertStableNodes(); + + this.rerender({ src: undefined }); + this.assertHTML(""); + this.assertStableNodes(); + } + + @test "Unquoted a href attribute is not rendered when set to `null`"() { + this.render("", { href: null }); + this.assertHTML(""); + this.assertStableRerender(); + + this.rerender({ href: 'http://example.com' }); + this.assertHTML(""); + this.assertStableNodes(); + + this.rerender({ href: '' }); + this.assertHTML(""); + this.assertStableNodes(); + + this.rerender({ href: null }); + this.assertHTML(""); + this.assertStableNodes(); + } + + @test "Unquoted a href attribute is not rendered when set to `undefined`"() { + this.render("", { href: undefined }); + this.assertHTML(""); + this.assertStableRerender(); + + this.rerender({ href: 'http://example.com' }); + this.assertHTML(""); + this.assertStableNodes(); + + this.rerender({ href: '' }); + this.assertHTML(""); + this.assertStableNodes(); + + this.rerender({ href: undefined }); + this.assertHTML(""); + this.assertStableNodes(); + } + + @test "Attribute expression can be followed by another attribute"() { + this.render("
", { funstuff: "oh my" }); + this.assertHTML("
"); + this.assertStableRerender(); + + this.rerender({ funstuff: 'oh boy' }); + this.assertHTML("
"); + this.assertStableNodes(); + + this.rerender({ funstuff: '' }); + this.assertHTML("
"); + this.assertStableNodes(); + + this.rerender({ funstuff: "oh my" }); + this.assertHTML("
"); + this.assertStableNodes(); + } + + @test "Dynamic selected options"() { + this.render(strip` + + `, { selected: true }); + this.assertHTML(strip` + + `); + + let selectNode: any = this.element.childNodes[1]; + this.assert.equal(selectNode.selectedIndex, 1); + this.assertStableRerender(); + + this.rerender({ selected: false }); + this.assertHTML(strip` + + `); + selectNode = this.element.childNodes[1]; + + if (IE9_SELECT_QUIRK) { + this.assert.equal(selectNode.selectedIndex, -1); + } else { + this.assert.equal(selectNode.selectedIndex, 0); + } + + this.assertStableNodes(); + + this.rerender({ selected: '' }); + this.assertHTML(strip` + + `); + selectNode = this.element.childNodes[1]; + + if (IE9_SELECT_QUIRK) { + this.assert.equal(selectNode.selectedIndex, -1); + } else { + this.assert.equal(selectNode.selectedIndex, 0); + } + + this.assertStableNodes(); + + this.rerender({ selected: true }); + this.assertHTML(strip` + + `); + selectNode = this.element.childNodes[1]; + this.assert.equal(selectNode.selectedIndex, 1); + this.assertStableNodes(); + } + + @test "Dynamic multi-select"() { + this.render(strip` + `, + { + somethingTrue: true, + somethingTruthy: 'is-true', + somethingUndefined: undefined, + somethingNull: null, + somethingFalse: false + }); + + let selectNode = this.element.firstElementChild; + this.assert.ok(selectNode, 'rendered select'); + if (selectNode === null) { + return; + } + let options = selectNode.querySelectorAll('option'); + let selected: HTMLOptionElement[] = []; + for (let i = 0; i < options.length; i++) { + let option = options[i]; + if (option.selected) { + selected.push(option); + } + } + + this.assertHTML(strip` + `); + + this.assert.equal(selected.length, 2, 'two options are selected'); + this.assert.equal(selected[0].value, '1', 'first selected item is "1"'); + this.assert.equal(selected[1].value, '2', 'second selected item is "2"'); + } + + @test "HTML comments"() { + this.render('
'); + this.assertHTML('
'); + this.assertStableRerender(); + } + + @test "Curlies in HTML comments"() { + this.render('
', { foo: 'foo' }); + this.assertHTML('
'); + this.assertStableRerender(); + + this.rerender({ foo: 'bar' }); + this.assertHTML('
'); + this.assertStableNodes(); + + this.rerender({ foo: '' }); + this.assertHTML('
'); + this.assertStableNodes(); + + this.rerender({ foo: 'foo' }); + this.assertHTML('
'); + this.assertStableNodes(); + } + + @test "Complex Curlies in HTML comments"() { + this.render('
', { foo: 'foo' }); + this.assertHTML('
'); + this.assertStableRerender(); + + this.rerender({ foo: 'bar' }); + this.assertHTML('
'); + this.assertStableNodes(); + + this.rerender({ foo: '' }); + this.assertHTML('
'); + this.assertStableNodes(); + + this.rerender({ foo: 'foo' }); + this.assertHTML('
'); + this.assertStableNodes(); + } + + @test "HTML comments with multi-line mustaches"() { + this.render('
'); + this.assertHTML('
'); + this.assertStableRerender(); + } + + @test "Top level comments"() { + this.render(''); + this.assertHTML(''); + this.assertStableRerender(); + } + + @test "Handlebars comments"() { + this.render('
{{! Better not break! }}content
'); + this.assertHTML('
content
'); + this.assertStableRerender(); + } + + @test "Namespaced attribute"() { + this.render("content"); + this.assertHTML("content"); + this.assertStableRerender(); + } + + @test " tag with case-sensitive attribute"() { + this.render(''); + this.assertHTML(''); + let svg = this.element.firstChild; + if (assertNodeTagName(svg, 'svg')) { + this.assert.equal(svg.namespaceURI, SVG_NAMESPACE); + this.assert.equal(svg.getAttribute('viewBox'), '0 0 0 0'); + } + this.assertStableRerender(); + } + + @test "nested element in the SVG namespace"() { + let d = 'M 0 0 L 100 100'; + this.render(``); + this.assertHTML(``); + + let svg = this.element.firstChild; + + if (assertNodeTagName(svg, 'svg')) { + this.assert.equal(svg.namespaceURI, SVG_NAMESPACE); + + let path = svg.firstChild; + if (assertNodeTagName(path, 'path')) { + this.assert.equal(path.namespaceURI, SVG_NAMESPACE, + "creates the path element with a namespace"); + this.assert.equal(path.getAttribute('d'), d); + } + } + + this.assertStableRerender(); + } + + @test " tag has an SVG namespace"() { + this.render('Hi'); + this.assertHTML('Hi'); + + let svg = this.element.firstChild; + + if (assertNodeTagName(svg, 'svg')) { + this.assert.equal(svg.namespaceURI, SVG_NAMESPACE); + + let foreignObject = svg.firstChild; + if (assertNodeTagName(foreignObject, 'foreignobject')) { + this.assert.equal(foreignObject.namespaceURI, SVG_NAMESPACE, + "creates the foreignObject element with a namespace"); + } + } + + this.assertStableRerender(); + } + + @test "Namespaced and non-namespaced elements as siblings"() { + this.render('
'); + this.assertHTML('
'); + + this.assert.equal(this.element.childNodes[0].namespaceURI, SVG_NAMESPACE, + "creates the first svg element with a namespace"); + + this.assert.equal(this.element.childNodes[1].namespaceURI, SVG_NAMESPACE, + "creates the second svg element with a namespace"); + + this.assert.equal(this.element.childNodes[2].namespaceURI, XHTML_NAMESPACE, + "creates the div element without a namespace"); + + this.assertStableRerender(); + } + + @test "Namespaced and non-namespaced elements with nesting"() { + this.render('
'); + + let firstDiv = this.element.firstChild; + let secondDiv = this.element.lastChild; + let svg = firstDiv && firstDiv.firstChild; + + this.assertHTML('
'); + + if (assertNodeTagName(firstDiv, 'div')) { + this.assert.equal(firstDiv.namespaceURI, XHTML_NAMESPACE, + "first div's namespace is xhtmlNamespace"); + } + + if (assertNodeTagName(svg, 'svg')) { + this.assert.equal(svg.namespaceURI, SVG_NAMESPACE, + "svg's namespace is svgNamespace"); + } + + if (assertNodeTagName(secondDiv, 'div')) { + this.assert.equal(secondDiv.namespaceURI, XHTML_NAMESPACE, + "last div's namespace is xhtmlNamespace"); + } + + this.assertStableRerender(); + } + + @test "Case-sensitive tag has capitalization preserved"() { + this.render(''); + this.assertHTML(''); + this.assertStableRerender(); + } + + @test "Text curlies"() { + this.render('
{{title}}{{title}}
', { title: 'hello' }); + this.assertHTML('
hellohello
'); + this.assertStableRerender(); + + this.rerender({ title: 'goodbye' }); + this.assertHTML('
goodbyegoodbye
'); + this.assertStableNodes(); + + this.rerender({ title: '' }); + this.assertHTML('
'); + this.assertStableNodes(); + + this.rerender({ title: 'hello' }); + this.assertHTML('
hellohello
'); + this.assertStableNodes(); + } + + @test "Repaired text nodes are ensured in the right place Part 1"() { + this.render('{{a}} {{b}}', { a: "A", b: "B", c: "C", d: "D" }); + this.assertHTML('A B'); + this.assertStableRerender(); + } + + @test "Repaired text nodes are ensured in the right place Part 2"() { + this.render('
{{a}}{{b}}{{c}}wat{{d}}
', { a: "A", b: "B", c: "C", d: "D" }); + this.assertHTML('
ABCwatD
'); + this.assertStableRerender(); + } + + @test "Repaired text nodes are ensured in the right place Part 3"() { + this.render('{{a}}{{b}}', { a: "A", b: "B", c: "C", d: "D" }); + this.assertHTML('AB'); + this.assertStableRerender(); + } + + @test "Path expressions"() { + this.render('
{{model.foo.bar}}{{model.foo.bar}}
', { model: { foo: { bar: 'hello' } } }); + this.assertHTML('
hellohello
'); + this.assertStableRerender(); + + this.rerender({ model: { foo: { bar: 'goodbye' } } }); + this.assertHTML('
goodbyegoodbye
'); + this.assertStableNodes(); + + this.rerender({ model: { foo: { bar: '' } } }); + this.assertHTML('
'); + this.assertStableNodes(); + + this.rerender({ model: { foo: { bar: 'hello' } } }); + this.assertHTML('
hellohello
'); + this.assertStableNodes(); + } + + @test "Text curlies perform escaping"() { + this.render('
{{title}}{{title}}
', { title: 'hello' }); + this.assertHTML('
<strong>hello</strong><strong>hello</strong>
'); + this.assertStableRerender(); + + this.rerender({ title: 'goodbye' }); + this.assertHTML('
<i>goodbye</i><i>goodbye</i>
'); + this.assertStableNodes(); + + this.rerender({ title: '' }); + this.assertHTML('
'); + this.assertStableNodes(); + + this.rerender({ title: 'hello' }); + this.assertHTML('
<strong>hello</strong><strong>hello</strong>
'); + this.assertStableNodes(); + } + + @test "Rerender respects whitespace"() { + this.render('Hello {{ foo }} ', { foo: 'bar' }); + this.assertHTML('Hello bar '); + this.assertStableRerender(); + + this.rerender({ foo: 'baz' }); + this.assertHTML('Hello baz '); + this.assertStableNodes(); + + this.rerender({ foo: '' }); + this.assertHTML('Hello '); + this.assertStableNodes(); + + this.rerender({ foo: 'bar' }); + this.assertHTML('Hello bar '); + this.assertStableNodes(); + } + + @test "Safe HTML curlies"() { + let title = { toHTML() { return 'hello world'; } }; + this.render('
{{title}}
', { title }); + this.assertHTML('
hello world
'); + this.assertStableRerender(); + } + + @test "Triple curlies"() { + let title = 'hello world'; + this.render('
{{{title}}}
', { title }); + this.assertHTML('
hello world
'); + this.assertStableRerender(); + } + + @test "Triple curlie helpers"() { + this.registerHelper('unescaped', ([param]) => param); + this.registerHelper('escaped', ([param]) => param); + this.render('{{{unescaped "Yolo"}}} {{escaped "Yolo"}}'); + this.assertHTML('Yolo <strong>Yolo</strong>'); + this.assertStableRerender(); + } + + @test "Top level triple curlies"() { + let title = 'hello world'; + this.render('{{{title}}}', { title }); + this.assertHTML('hello world'); + this.assertStableRerender(); + } + + @test "Top level unescaped tr"() { + let title = 'Yo'; + this.render('{{{title}}}
', { title }); + this.assertHTML('
Yo
'); + this.assertStableRerender(); + } + @test "The compiler can handle top-level unescaped td inside tr contextualElement"() { + this.render('{{{html}}}', { html: 'Yo' }); + this.assertHTML('Yo'); + this.assertStableRerender(); + } + + @test "Extreme nesting"() { + this.render('{{foo}}{{bar}}{{baz}}{{boo}}{{brew}}{{bat}}{{flute}}{{argh}}', + { foo: "FOO", bar: "BAR", baz: "BAZ", boo: "BOO", brew: "BREW", bat: "BAT", flute: "FLUTE", argh: "ARGH" }); + this.assertHTML('FOOBARBAZBOOBREWBATFLUTEARGH'); + this.assertStableRerender(); + } + + @test "Simple blocks"() { + this.render('
{{#if admin}}

{{user}}

{{/if}}!
', { admin: true, user: 'chancancode' }); + this.assertHTML('

chancancode

!
'); + this.assertStableRerender(); + + let p = this.element.firstChild!.firstChild!; + + this.rerender({ admin: false }); + this.assertHTML('
!
'); + this.assertStableNodes({ except: p }); + + let comment = this.element.firstChild!.firstChild!; + + this.rerender({ admin: true }); + this.assertHTML('

chancancode

!
'); + this.assertStableNodes({ except: comment }); + } + + @test "Nested blocks"() { + this.render('
{{#if admin}}{{#if access}}

{{user}}

{{/if}}{{/if}}!
', { admin: true, access: true, user: 'chancancode' }); + this.assertHTML('

chancancode

!
'); + this.assertStableRerender(); + + let p = this.element.firstChild!.firstChild!; + + this.rerender({ admin: false }); + this.assertHTML('
!
'); + this.assertStableNodes({ except: p }); + + let comment = this.element.firstChild!.firstChild!; + + this.rerender({ admin: true }); + this.assertHTML('

chancancode

!
'); + this.assertStableNodes({ except: comment }); + + p = this.element.firstChild!.firstChild!; + + this.rerender({ access: false }); + this.assertHTML('
!
'); + this.assertStableNodes({ except: p }); + } + + @test "Loops"() { + this.render('
{{#each people key="handle" as |p|}}{{p.handle}} - {{p.name}}{{/each}}
', { + people: [ + { handle: 'tomdale', name: 'Tom Dale' }, + { handle: 'chancancode', name: 'Godfrey Chan' }, + { handle: 'wycats', name: 'Yehuda Katz' } + ] + }); + + this.assertHTML('
tomdale - Tom Dalechancancode - Godfrey Chanwycats - Yehuda Katz
'); + this.assertStableRerender(); + + this.rerender({ + people: [ + { handle: 'tomdale', name: 'Thomas Dale' }, + { handle: 'wycats', name: 'Yehuda Katz' } + ] + }); + + this.assertHTML('
tomdale - Thomas Dalewycats - Yehuda Katz
'); + } + + @test "Simple helpers"() { + this.registerHelper('testing', ([id]) => id); + this.render('
{{testing title}}
', { title: 'hello' }); + this.assertHTML('
hello
'); + this.assertStableRerender(); + } + + @test "GH#13999 The compiler can handle simple helpers with inline null parameter"() { + let value; + this.registerHelper('say-hello', function (params) { + value = params[0]; + return 'hello'; + }); + this.render('
{{say-hello null}}
'); + this.assertHTML('
hello
'); + this.assert.strictEqual(value, null, 'is null'); + this.assertStableRerender(); + } + + @test "GH#13999 The compiler can handle simple helpers with inline string literal null parameter"() { + let value; + this.registerHelper('say-hello', function (params) { + value = params[0]; + return 'hello'; + }); + + this.render('
{{say-hello "null"}}
'); + this.assertHTML('
hello
'); + this.assert.strictEqual(value, 'null', 'is null string literal'); + this.assertStableRerender(); + } + + @test "GH#13999 The compiler can handle simple helpers with inline undefined parameter"() { + let value: Opaque = 'PLACEHOLDER'; + let length; + this.registerHelper('say-hello', function (params) { + length = params.length; + value = params[0]; + return 'hello'; + }); + + this.render('
{{say-hello undefined}}
'); + this.assertHTML('
hello
'); + this.assert.strictEqual(length, 1); + this.assert.strictEqual(value, undefined, 'is undefined'); + this.assertStableRerender(); + } + + @test "GH#13999 The compiler can handle simple helpers with positional parameter undefined string literal"() { + let value: Opaque = 'PLACEHOLDER'; + let length; + this.registerHelper('say-hello', function (params) { + length = params.length; + value = params[0]; + return 'hello'; + }); + + this.render('
{{say-hello "undefined"}} undefined
'); + this.assertHTML('
hello undefined
'); + this.assert.strictEqual(length, 1); + this.assert.strictEqual(value, 'undefined', 'is undefined string literal'); + this.assertStableRerender(); + } + + @test "GH#13999 The compiler can handle components with undefined named arguments"() { + let value: Opaque = 'PLACEHOLDER'; + this.registerHelper('say-hello', function (_, hash) { + value = hash['foo']; + return 'hello'; + }); + + this.render('
{{say-hello foo=undefined}}
'); + this.assertHTML('
hello
'); + this.assert.strictEqual(value, undefined, 'is undefined'); + this.assertStableRerender(); + } + + @test "GH#13999 The compiler can handle components with undefined string literal named arguments"() { + let value: Opaque = 'PLACEHOLDER'; + this.registerHelper('say-hello', function (_, hash) { + value = hash['foo']; + return 'hello'; + }); + + this.render('
{{say-hello foo="undefined"}}
'); + this.assertHTML('
hello
'); + this.assert.strictEqual(value, 'undefined', 'is undefined string literal'); + this.assertStableRerender(); + } + + @test "GH#13999 The compiler can handle components with null named arguments"() { + let value; + this.registerHelper('say-hello', function (_, hash) { + value = hash['foo']; + return 'hello'; + }); + + this.render('
{{say-hello foo=null}}
'); + this.assertHTML('
hello
'); + this.assert.strictEqual(value, null, 'is null'); + this.assertStableRerender(); + } + + @test "GH#13999 The compiler can handle components with null string literal named arguments"() { + let value; + this.registerHelper('say-hello', function (_, hash) { + value = hash['foo']; + return 'hello'; + }); + + this.render('
{{say-hello foo="null"}}
'); + this.assertHTML('
hello
'); + this.assert.strictEqual(value, 'null', 'is null string literal'); + this.assertStableRerender(); + } + + @test "Null curly in attributes"() { + this.render('
hello
'); + this.assertHTML('
hello
'); + this.assertStableRerender(); + } + + @test "Null in primitive syntax"() { + this.render('{{#if null}}NOPE{{else}}YUP{{/if}}'); + this.assertHTML('YUP'); + this.assertStableRerender(); + } + + @test "Sexpr helpers"() { + this.registerHelper('testing', function (params) { + return params[0] + "!"; + }); + + this.render('
{{testing (testing "hello")}}
'); + this.assertHTML('
hello!!
'); + this.assertStableRerender(); + } + + @test "The compiler can handle multiple invocations of sexprs"() { + this.registerHelper('testing', function (params) { + return "" + params[0] + params[1]; + }); + + this.render('
{{testing (testing "hello" foo) (testing (testing bar "lol") baz)}}
', { foo: "FOO", bar: "BAR", baz: "BAZ" }); + this.assertHTML('
helloFOOBARlolBAZ
'); + this.assertStableRerender(); + } + + @test "The compiler passes along the hash arguments"() { + this.registerHelper('testing', function (_, hash) { + return hash['first'] + '-' + hash['second']; + }); + + this.render('
{{testing first="one" second="two"}}
'); + this.assertHTML('
one-two
'); + this.assertStableRerender(); + } + + @test "Attributes can be populated with helpers that generate a string"() { + this.registerHelper('testing', function (params) { + return params[0]; + }); + + this.render('linky', { url: 'linky.html' }); + this.assertHTML('linky'); + this.assertStableRerender(); + } + + @test "Attribute helpers take a hash"() { + this.registerHelper('testing', function (_, hash) { + return hash['path']; + }); + + this.render('linky', { url: 'linky.html' }); + this.assertHTML('linky'); + this.assertStableRerender(); + } + + @test "Attributes containing multiple helpers are treated like a block"() { + this.registerHelper('testing', function (params) { + return params[0]; + }); + + this.render('linky', { foo: 'foo.com', bar: 'bar' }); + this.assertHTML('linky'); + this.assertStableRerender(); + } + + @test "Elements inside a yielded block"() { + this.render('{{#identity}}
123
{{/identity}}'); + this.assertHTML('
123
'); + this.assertStableRerender(); + } + + @test "A simple block helper can return text"() { + this.render('{{#identity}}test{{else}}not shown{{/identity}}'); + this.assertHTML('test'); + this.assertStableRerender(); + } + + @test "A block helper can have an else block"() { + this.render('{{#render-inverse}}Nope{{else}}
123
{{/render-inverse}}'); + this.assertHTML('
123
'); + this.assertStableRerender(); + } +} + +class Rehydration extends RenderingTest { + public serialized: string; + public doc: any; + + @test "mismatched text nodes"() { + this.setupServer("{{content}}"); + this.renderServerSide({ content: 'hello' }); + this.assertServerOutput("hello"); + + this.setupClient(); + + this.renderClientSide({ content: 'goodbye' }); + this.assertHTML("goodbye"); + this.assertStableRerender(); + } + + @test "mismatched text nodes (server-render empty)"() { + this.setupServer("{{content}} world"); + this.renderServerSide({ content: '' }); + this.assertServerOutput(EMPTY, " world"); + + this.setupClient(); + + this.renderClientSide({ content: 'hello' }); + this.assertHTML("hello world"); + + // TODO: handle %empty% in the testing DSL + // this.assertStableNodes(); + this.assertStableRerender(); + } + + @test "mismatched elements"() { + this.setupServer("{{#if admin}}
hi admin
{{else}}

HAXOR

{{/if}}"); + this.renderServerSide({ admin: true }); + this.assertServerOutput(OPEN, "
hi admin
", CLOSE); + + this.setupClient(); + + this.renderClientSide({ admin: false }); + this.assertHTML("

HAXOR

"); + this.assertStableRerender(); + } + + @test "extra nodes at the end"() { + this.setupServer("{{#if admin}}
hi admin
{{else}}
HAXOR{{stopHaxing}}
{{/if}}"); + this.renderServerSide({ admin: false, stopHaxing: 'stahp' }); + this.assertServerOutput(OPEN, "
HAXORstahp
", CLOSE); + + this.setupClient(); + this.renderClientSide({ admin: true }); + this.assertHTML("
hi admin
"); + this.assertStableRerender(); + } + + @test "Node curlies"() { + this.setupServer('
{{node}}
'); + + let node = this.env.getAppendOperations().createTextNode('hello'); + this.renderServerSide({ node }); + this.assertServerOutput('
hello
'); + + this.setupClient(); + + let clientNode = this.env.getDOM().createTextNode('hello'); + this.renderClientSide({ node: clientNode }); + this.assertHTML('
hello
'); + this.assertStableRerender(); + + let clientNode2 = this.env.getDOM().createTextNode('goodbye'); + this.rerender({ node: clientNode2 }); + this.assertHTML('
goodbye
'); + this.assertStableNodes({ except: clientNode as Text }); + + this.rerender({ node: clientNode }); + this.assertHTML('
hello
'); + this.assertStableNodes({ except: clientNode2 as Text }); + } +} + +module("[Bundle Compiler] Rehydration Tests", Rehydration); +module("[Bundle Compiler] Initial Render Tests", RenderingTest); diff --git a/packages/@glimmer/bundle-compiler/test/support.ts b/packages/@glimmer/bundle-compiler/test/support.ts new file mode 100644 index 0000000000..e821911fb0 --- /dev/null +++ b/packages/@glimmer/bundle-compiler/test/support.ts @@ -0,0 +1,34 @@ +import { CompilerDelegate, Specifier } from "@glimmer/bundle-compiler"; + +export class TestCompilerDelegate implements CompilerDelegate { + hasComponentInScope(componentName: string, referrer: Specifier): boolean { + throw new Error("Method not implemented."); + } + resolveComponentSpecifier(componentName: string, referrer: Specifier): Specifier { + throw new Error("Method not implemented."); + } + getComponentCapabilities(specifier: Specifier): ComponentCapabilities { + throw new Error("Method not implemented."); + } + getComponentLayout(specifier: Specifier): SerializedTemplateBlock { + throw new Error("Method not implemented."); + } + hasHelperInScope(helperName: string, referer: Specifier): boolean { + throw new Error("Method not implemented."); + } + resolveHelperSpecifier(helperName: string, referer: Specifier): Specifier { + throw new Error("Method not implemented."); + } + hasModifierInScope(modifierName: string, referer: Specifier): boolean { + throw new Error("Method not implemented."); + } + resolveModifierSpecifier(modifierName: string, referer: Specifier): Specifier { + throw new Error("Method not implemented."); + } + hasPartialInScope(partialName: string, referer: Specifier): boolean { + throw new Error("Method not implemented."); + } + resolvePartialSpecifier(partialName: string, referer: Specifier): Specifier { + throw new Error("Method not implemented."); + } +} \ No newline at end of file From 255bf2f6a9dc88bfe19031cfa30538a50e54bd51 Mon Sep 17 00:00:00 2001 From: Yehuda Katz Date: Mon, 14 Aug 2017 18:23:45 -0700 Subject: [PATCH 12/42] [WIP] Starting whole-flow tests More progress made on the bundle compiler. We're now testing the whole flow of bundle compiler -> runtime. --- .../@glimmer/bundle-compiler/lib/templates.ts | 135 +--- .../test/initial-render-test.ts | 659 ++++++++++-------- .../interfaces/lib/tier1/symbol-table.d.ts | 3 +- packages/@glimmer/opcode-compiler/index.ts | 1 + .../lib/compilable-template.ts | 4 +- .../opcode-compiler/lib/interfaces.ts | 2 +- .../opcode-compiler/lib/opcode-builder.ts | 28 +- .../@glimmer/opcode-compiler/lib/syntax.ts | 1 + packages/@glimmer/runtime/index.ts | 4 +- packages/@glimmer/runtime/lib/template.ts | 2 +- .../@glimmer/runtime/lib/vm/render-result.ts | 2 +- .../runtime/test/initial-render-test.ts | 60 +- .../@glimmer/runtime/test/template-test.ts | 26 +- packages/@glimmer/test-helpers/index.ts | 1 + .../test-helpers/lib/abstract-test-case.ts | 166 ++--- .../@glimmer/test-helpers/lib/environment.ts | 156 +++-- .../test-helpers/test/i-n-u-r-test.ts | 10 +- .../test/invocation-generation-test.ts | 4 +- 18 files changed, 631 insertions(+), 633 deletions(-) diff --git a/packages/@glimmer/bundle-compiler/lib/templates.ts b/packages/@glimmer/bundle-compiler/lib/templates.ts index 22d66b0ef4..4614e97d4f 100644 --- a/packages/@glimmer/bundle-compiler/lib/templates.ts +++ b/packages/@glimmer/bundle-compiler/lib/templates.ts @@ -1,10 +1,8 @@ -import { SerializedTemplateBlock } from '@glimmer/wire-format'; import { ASTPluginBuilder, preprocess } from "@glimmer/syntax"; import { TemplateCompiler } from "@glimmer/compiler"; -import { CompilableTemplate, Macros, OpcodeBuilderConstructor, ComponentCapabilities, CompileTimeLookup, CompileOptions, VMHandle, ICompilableTemplate } from "@glimmer/opcode-compiler"; +import { CompilableTemplate, Macros, OpcodeBuilderConstructor, ComponentCapabilities, CompileTimeLookup, CompileOptions, VMHandle } from "@glimmer/opcode-compiler"; import { WriteOnlyProgram, WriteOnlyConstants } from "@glimmer/program"; -import { Option, ProgramSymbolTable, Recast, Dict } from "@glimmer/interfaces"; -import { dict } from "@glimmer/util"; +import { Option, ProgramSymbolTable, Recast } from "@glimmer/interfaces"; export interface BundleCompileOptions { plugins: ASTPluginBuilder[]; @@ -13,104 +11,54 @@ export interface BundleCompileOptions { export type ModuleName = string; export type NamedExport = string; -export interface Specifier extends Readonly { - module: ModuleName; - name: NamedExport; -} - -class SpecifierValue implements Specifier { - private static values: Dict> = dict(); - - static for(name: NamedExport, module: ModuleName): Specifier { - let specifiers = this.values[name]; - - if (!specifiers) { - specifiers = this.values[module] = dict(); - } - - let specifier = specifiers[name]; - - if (!specifier) { - specifier = { name, module }; - } - - return Object.freeze(specifier); - } - - private constructor(public name: NamedExport, public module: ModuleName) {} -} - -export function specifier(name: NamedExport, module: ModuleName): Specifier { - return SpecifierValue.for(name, module); +export interface Specifier { + module: NamedExport; + name: ModuleName; } export class SpecifierMap { public helpers = new Map(); public modifiers = new Map(); public components = new Map(); - - private helpersBySpecifier = new Map(); - private modifiersBySpecifier = new Map(); - private componentsBySpecifier = new Map(); - - private handleFor(handles: Map, specifier: Specifier): number { - let handle = handles.get(specifier); - - if (!handle) { - handle = handles.size; - handles.set(specifier, handle); - } - - return handle; - } - - handleForHelper(helper: Specifier): number { - return this.handleFor(this.helpersBySpecifier, helper); - } - - handleForModifier(modifier: Specifier): number { - return this.handleFor(this.modifiersBySpecifier, modifier); - } - - handleForComponent(component: Specifier): number { - return this.handleFor(this.componentsBySpecifier, component); - } } export class BundleCompiler { - private program = new WriteOnlyProgram(new WriteOnlyConstants()); - public specifiers = new SpecifierMap(); + private specifiers = new SpecifierMap(); constructor( - protected plugins: ASTPluginBuilder[] = [], protected macros: Macros, protected Builder: OpcodeBuilderConstructor, - protected delegate: CompilerDelegate + protected delegate: CompilerDelegate, + private program: WriteOnlyProgram = new WriteOnlyProgram(new WriteOnlyConstants()), + protected plugins: ASTPluginBuilder[] = [] ) {} - compile(input: string, specifier: Specifier, delegate: CompilerDelegate): { symbolTable: ProgramSymbolTable, handle: number } { + getSpecifierMap(): SpecifierMap { + return this.specifiers; + } + + compile(input: string, specifier: Specifier): { symbolTable: ProgramSymbolTable, handle: VMHandle } { let ast = preprocess(input, { plugins: { ast: this.plugins } }); - let template = TemplateCompiler.compile({ meta: null }, ast); + let template = TemplateCompiler.compile({ meta: specifier }, ast); let block = template.toJSON(); let { program, macros, Builder } = this; - let lookup = new BundlingLookup(delegate, this.specifiers); + let lookup = new BundlingLookup(this.delegate); let options: CompileOptions = { program, macros, Builder, lookup, + referer: specifier, asPartial: false }; - lookup.options = options; - let compilable = CompilableTemplate.topLevel(block, options); - let handle = compilable.compile() as Recast; + let handle = compilable.compile(); - this.specifiers.components.set(handle, specifier); + this.specifiers.components.set(handle as Recast, specifier); return { handle, symbolTable: compilable.symbolTable }; } @@ -141,8 +89,6 @@ export interface CompilerDelegate { */ getComponentCapabilities(specifier: Specifier): ComponentCapabilities; - getComponentLayout(specifier: Specifier): SerializedTemplateBlock; - hasHelperInScope(helperName: string, referer: Specifier): boolean; resolveHelperSpecifier(helperName: string, referer: Specifier): Specifier; @@ -153,51 +99,38 @@ export interface CompilerDelegate { resolvePartialSpecifier(partialName: string, referer: Specifier): Specifier; } -class BundlingLookup implements CompileTimeLookup { - public options: CompileOptions; - - constructor(private delegate: CompilerDelegate, private specifiers: SpecifierMap) {} +class BundlingLookup implements CompileTimeLookup { + constructor(private delegate: CompilerDelegate) {} - lookupComponentSpec(name: string, referer: Specifier): Option { - if (this.delegate.hasComponentInScope(name, referer)) { - let specifier = this.delegate.resolveComponentSpecifier(name, referer); - return this.specifiers.handleForComponent(specifier); - } else { - return null; - } - } - - getCapabilities(handle: number): ComponentCapabilities { - let specifier = this.specifiers.components.get(handle)!; - return this.delegate.getComponentCapabilities(specifier); + getCapabilities(meta: Specifier): ComponentCapabilities { + return this.delegate.getComponentCapabilities(meta); } - getLayout(handle: number): Option> { - let specifier = this.specifiers.components.get(handle)!; - let block = this.delegate.getComponentLayout(specifier); - - return CompilableTemplate.topLevel(block, this.options); + getLayout(name: string, meta: Specifier): Option<{ symbolTable: ProgramSymbolTable; handle: VMHandle }> { + throw new Error("Method not implemented."); } - lookupHelper(name: string, referer: Specifier): Option { + lookupHelper(name: string, referer: Specifier): Option { if (this.delegate.hasHelperInScope(name, referer)) { - let specifier = this.delegate.resolveHelperSpecifier(name, referer); - return this.specifiers.handleForHelper(specifier); + return this.delegate.resolveHelperSpecifier(name, referer); } else { return null; } } - lookupModifier(name: string, referer: Specifier): Option { + lookupModifier(name: string, referer: Specifier): Option { if (this.delegate.hasModifierInScope(name, referer)) { - let specifier = this.delegate.resolveHelperSpecifier(name, referer); - return this.specifiers.handleForHelper(specifier); + return this.delegate.resolveModifierSpecifier(name, referer); } else { return null; } } - lookupPartial(_name: string, _referer: Specifier): Option { + lookupComponent(name: string, meta: Specifier): Option { + throw new Error("Method not implemented."); + } + + lookupPartial(name: string, meta: Specifier): Option { throw new Error("Method not implemented."); } } diff --git a/packages/@glimmer/bundle-compiler/test/initial-render-test.ts b/packages/@glimmer/bundle-compiler/test/initial-render-test.ts index 5765cc74cb..9685e1deb9 100644 --- a/packages/@glimmer/bundle-compiler/test/initial-render-test.ts +++ b/packages/@glimmer/bundle-compiler/test/initial-render-test.ts @@ -1,25 +1,20 @@ -import { Opaque, Dict } from "@glimmer/interfaces"; -import { SVG_NAMESPACE, RenderResult } from "@glimmer/runtime"; +import { Opaque, Resolver, Option, Dict } from "@glimmer/interfaces"; import { - RenderTests, module, test, - strip, - assertNodeTagName, - EMPTY, - OPEN, - CLOSE, - renderTemplate, - TestEnvironment, - equalTokens, - Content, + AbstractRenderTest, TestDynamicScope, - content + strip, + UserHelper } from "@glimmer/test-helpers"; -import * as SimpleDOM from "simple-dom"; -import { NodeDOMTreeConstruction } from "@glimmer/node"; -import { expect } from "@glimmer/util"; +import { AbstractTestEnvironment } from "@glimmer/test-helpers/lib/environment"; +import { BundleCompiler, CompilerDelegate, Specifier } from "@glimmer/bundle-compiler"; +import { Macros, EagerOpcodeBuilder, CompileTimeLookup } from "@glimmer/opcode-compiler"; +import { Program, Constants } from "@glimmer/program"; +import { elementBuilder, LowLevelVM, TemplateIterator, RenderResult } from "@glimmer/runtime"; import { UpdatableReference } from "@glimmer/object-reference"; +import { renderSync } from "@glimmer/test-helpers/lib/abstract-test-case"; +import { dict } from "@glimmer/util"; const XHTML_NAMESPACE = 'http://www.w3.org/1999/xhtml'; @@ -39,7 +34,146 @@ const IE9_SELECT_QUIRK = (() => { return isSelectQuirk; })(); -class RenderingTest extends RenderTests { +class BundledClientEnvironment extends AbstractTestEnvironment { + protected program: Program; + protected resolver: Resolver; + + constructor(options = {}) { + super(options); + } +} + +class RuntimeResolver implements Resolver { + lookupHelper(name: string, meta: Opaque): Option { + throw new Error("Method not implemented."); + } + lookupModifier(name: string, meta: Opaque): Option { + throw new Error("Method not implemented."); + } + lookupComponent(name: string, meta: Opaque): Option { + throw new Error("Method not implemented."); + } + lookupPartial(name: string, meta: Opaque): Option { + throw new Error("Method not implemented."); + } + resolve(specifier: number): U { + throw new Error("Method not implemented."); + } +} + +class BundlingLookup implements CompileTimeLookup { + public registry = dict>(); + + getCapabilities(handle: number): ComponentCapabilities { + throw new Error("Method not implemented."); + } + getLayout(handle: number): Option> { + throw new Error("Method not implemented."); + } + + lookupHelper(name: string, referer: Specifier): Option { + if (referer.module === 'main') { + let module = this.registry[name]; + } + throw new Error("Method not implemented."); + } + + lookupModifier(name: string, referer: Specifier): Option { + throw new Error("Method not implemented."); + } + lookupComponentSpec(name: string, referer: Specifier): Option { + throw new Error("Method not implemented."); + } + lookupPartial(name: string, referer: Specifier): Option { + throw new Error("Method not implemented."); + } + + registerModule(name: string, value: Opaque): void { + let module = this.registry[name]; + + if (!module) module = this.registry[name] = dict(); + + module.default = value; + } +} + +class BundlingDelegate implements CompilerDelegate { + constructor(private lookup: BundlingLookup) {} + + hasComponentInScope(componentName: string, referrer: Specifier): boolean { + return false; + } + resolveComponentSpecifier(componentName: string, referrer: Specifier): Specifier { + throw new Error("Method not implemented."); + } + getComponentCapabilities(specifier: Specifier): ComponentCapabilities { + throw new Error("Method not implemented."); + } + hasHelperInScope(helperName: string, referer: Specifier): boolean { + return !!this.lookup.lookupHelper(name, referer); + } + resolveHelperSpecifier(helperName: string, referer: Specifier): Specifier { + throw new Error("Method not implemented."); + } + hasModifierInScope(modifierName: string, referer: Specifier): boolean { + return false; + } + resolveModifierSpecifier(modifierName: string, referer: Specifier): Specifier { + throw new Error("Method not implemented."); + } + hasPartialInScope(partialName: string, referer: Specifier): boolean { + return false; + } + resolvePartialSpecifier(partialName: string, referer: Specifier): Specifier { + throw new Error("Method not implemented."); + } +} + +class RenderingTest extends AbstractRenderTest { + protected element: HTMLElement; + protected lookup = new BundlingLookup(); + + constructor() { + super(); + this.element = this.env.getAppendOperations().createElement("div") as HTMLDivElement; + } + + registerHelper(name: string, helper: UserHelper): void { + this.lookup.registerModule(name, helper); + } + + registerComponent(type: "Glimmer" | "Curly" | "Dynamic" | "Basic" | "Fragment", name: string, layout: string): void { + throw new Error("Method not implemented."); + } + + populateHelpers(): void { + throw new Error("Method not implemented."); + } + + protected renderTemplate(template: string): RenderResult { + let macros = new Macros(); + let resolver = new RuntimeResolver(); + let program = new Program(new Constants(resolver)); + let delegate = new BundlingDelegate(this.lookup); + + let compiler = new BundleCompiler(macros, EagerOpcodeBuilder, delegate, program); + let { symbolTable, handle } = compiler.compile(template, { module: 'main', name: 'default' }); + + let { env } = this; + + let element = this.element = env.getAppendOperations().createElement('div') as HTMLDivElement; + let cursor = { element, nextSibling: null }; + let builder = elementBuilder({ mode: 'client', env, cursor }); + let self = new UpdatableReference(this.context); + let dynamicScope = new TestDynamicScope(); + let vm = LowLevelVM.initial(program, env, self, null, dynamicScope, builder, symbolTable, handle); + let iterator = new TemplateIterator(vm); + + return renderSync(env, iterator); + } + + protected env = new BundledClientEnvironment(); + @test "HTML text content"() { this.render("content"); this.assertHTML("content"); @@ -863,291 +997,212 @@ class RenderingTest extends RenderTests { this.assertHTML('
tomdale - Thomas Dalewycats - Yehuda Katz
'); } - @test "Simple helpers"() { - this.registerHelper('testing', ([id]) => id); - this.render('
{{testing title}}
', { title: 'hello' }); - this.assertHTML('
hello
'); - this.assertStableRerender(); - } - - @test "GH#13999 The compiler can handle simple helpers with inline null parameter"() { - let value; - this.registerHelper('say-hello', function (params) { - value = params[0]; - return 'hello'; - }); - this.render('
{{say-hello null}}
'); - this.assertHTML('
hello
'); - this.assert.strictEqual(value, null, 'is null'); - this.assertStableRerender(); - } - - @test "GH#13999 The compiler can handle simple helpers with inline string literal null parameter"() { - let value; - this.registerHelper('say-hello', function (params) { - value = params[0]; - return 'hello'; - }); - - this.render('
{{say-hello "null"}}
'); - this.assertHTML('
hello
'); - this.assert.strictEqual(value, 'null', 'is null string literal'); - this.assertStableRerender(); - } - - @test "GH#13999 The compiler can handle simple helpers with inline undefined parameter"() { - let value: Opaque = 'PLACEHOLDER'; - let length; - this.registerHelper('say-hello', function (params) { - length = params.length; - value = params[0]; - return 'hello'; - }); - - this.render('
{{say-hello undefined}}
'); - this.assertHTML('
hello
'); - this.assert.strictEqual(length, 1); - this.assert.strictEqual(value, undefined, 'is undefined'); - this.assertStableRerender(); - } - - @test "GH#13999 The compiler can handle simple helpers with positional parameter undefined string literal"() { - let value: Opaque = 'PLACEHOLDER'; - let length; - this.registerHelper('say-hello', function (params) { - length = params.length; - value = params[0]; - return 'hello'; - }); - - this.render('
{{say-hello "undefined"}} undefined
'); - this.assertHTML('
hello undefined
'); - this.assert.strictEqual(length, 1); - this.assert.strictEqual(value, 'undefined', 'is undefined string literal'); - this.assertStableRerender(); - } - - @test "GH#13999 The compiler can handle components with undefined named arguments"() { - let value: Opaque = 'PLACEHOLDER'; - this.registerHelper('say-hello', function (_, hash) { - value = hash['foo']; - return 'hello'; - }); - - this.render('
{{say-hello foo=undefined}}
'); - this.assertHTML('
hello
'); - this.assert.strictEqual(value, undefined, 'is undefined'); - this.assertStableRerender(); - } - - @test "GH#13999 The compiler can handle components with undefined string literal named arguments"() { - let value: Opaque = 'PLACEHOLDER'; - this.registerHelper('say-hello', function (_, hash) { - value = hash['foo']; - return 'hello'; - }); - - this.render('
{{say-hello foo="undefined"}}
'); - this.assertHTML('
hello
'); - this.assert.strictEqual(value, 'undefined', 'is undefined string literal'); - this.assertStableRerender(); - } - - @test "GH#13999 The compiler can handle components with null named arguments"() { - let value; - this.registerHelper('say-hello', function (_, hash) { - value = hash['foo']; - return 'hello'; - }); - - this.render('
{{say-hello foo=null}}
'); - this.assertHTML('
hello
'); - this.assert.strictEqual(value, null, 'is null'); - this.assertStableRerender(); - } - - @test "GH#13999 The compiler can handle components with null string literal named arguments"() { - let value; - this.registerHelper('say-hello', function (_, hash) { - value = hash['foo']; - return 'hello'; - }); - - this.render('
{{say-hello foo="null"}}
'); - this.assertHTML('
hello
'); - this.assert.strictEqual(value, 'null', 'is null string literal'); - this.assertStableRerender(); - } - - @test "Null curly in attributes"() { - this.render('
hello
'); - this.assertHTML('
hello
'); - this.assertStableRerender(); - } - - @test "Null in primitive syntax"() { - this.render('{{#if null}}NOPE{{else}}YUP{{/if}}'); - this.assertHTML('YUP'); - this.assertStableRerender(); - } - - @test "Sexpr helpers"() { - this.registerHelper('testing', function (params) { - return params[0] + "!"; - }); - - this.render('
{{testing (testing "hello")}}
'); - this.assertHTML('
hello!!
'); - this.assertStableRerender(); - } - - @test "The compiler can handle multiple invocations of sexprs"() { - this.registerHelper('testing', function (params) { - return "" + params[0] + params[1]; - }); - - this.render('
{{testing (testing "hello" foo) (testing (testing bar "lol") baz)}}
', { foo: "FOO", bar: "BAR", baz: "BAZ" }); - this.assertHTML('
helloFOOBARlolBAZ
'); - this.assertStableRerender(); - } - - @test "The compiler passes along the hash arguments"() { - this.registerHelper('testing', function (_, hash) { - return hash['first'] + '-' + hash['second']; - }); - - this.render('
{{testing first="one" second="two"}}
'); - this.assertHTML('
one-two
'); - this.assertStableRerender(); - } - - @test "Attributes can be populated with helpers that generate a string"() { - this.registerHelper('testing', function (params) { - return params[0]; - }); - - this.render('linky', { url: 'linky.html' }); - this.assertHTML('linky'); - this.assertStableRerender(); - } - - @test "Attribute helpers take a hash"() { - this.registerHelper('testing', function (_, hash) { - return hash['path']; - }); - - this.render('linky', { url: 'linky.html' }); - this.assertHTML('linky'); - this.assertStableRerender(); - } - - @test "Attributes containing multiple helpers are treated like a block"() { - this.registerHelper('testing', function (params) { - return params[0]; - }); - - this.render('linky', { foo: 'foo.com', bar: 'bar' }); - this.assertHTML('linky'); - this.assertStableRerender(); - } - - @test "Elements inside a yielded block"() { - this.render('{{#identity}}
123
{{/identity}}'); - this.assertHTML('
123
'); - this.assertStableRerender(); - } - - @test "A simple block helper can return text"() { - this.render('{{#identity}}test{{else}}not shown{{/identity}}'); - this.assertHTML('test'); - this.assertStableRerender(); - } - - @test "A block helper can have an else block"() { - this.render('{{#render-inverse}}Nope{{else}}
123
{{/render-inverse}}'); - this.assertHTML('
123
'); - this.assertStableRerender(); - } -} - -class Rehydration extends RenderingTest { - public serialized: string; - public doc: any; - - @test "mismatched text nodes"() { - this.setupServer("{{content}}"); - this.renderServerSide({ content: 'hello' }); - this.assertServerOutput("hello"); - - this.setupClient(); - - this.renderClientSide({ content: 'goodbye' }); - this.assertHTML("goodbye"); - this.assertStableRerender(); - } - - @test "mismatched text nodes (server-render empty)"() { - this.setupServer("{{content}} world"); - this.renderServerSide({ content: '' }); - this.assertServerOutput(EMPTY, " world"); - - this.setupClient(); - - this.renderClientSide({ content: 'hello' }); - this.assertHTML("hello world"); - - // TODO: handle %empty% in the testing DSL - // this.assertStableNodes(); - this.assertStableRerender(); - } - - @test "mismatched elements"() { - this.setupServer("{{#if admin}}
hi admin
{{else}}

HAXOR

{{/if}}"); - this.renderServerSide({ admin: true }); - this.assertServerOutput(OPEN, "
hi admin
", CLOSE); - - this.setupClient(); - - this.renderClientSide({ admin: false }); - this.assertHTML("

HAXOR

"); - this.assertStableRerender(); - } - - @test "extra nodes at the end"() { - this.setupServer("{{#if admin}}
hi admin
{{else}}
HAXOR{{stopHaxing}}
{{/if}}"); - this.renderServerSide({ admin: false, stopHaxing: 'stahp' }); - this.assertServerOutput(OPEN, "
HAXORstahp
", CLOSE); - - this.setupClient(); - this.renderClientSide({ admin: true }); - this.assertHTML("
hi admin
"); - this.assertStableRerender(); - } - - @test "Node curlies"() { - this.setupServer('
{{node}}
'); - - let node = this.env.getAppendOperations().createTextNode('hello'); - this.renderServerSide({ node }); - this.assertServerOutput('
hello
'); - - this.setupClient(); - - let clientNode = this.env.getDOM().createTextNode('hello'); - this.renderClientSide({ node: clientNode }); - this.assertHTML('
hello
'); - this.assertStableRerender(); - - let clientNode2 = this.env.getDOM().createTextNode('goodbye'); - this.rerender({ node: clientNode2 }); - this.assertHTML('
goodbye
'); - this.assertStableNodes({ except: clientNode as Text }); - - this.rerender({ node: clientNode }); - this.assertHTML('
hello
'); - this.assertStableNodes({ except: clientNode2 as Text }); - } + // @test "Simple helpers"() { + // this.registerHelper('testing', ([id]) => id); + // this.render('
{{testing title}}
', { title: 'hello' }); + // this.assertHTML('
hello
'); + // this.assertStableRerender(); + // } + + // @test "GH#13999 The compiler can handle simple helpers with inline null parameter"() { + // let value; + // this.registerHelper('say-hello', function (params) { + // value = params[0]; + // return 'hello'; + // }); + // this.render('
{{say-hello null}}
'); + // this.assertHTML('
hello
'); + // this.assert.strictEqual(value, null, 'is null'); + // this.assertStableRerender(); + // } + + // @test "GH#13999 The compiler can handle simple helpers with inline string literal null parameter"() { + // let value; + // this.registerHelper('say-hello', function (params) { + // value = params[0]; + // return 'hello'; + // }); + + // this.render('
{{say-hello "null"}}
'); + // this.assertHTML('
hello
'); + // this.assert.strictEqual(value, 'null', 'is null string literal'); + // this.assertStableRerender(); + // } + + // @test "GH#13999 The compiler can handle simple helpers with inline undefined parameter"() { + // let value: Opaque = 'PLACEHOLDER'; + // let length; + // this.registerHelper('say-hello', function (params) { + // length = params.length; + // value = params[0]; + // return 'hello'; + // }); + + // this.render('
{{say-hello undefined}}
'); + // this.assertHTML('
hello
'); + // this.assert.strictEqual(length, 1); + // this.assert.strictEqual(value, undefined, 'is undefined'); + // this.assertStableRerender(); + // } + + // @test "GH#13999 The compiler can handle simple helpers with positional parameter undefined string literal"() { + // let value: Opaque = 'PLACEHOLDER'; + // let length; + // this.registerHelper('say-hello', function (params) { + // length = params.length; + // value = params[0]; + // return 'hello'; + // }); + + // this.render('
{{say-hello "undefined"}} undefined
'); + // this.assertHTML('
hello undefined
'); + // this.assert.strictEqual(length, 1); + // this.assert.strictEqual(value, 'undefined', 'is undefined string literal'); + // this.assertStableRerender(); + // } + + // @test "GH#13999 The compiler can handle components with undefined named arguments"() { + // let value: Opaque = 'PLACEHOLDER'; + // this.registerHelper('say-hello', function (_, hash) { + // value = hash['foo']; + // return 'hello'; + // }); + + // this.render('
{{say-hello foo=undefined}}
'); + // this.assertHTML('
hello
'); + // this.assert.strictEqual(value, undefined, 'is undefined'); + // this.assertStableRerender(); + // } + + // @test "GH#13999 The compiler can handle components with undefined string literal named arguments"() { + // let value: Opaque = 'PLACEHOLDER'; + // this.registerHelper('say-hello', function (_, hash) { + // value = hash['foo']; + // return 'hello'; + // }); + + // this.render('
{{say-hello foo="undefined"}}
'); + // this.assertHTML('
hello
'); + // this.assert.strictEqual(value, 'undefined', 'is undefined string literal'); + // this.assertStableRerender(); + // } + + // @test "GH#13999 The compiler can handle components with null named arguments"() { + // let value; + // this.registerHelper('say-hello', function (_, hash) { + // value = hash['foo']; + // return 'hello'; + // }); + + // this.render('
{{say-hello foo=null}}
'); + // this.assertHTML('
hello
'); + // this.assert.strictEqual(value, null, 'is null'); + // this.assertStableRerender(); + // } + + // @test "GH#13999 The compiler can handle components with null string literal named arguments"() { + // let value; + // this.registerHelper('say-hello', function (_, hash) { + // value = hash['foo']; + // return 'hello'; + // }); + + // this.render('
{{say-hello foo="null"}}
'); + // this.assertHTML('
hello
'); + // this.assert.strictEqual(value, 'null', 'is null string literal'); + // this.assertStableRerender(); + // } + + // @test "Null curly in attributes"() { + // this.render('
hello
'); + // this.assertHTML('
hello
'); + // this.assertStableRerender(); + // } + + // @test "Null in primitive syntax"() { + // this.render('{{#if null}}NOPE{{else}}YUP{{/if}}'); + // this.assertHTML('YUP'); + // this.assertStableRerender(); + // } + + // @test "Sexpr helpers"() { + // this.registerHelper('testing', function (params) { + // return params[0] + "!"; + // }); + + // this.render('
{{testing (testing "hello")}}
'); + // this.assertHTML('
hello!!
'); + // this.assertStableRerender(); + // } + + // @test "The compiler can handle multiple invocations of sexprs"() { + // this.registerHelper('testing', function (params) { + // return "" + params[0] + params[1]; + // }); + + // this.render('
{{testing (testing "hello" foo) (testing (testing bar "lol") baz)}}
', { foo: "FOO", bar: "BAR", baz: "BAZ" }); + // this.assertHTML('
helloFOOBARlolBAZ
'); + // this.assertStableRerender(); + // } + + // @test "The compiler passes along the hash arguments"() { + // this.registerHelper('testing', function (_, hash) { + // return hash['first'] + '-' + hash['second']; + // }); + + // this.render('
{{testing first="one" second="two"}}
'); + // this.assertHTML('
one-two
'); + // this.assertStableRerender(); + // } + + // @test "Attributes can be populated with helpers that generate a string"() { + // this.registerHelper('testing', function (params) { + // return params[0]; + // }); + + // this.render('linky', { url: 'linky.html' }); + // this.assertHTML('linky'); + // this.assertStableRerender(); + // } + + // @test "Attribute helpers take a hash"() { + // this.registerHelper('testing', function (_, hash) { + // return hash['path']; + // }); + + // this.render('linky', { url: 'linky.html' }); + // this.assertHTML('linky'); + // this.assertStableRerender(); + // } + + // @test "Attributes containing multiple helpers are treated like a block"() { + // this.registerHelper('testing', function (params) { + // return params[0]; + // }); + + // this.render('linky', { foo: 'foo.com', bar: 'bar' }); + // this.assertHTML('linky'); + // this.assertStableRerender(); + // } + + // @test "Elements inside a yielded block"() { + // this.render('{{#identity}}
123
{{/identity}}'); + // this.assertHTML('
123
'); + // this.assertStableRerender(); + // } + + // @test "A simple block helper can return text"() { + // this.render('{{#identity}}test{{else}}not shown{{/identity}}'); + // this.assertHTML('test'); + // this.assertStableRerender(); + // } + + // @test "A block helper can have an else block"() { + // this.render('{{#render-inverse}}Nope{{else}}
123
{{/render-inverse}}'); + // this.assertHTML('
123
'); + // this.assertStableRerender(); + // } } -module("[Bundle Compiler] Rehydration Tests", Rehydration); +// module("[Bundle Compiler] Rehydration Tests", Rehydration); module("[Bundle Compiler] Initial Render Tests", RenderingTest); diff --git a/packages/@glimmer/interfaces/lib/tier1/symbol-table.d.ts b/packages/@glimmer/interfaces/lib/tier1/symbol-table.d.ts index 593865bc9b..5fe59f9922 100644 --- a/packages/@glimmer/interfaces/lib/tier1/symbol-table.d.ts +++ b/packages/@glimmer/interfaces/lib/tier1/symbol-table.d.ts @@ -1,11 +1,12 @@ import { Option, Dict } from '../core'; import { TemplateMeta } from '@glimmer/wire-format'; +import { Opaque } from "@glimmer/interfaces"; export interface Symbols { } export interface SymbolTable { - meta: TemplateMeta; + meta: Opaque; } export interface ProgramSymbolTable extends SymbolTable { diff --git a/packages/@glimmer/opcode-compiler/index.ts b/packages/@glimmer/opcode-compiler/index.ts index 9955ea788f..254d6d8d08 100644 --- a/packages/@glimmer/opcode-compiler/index.ts +++ b/packages/@glimmer/opcode-compiler/index.ts @@ -11,6 +11,7 @@ export { AbstractTemplate, CompileTimeLookup, LazyOpcodeBuilder, + EagerOpcodeBuilder, OpcodeBuilder, OpcodeBuilderConstructor } from './lib/opcode-builder'; diff --git a/packages/@glimmer/opcode-compiler/lib/compilable-template.ts b/packages/@glimmer/opcode-compiler/lib/compilable-template.ts index e12d20efdf..09666dff79 100644 --- a/packages/@glimmer/opcode-compiler/lib/compilable-template.ts +++ b/packages/@glimmer/opcode-compiler/lib/compilable-template.ts @@ -16,9 +16,9 @@ export default class CompilableTemplate implem static topLevel(block: SerializedTemplateBlock, options: CompileOptions): ICompilableTemplate { return new CompilableTemplate( block.statements, - { block, meta: {} }, + { block, meta: options.referer }, options, - { meta: {}, hasEval: block.hasEval, symbols: block.symbols } + { meta: options.referer, hasEval: block.hasEval, symbols: block.symbols } ); } diff --git a/packages/@glimmer/opcode-compiler/lib/interfaces.ts b/packages/@glimmer/opcode-compiler/lib/interfaces.ts index 469157da0c..12db5b7d27 100644 --- a/packages/@glimmer/opcode-compiler/lib/interfaces.ts +++ b/packages/@glimmer/opcode-compiler/lib/interfaces.ts @@ -75,5 +75,5 @@ export interface ComponentBuilder { export interface ParsedLayout { id?: Option; block: SerializedTemplateBlock; - meta: TemplateMeta; + meta: Opaque; } diff --git a/packages/@glimmer/opcode-compiler/lib/opcode-builder.ts b/packages/@glimmer/opcode-compiler/lib/opcode-builder.ts index 5f2d613ce9..d67edb1140 100644 --- a/packages/@glimmer/opcode-compiler/lib/opcode-builder.ts +++ b/packages/@glimmer/opcode-compiler/lib/opcode-builder.ts @@ -1,4 +1,4 @@ -import { Opaque, Option, ProgramSymbolTable, SymbolTable } from '@glimmer/interfaces'; +import { Opaque, Option, ProgramSymbolTable, SymbolTable, Recast, BlockSymbolTable } from '@glimmer/interfaces'; import { dict, EMPTY_ARRAY, expect, fillNulls, Stack, typePos, unreachable } from '@glimmer/util'; import { Op, Register } from '@glimmer/vm'; import * as WireFormat from '@glimmer/wire-format'; @@ -75,7 +75,7 @@ export interface CompileTimeLookup { export interface OpcodeBuilderConstructor { new(program: CompileTimeProgram, lookup: CompileTimeLookup, - meta: TemplateMeta, + meta: Opaque, macros: Macros, containingLayout: ParsedLayout, asPartial: boolean, @@ -93,7 +93,7 @@ export abstract class OpcodeBuilder, - public meta: TemplateMeta, + public meta: Opaque, public macros: Macros, public containingLayout: ParsedLayout, public asPartial: boolean, @@ -175,7 +175,7 @@ export abstract class OpcodeBuilder extends OpcodeBuilder = AbstractTemplate> extends OpcodeBuilder { + pushBlock(block: Option>): void { + let handle = block ? block.compile() as Recast : null; + this.primitive(handle); + } + resolveBlock(): void { + return; + } + pushLayout(layout: Option): void { + throw new Error("Method not implemented."); + } + resolveLayout(): void { + throw new Error("Method not implemented."); + } + pushSymbolTable(block: Option): void { + throw new Error("Method not implemented."); + } +} diff --git a/packages/@glimmer/opcode-compiler/lib/syntax.ts b/packages/@glimmer/opcode-compiler/lib/syntax.ts index 8624391bfc..73d8810aae 100644 --- a/packages/@glimmer/opcode-compiler/lib/syntax.ts +++ b/packages/@glimmer/opcode-compiler/lib/syntax.ts @@ -797,4 +797,5 @@ export interface TemplateOptions { export interface CompileOptions extends TemplateOptions { asPartial: boolean; + referer: Specifier; } diff --git a/packages/@glimmer/runtime/index.ts b/packages/@glimmer/runtime/index.ts index 897da90979..e57e61e1b6 100644 --- a/packages/@glimmer/runtime/index.ts +++ b/packages/@glimmer/runtime/index.ts @@ -1,6 +1,6 @@ import './lib/bootstrap'; -export { default as templateFactory, ScannableTemplate, TemplateFactory, Template, TemplateIterator, RenderLayoutOptions } from './lib/template'; +export { default as templateFactory, ScannableTemplate, TemplateFactory, Template, TemplateIterator, RenderLayoutOptions, elementBuilder } from './lib/template'; export { NULL_REFERENCE, UNDEFINED_REFERENCE, PrimitiveReference, ConditionalReference } from './lib/references'; @@ -20,7 +20,7 @@ export { TopLevelSyntax } from './lib/syntax/interfaces'; -export { PublicVM as VM, UpdatingVM, RenderResult, IteratorResult } from './lib/vm'; +export { PublicVM as VM, VM as LowLevelVM, UpdatingVM, RenderResult, IteratorResult } from './lib/vm'; export { SimpleDynamicAttribute, diff --git a/packages/@glimmer/runtime/lib/template.ts b/packages/@glimmer/runtime/lib/template.ts index 840b3e8a4f..c51737f7e0 100644 --- a/packages/@glimmer/runtime/lib/template.ts +++ b/packages/@glimmer/runtime/lib/template.ts @@ -166,7 +166,7 @@ export function compilable(layout: ParsedLayout, options: TemplateOptions) { +export function elementBuilder({ mode, env, cursor }: Pick) { switch (mode) { case 'client': return NewElementBuilder.forInitialRender(env, cursor); case 'rehydrate': return RehydrateBuilder.forInitialRender(env, cursor); diff --git a/packages/@glimmer/runtime/lib/vm/render-result.ts b/packages/@glimmer/runtime/lib/vm/render-result.ts index 67f33c98fd..e19d3106ca 100644 --- a/packages/@glimmer/runtime/lib/vm/render-result.ts +++ b/packages/@glimmer/runtime/lib/vm/render-result.ts @@ -8,7 +8,7 @@ import { Program } from "@glimmer/program"; export default class RenderResult implements DestroyableBounds, ExceptionHandler { constructor( - private env: Environment, + public env: Environment, private program: Program, private updating: LinkedList, private bounds: DestroyableBounds diff --git a/packages/@glimmer/runtime/test/initial-render-test.ts b/packages/@glimmer/runtime/test/initial-render-test.ts index e19940e147..6443ea1ea6 100644 --- a/packages/@glimmer/runtime/test/initial-render-test.ts +++ b/packages/@glimmer/runtime/test/initial-render-test.ts @@ -20,6 +20,7 @@ import * as SimpleDOM from "simple-dom"; import { NodeDOMTreeConstruction } from "@glimmer/node"; import { expect } from "@glimmer/util"; import { UpdatableReference } from "@glimmer/object-reference"; +import { UserHelper } from "@glimmer/test-helpers/lib/environment"; const XHTML_NAMESPACE = 'http://www.w3.org/1999/xhtml'; @@ -1084,37 +1085,49 @@ class Rehydration extends RenderingTest { public serialized: string; public doc: any; - setupServer(template: string = this.template) { - let doc = this.doc = new SimpleDOM.Document(); - let env = new TestEnvironment({ + protected env: never; + protected clientEnv: TestEnvironment; + protected serverEnv: TestEnvironment; + + constructor() { + super(); + this.clientEnv = new TestEnvironment(); + + let doc = new SimpleDOM.Document(); + + this.serverEnv = new TestEnvironment({ document: doc, appendOperations: new NodeDOMTreeConstruction(doc) }); + } - this.setup({ template, env }); + registerHelper(name: string, helper: UserHelper): void { + this.clientEnv.registerHelper(name, helper); + this.serverEnv.registerHelper(name, helper); + } + + setupServer(template: string = this.template) { + this.setup({ template }); } setupClient(template: string = this.template) { - let env = new TestEnvironment(); - this.doc = document; - let div = this.doc.createElement("div"); + let div = document.createElement("div"); expect(this.serialized, "Should have serialized HTML from `this.renderServerSide()`"); div.innerHTML = this.serialized; this.element = div; - this.setup({ template, env }); + this.setup({ template }); } - setup({ template, context, env }: { template: string; context?: Dict; env?: TestEnvironment }) { - if (env) this.env = env; + setup({ template, context }: { template: string; context?: Dict; env?: TestEnvironment }) { this.template = template; if (context) this.setProperties(context); } assertServerOutput(..._expected: Content[]) { let serialized = this.serialize(); - equalTokens(serialized, content(['
', OPEN, ..._expected, CLOSE, '
'])); + equalTokens(serialized, content([OPEN, ..._expected, CLOSE])); this.serialized = serialized; } @@ -1123,27 +1136,25 @@ class Rehydration extends RenderingTest { this.context = context; } this.setupServer(); - this.populateHelpers(); - this.element = this.doc.createElement("main") as HTMLDivElement; + let env = this.serverEnv; + this.element = env.getAppendOperations().createElement("div") as HTMLDivElement; let template = expect(this.template, "Must set up a template before calling renderServerSide"); // Emulate server-side render renderTemplate(template, { - env: this.env, + env, self: new UpdatableReference(this.context), cursor: { element: this.element, nextSibling: null }, dynamicScope: new TestDynamicScope(), mode: "serialize" }); - this.doc.appendChild(this.element); - this.takeSnapshot(); this.serialized = this.serialize(); } serialize() { let serializer = new SimpleDOM.HTMLSerializer(SimpleDOM.voidMap); - let serialized = serializer.serializeChildren(this.doc); + let serialized = serializer.serializeChildren(this.element); return serialized; } @@ -1152,12 +1163,13 @@ class Rehydration extends RenderingTest { this.context = context; } this.setupClient(); - this.populateHelpers(); - this.element = this.doc.createElement("div") as HTMLDivElement; + // this.populateHelpers(); + let env = this.clientEnv; + this.element = env.getAppendOperations().createElement("div") as HTMLDivElement; let template = expect(this.template, "Must set up a template before calling renderClientSide"); // Client-side rehydration this.renderResult = renderTemplate(template, { - env: this.env, + env, self: new UpdatableReference(this.context), cursor: { element: this.element, nextSibling: null }, dynamicScope: new TestDynamicScope(), @@ -1225,18 +1237,20 @@ class Rehydration extends RenderingTest { @test "Node curlies"() { this.setupServer('
{{node}}
'); - let node = this.env.getAppendOperations().createTextNode('hello'); + let env = this.serverEnv; + let node = env.getAppendOperations().createTextNode('hello'); this.renderServerSide({ node }); this.assertServerOutput('
hello
'); this.setupClient(); - let clientNode = this.env.getDOM().createTextNode('hello'); + env = this.clientEnv; + let clientNode = env.getDOM().createTextNode('hello'); this.renderClientSide({ node: clientNode }); this.assertHTML('
hello
'); this.assertStableRerender(); - let clientNode2 = this.env.getDOM().createTextNode('goodbye'); + let clientNode2 = env.getDOM().createTextNode('goodbye'); this.rerender({ node: clientNode2 }); this.assertHTML('
goodbye
'); this.assertStableNodes({ except: clientNode as Text }); diff --git a/packages/@glimmer/runtime/test/template-test.ts b/packages/@glimmer/runtime/test/template-test.ts index a5940c40e3..302508eca6 100644 --- a/packages/@glimmer/runtime/test/template-test.ts +++ b/packages/@glimmer/runtime/test/template-test.ts @@ -1,5 +1,6 @@ import { TestEnvironment } from "@glimmer/test-helpers"; import { templateFactory } from "@glimmer/runtime"; +import { precompile } from "@glimmer/compiler"; import { SerializedTemplateWithLazyBlock, TemplateMeta } from "@glimmer/wire-format"; let env: TestEnvironment; @@ -20,32 +21,17 @@ let serializedTemplateNoId: SerializedTemplateWithLazyBlock; QUnit.module("templateFactory", { beforeEach() { env = new TestEnvironment(); - - let templates = new Templates(); - - templates.compile("
{{name}}
", { + let templateJs = precompile("
{{name}}
", { meta: { version: 12, lang: 'es', moduleName: "template/module/name" - }, - - plugins: [], - lookup: "template/module/name" + } }); + serializedTemplate = JSON.parse(templateJs); + serializedTemplate.id = 'server-id-1'; - let templateJs: string; - - templates.eachTemplate(template => { - templateJs = template; - }); - - // in the browser, require('crypto') will fail, and all we're testing - // here is that the factory scrapes the id off correctly. - serializedTemplate = JSON.parse(templateJs!); - serializedTemplate.id = "server-id-1"; - - serializedTemplateNoId = JSON.parse(templateJs!); + serializedTemplateNoId = JSON.parse(templateJs); serializedTemplateNoId.id = null; } }); diff --git a/packages/@glimmer/test-helpers/index.ts b/packages/@glimmer/test-helpers/index.ts index a459497ed4..9ed8f1b66b 100644 --- a/packages/@glimmer/test-helpers/index.ts +++ b/packages/@glimmer/test-helpers/index.ts @@ -26,6 +26,7 @@ export { TestDynamicScope, TestSpecifier, LookupType, + UserHelper, equalsElement, inspectHooks, regex, diff --git a/packages/@glimmer/test-helpers/lib/abstract-test-case.ts b/packages/@glimmer/test-helpers/lib/abstract-test-case.ts index 5d5fffb73c..25019975a4 100644 --- a/packages/@glimmer/test-helpers/lib/abstract-test-case.ts +++ b/packages/@glimmer/test-helpers/lib/abstract-test-case.ts @@ -1,5 +1,5 @@ import { PathReference, Tagged, TagWrapper, RevisionTag, DirtyableTag, Tag } from "@glimmer/reference"; -import { RenderResult, RenderLayoutOptions } from "@glimmer/runtime"; +import { RenderResult, RenderLayoutOptions, TemplateIterator, Environment } from "@glimmer/runtime"; import { Opaque, Dict, dict, expect } from "@glimmer/util"; import { NodeDOMTreeConstruction } from "@glimmer/node"; import { Option } from "@glimmer/interfaces"; @@ -111,62 +111,14 @@ export abstract class AbstractRenderTest { protected assert = QUnit.assert; protected context: Dict = dict(); protected renderResult: Option = null; + protected helpers = dict(); private snapshot: NodesSnapshot = []; - private helpers = {}; public testType: ComponentKind; public template: string; - constructor(protected env = new TestEnvironment()) {} - - reset() { - this.element.innerHTML = ''; - this.env = new TestEnvironment(); - } - - registerHelper(name: string, helper: UserHelper) { - this.helpers[name] = helper; - } - - registerComponent( - type: ComponentKind = this.testType, - name: string, - layout: string - ) { - switch (type) { - case 'Glimmer': - this.env.registerEmberishGlimmerComponent( - name, - EmberishGlimmerComponent, - layout - ); - break; - case 'Curly': - this.env.registerEmberishCurlyComponent( - name, - EmberishCurlyComponent, - layout - ); - break; - - case 'Dynamic': - this.env.registerEmberishCurlyComponent( - name, - EmberishCurlyComponent, - layout - ); - break; - case "Basic": - case "Fragment": - this.env.registerBasicComponent(name, BasicComponent, layout); - break; - } - } - - populateHelpers() { - Object.keys(this.helpers).forEach(name => - this.env.registerHelper(name, this.helpers[name]) - ); - } + abstract registerComponent(type: ComponentKind, name: string, layout: string): void; + abstract registerHelper(name: string, helper: UserHelper): void; + // abstract populateHelpers(): void; buildComponent(blueprint: ComponentBlueprint): string { let invocation = ''; @@ -436,12 +388,11 @@ export abstract class AbstractRenderTest { rerender(properties: Dict = {}): void { this.setProperties(properties); - this.env.begin(); - expect( - this.renderResult, - 'the test should call render() before rerender()' - ).rerender(); - this.env.commit(); + let result = expect(this.renderResult, "the test should call render() before rerender()"); + + result.env.begin(); + result.rerender(); + result.env.commit(); } protected set(key: string, value: Opaque): void { @@ -530,16 +481,44 @@ export abstract class AbstractRenderTest { } } -export class RenderTests extends AbstractRenderTest { +export abstract class AbstractTestEnvironmentRenderTest extends AbstractRenderTest { + protected env: TestEnvironment; + + registerComponent(type: ComponentKind = this.testType, name: string, layout: string) { + switch (type) { + case "Glimmer": + this.env.registerEmberishGlimmerComponent(name, EmberishGlimmerComponent, layout); + break; + case "Curly": + this.env.registerEmberishCurlyComponent(name, EmberishCurlyComponent, layout); + break; + + case "Dynamic": + this.env.registerEmberishCurlyComponent(name, EmberishCurlyComponent, layout); + break; + case "Basic": + case "Fragment": + this.env.registerBasicComponent(name, BasicComponent, layout); + break; + } + } + + registerHelper(name: string, helper: UserHelper): void { + this.env.registerHelper(name, helper); + } +} + +export class RenderTests extends AbstractTestEnvironmentRenderTest { protected element: HTMLDivElement; - constructor(env: TestEnvironment) { - super(env); - this.element = this.env - .getAppendOperations() - .createElement('div') as HTMLDivElement; + + protected env: TestEnvironment = new TestEnvironment(); + + constructor() { + super(); + this.element = this.env.getAppendOperations().createElement("div") as HTMLDivElement; } + renderTemplate(template: string): RenderResult { - this.populateHelpers(); let { env } = this; return renderTemplate(template, { env, @@ -552,18 +531,29 @@ export class RenderTests extends AbstractRenderTest { export class RehydrationTests extends RenderTests { serialized: string; - setupServer(template: string = this.template) { + + protected env: never; + protected clientEnv: TestEnvironment; + protected serverEnv: TestEnvironment; + + constructor() { + super(); + this.clientEnv = new TestEnvironment(); + let doc = new SimpleDOM.Document(); - let env = new TestEnvironment({ + + this.serverEnv = new TestEnvironment({ document: doc, appendOperations: new NodeDOMTreeConstruction(doc) }); - this.setup({ template, env }); + } + + setupServer(template: string = this.template) { + this.setup({ template }); } setupClient(template: string = this.template) { - let env = new TestEnvironment(); - let div = document.createElement('div'); + let div = document.createElement("div"); expect( this.serialized, @@ -572,19 +562,10 @@ export class RehydrationTests extends RenderTests { div.innerHTML = this.serialized; this.element = div; - this.setup({ template, env }); - } - - setup({ - template, - context, - env - }: { - template: string; - context?: Dict; - env?: TestEnvironment; - }) { - if (env) this.env = env; + this.setup({ template }); + } + + setup({ template, context }: { template: string; context?: Dict; env?: TestEnvironment }) { this.template = template; if (context) this.setProperties(context); } @@ -600,8 +581,7 @@ export class RehydrationTests extends RenderTests { this.context = context; } this.setupServer(); - this.populateHelpers(); - let { env } = this; + let env = this.serverEnv; this.element = env.getAppendOperations().createElement("div") as HTMLDivElement; let template = expect(this.template, "Must set up a template before calling renderServerSide"); // Emulate server-side render @@ -628,8 +608,8 @@ export class RehydrationTests extends RenderTests { this.context = context; } this.setupClient(); - this.populateHelpers(); - let { env } = this; + // this.populateHelpers(); + let env = this.clientEnv; this.element = env.getAppendOperations().createElement("div") as HTMLDivElement; let template = expect(this.template, "Must set up a template before calling renderClientSide"); // Client-side rehydration @@ -921,14 +901,18 @@ function isTestFunction( export function renderTemplate(template: string, options: RenderLayoutOptions & { env: TestEnvironment }) { let { env } = options; - env.begin(); - let templateIterator = env.compile(template).renderLayout(options); + let iterator = env.compile(template).renderLayout(options); + return renderSync(env, iterator); +} + +export function renderSync(env: Environment, iterator: TemplateIterator) { + env.begin(); let iteratorResult: IteratorResult; do { - iteratorResult = templateIterator.next() as IteratorResult; + iteratorResult = iterator.next() as IteratorResult; } while (!iteratorResult.done); let result = iteratorResult.value; diff --git a/packages/@glimmer/test-helpers/lib/environment.ts b/packages/@glimmer/test-helpers/lib/environment.ts index b81847d13c..c5337342f1 100644 --- a/packages/@glimmer/test-helpers/lib/environment.ts +++ b/packages/@glimmer/test-helpers/lib/environment.ts @@ -912,72 +912,81 @@ class TestMacros extends Macros { } } -export class TestEnvironment extends Environment { - public resolver = new TestResolver(); - private program = new Program(new LazyConstants(this.resolver)); - private uselessAnchor: HTMLAnchorElement; +export abstract class AbstractTestEnvironment extends Environment { public compiledLayouts = dict(); - private lookup: LookupResolver; - public compileOptions: TemplateOptions = { - lookup: new LookupResolver(this.resolver), - program: this.program, - macros: new TestMacros(), - Builder: LazyOpcodeBuilder - }; + protected abstract program: Program; + protected abstract resolver: Resolver; - constructor(options: TestEnvironmentOptions = {}) { - // let document = options.document || window.document; - // let appendOperations = options.appendOperations || new DOMTreeConstruction(document); + constructor(options: TestEnvironmentOptions) { super({ appendOperations: options.appendOperations || new DOMTreeConstruction(options.document as Document || window.document), updateOperations: new DOMChanges((options.document || window.document) as Document) }); - - // recursive field, so "unsafely" set one half late (but before the resolver is actually used) - this.resolver['options'] = this.compileOptions; - this.lookup = new LookupResolver(this.resolver); - let document = options.document || window.document; - - this.uselessAnchor = document.createElement('a') as HTMLAnchorElement; - this.registerHelper("if", ([cond, yes, no]) => cond ? yes : no); - this.registerHelper("unless", ([cond, yes, no]) => cond ? no : yes); - this.registerInternalHelper("-get-dynamic-var", getDynamicVar); - this.registerModifier("action", new InertModifierManager()); - - this.registerInternalHelper("hash", (_vm: VM, args: Arguments) => args.capture().named); } protocolForURL(url: string): string { - this.uselessAnchor.href = url; - return this.uselessAnchor.protocol; - } + if (typeof window === 'undefined') { + throw new Error('Must implement protocolForURL outside of the browser'); + } - registerHelper(name: string, helper: UserHelper): GlimmerHelper { - let glimmerHelper = (_vm: VM, args: Arguments) => new HelperReference(helper, args); - this.resolver.register('helper', name, glimmerHelper); - return glimmerHelper; + let anchor = window.document.createElement('a'); + anchor.href = url; + return anchor.protocol; } - registerInternalHelper(name: string, helper: GlimmerHelper): GlimmerHelper { - this.resolver.register('helper', name, helper); - return helper; - } + toConditionalReference(reference: Reference): Reference { + if (isConst(reference)) { + return PrimitiveReference.create(emberToBool(reference.value())); + } - registerModifier(name: string, modifier: ModifierManager): ModifierManager { - this.resolver.register('modifier', name, modifier); - return modifier; + return new EmberishConditionalReference(reference); } - registerPartial(name: string, source: string): PartialDefinition { - let definition = new PartialDefinition(name, this.compile(source, null)); - this.resolver.register('partial', name, definition); - return definition; + iterableFor(ref: Reference, keyPath: string): OpaqueIterable { + let keyFor: KeyFor; + + if (!keyPath) { + throw new Error('Must specify a key for #each'); + } + + switch (keyPath) { + case '@index': + keyFor = (_, index: number) => String(index); + break; + case '@primitive': + keyFor = (item: Opaque) => String(item); + break; + default: + keyFor = (item: Opaque) => item && item[keyPath]; + break; + } + + return new Iterable(ref, keyFor); } +} - private registerComponent(name: string, definition: GenericComponentDefinition) { - this.resolver.register('component', name, { definition, manager: definition.manager }); - return definition; +export class TestEnvironment extends AbstractTestEnvironment { + public resolver = new TestResolver(); + protected program = new Program(new LazyConstants(this.resolver)); + + public compileOptions: TemplateOptions = { + lookup: new LookupResolver(this.resolver), + program: this.program, + macros: new TestMacros(), + Builder: LazyOpcodeBuilder + }; + + constructor(options: TestEnvironmentOptions = {}) { + super(options); + // recursive field, so "unsafely" set one half late (but before the resolver is actually used) + this.resolver['options'] = this.compileOptions; + this.registerHelper("if", ([cond, yes, no]) => cond ? yes : no); + this.registerHelper("unless", ([cond, yes, no]) => cond ? no : yes); + this.registerInternalHelper("-get-dynamic-var", getDynamicVar); + this.registerModifier("action", new InertModifierManager()); + + this.registerInternalHelper("hash", (_vm: VM, args: Arguments) => args.capture().named); } registerTemplate(name: string, source: string): { name: string, handle: number } { @@ -1024,12 +1033,26 @@ export class TestEnvironment extends Environment { this.registerComponent(name, definition); } - toConditionalReference(reference: Reference): Reference { - if (isConst(reference)) { - return PrimitiveReference.create(emberToBool(reference.value())); - } + registerHelper(name: string, helper: UserHelper): GlimmerHelper { + let glimmerHelper = (_vm: VM, args: Arguments) => new HelperReference(helper, args); + this.resolver.register('helper', name, glimmerHelper); + return glimmerHelper; + } - return new EmberishConditionalReference(reference); + registerInternalHelper(name: string, helper: GlimmerHelper): GlimmerHelper { + this.resolver.register('helper', name, helper); + return helper; + } + + registerModifier(name: string, modifier: ModifierManager): ModifierManager { + this.resolver.register('modifier', name, modifier); + return modifier; + } + + registerPartial(name: string, source: string): PartialDefinition { + let definition = new PartialDefinition(name, this.compile(source, null)); + this.resolver.register('partial', name, definition); + return definition; } resolveHelper(helperName: string): Option { @@ -1056,32 +1079,15 @@ export class TestEnvironment extends Environment { return handle === null ? null : this.resolver.resolve(handle); } - compile(template: string, meta: TemplateMeta): Template { + compile(template: string, meta?: TemplateMeta): Template { let wrapper = JSON.parse(precompile(template)); let factory = templateFactory(wrapper); - return factory.create(this.compileOptions, meta); + return factory.create(this.compileOptions, (meta || {}) as any as TemplateMeta); } - iterableFor(ref: Reference, keyPath: string): OpaqueIterable { - let keyFor: KeyFor; - - if (!keyPath) { - throw new Error('Must specify a key for #each'); - } - - switch (keyPath) { - case '@index': - keyFor = (_, index: number) => String(index); - break; - case '@primitive': - keyFor = (item: Opaque) => String(item); - break; - default: - keyFor = (item: Opaque) => item && item[keyPath]; - break; - } - - return new Iterable(ref, keyFor); + private registerComponent(name: string, definition: GenericComponentDefinition) { + this.resolver.register('component', name, { definition, manager: definition.manager }); + return definition; } } diff --git a/packages/@glimmer/test-helpers/test/i-n-u-r-test.ts b/packages/@glimmer/test-helpers/test/i-n-u-r-test.ts index e6dbf92bd7..436e1ba311 100644 --- a/packages/@glimmer/test-helpers/test/i-n-u-r-test.ts +++ b/packages/@glimmer/test-helpers/test/i-n-u-r-test.ts @@ -1,11 +1,11 @@ -import { RenderTests, TestEnvironment } from ".."; +import { RenderTests } from "../lib/abstract-test-case"; QUnit.module("Render Tests: I-N-U-R"); QUnit.test("Can set properties", assert => { new class extends RenderTests { constructor() { - super(new TestEnvironment()); + super(); this.setProperties({ foo: "bar" }); assert.equal(this.context.foo, "bar"); } @@ -20,7 +20,7 @@ QUnit.test("Can take basic snapshots", assert => { new class extends RenderTests { element = div; constructor() { - super(new TestEnvironment()); + super(); let snapShot = this.takeSnapshot(); assert.deepEqual(snapShot, [text, "up"]); } @@ -37,7 +37,7 @@ QUnit.test("Can take nested snapshots", assert => { new class extends RenderTests { element = div; constructor() { - super(new TestEnvironment()); + super(); let snapShot = this.takeSnapshot(); assert.deepEqual(snapShot, [p, "down", text, "up", "up"]); } @@ -56,7 +56,7 @@ QUnit.test("Can take nested snapshots of serialized blocks", assert => { new class extends RenderTests { element = div; constructor() { - super(new TestEnvironment()); + super(); let snapShot = this.takeSnapshot(); assert.deepEqual(snapShot, [open, text, close, "up"]); } diff --git a/packages/@glimmer/test-helpers/test/invocation-generation-test.ts b/packages/@glimmer/test-helpers/test/invocation-generation-test.ts index 506d7b1bd0..d5d10b81a0 100644 --- a/packages/@glimmer/test-helpers/test/invocation-generation-test.ts +++ b/packages/@glimmer/test-helpers/test/invocation-generation-test.ts @@ -1,9 +1,9 @@ -import { RenderTests, TestEnvironment } from ".."; +import { RenderTests } from "../lib/abstract-test-case"; let renderTests: RenderTests; QUnit.module("Render Tests: buildComponent", { beforeEach() { - renderTests = new RenderTests(new TestEnvironment()); + renderTests = new RenderTests(); } }); From 94d67ce34d78ab9b468483e908b1453abc359b00 Mon Sep 17 00:00:00 2001 From: Yehuda Katz Date: Mon, 14 Aug 2017 21:27:59 -0700 Subject: [PATCH 13/42] Tests pass There's still a lot of cruft, missing features, and type errors, but the first blob of tests are passing in bundle compilation mode! --- .../@glimmer/bundle-compiler/lib/templates.ts | 42 +- .../test/initial-render-test.ts | 514 +++++++++--------- .../opcode-compiler/lib/opcode-builder.ts | 16 +- .../@glimmer/opcode-compiler/lib/syntax.ts | 9 +- packages/@glimmer/program/lib/constants.ts | 89 ++- packages/@glimmer/program/lib/program.ts | 21 +- packages/@glimmer/runtime/lib/vm/append.ts | 4 +- .../@glimmer/runtime/lib/vm/render-result.ts | 4 +- packages/@glimmer/runtime/lib/vm/update.ts | 2 +- packages/@glimmer/test-helpers/index.ts | 2 + .../@glimmer/test-helpers/lib/environment.ts | 12 +- 11 files changed, 435 insertions(+), 280 deletions(-) diff --git a/packages/@glimmer/bundle-compiler/lib/templates.ts b/packages/@glimmer/bundle-compiler/lib/templates.ts index 4614e97d4f..a834b5213e 100644 --- a/packages/@glimmer/bundle-compiler/lib/templates.ts +++ b/packages/@glimmer/bundle-compiler/lib/templates.ts @@ -17,9 +17,10 @@ export interface Specifier { } export class SpecifierMap { - public helpers = new Map(); - public modifiers = new Map(); - public components = new Map(); + public bySpecifier = new Map(); + public byHandle = new Map(); + + public byTemplateHandle = new Map(); } export class BundleCompiler { @@ -43,7 +44,7 @@ export class BundleCompiler { let block = template.toJSON(); let { program, macros, Builder } = this; - let lookup = new BundlingLookup(this.delegate); + let lookup = new BundlingLookup(this.delegate, this.specifiers); let options: CompileOptions = { program, @@ -58,7 +59,7 @@ export class BundleCompiler { let handle = compilable.compile(); - this.specifiers.components.set(handle as Recast, specifier); + this.specifiers.byTemplateHandle.set(handle as Recast, specifier); return { handle, symbolTable: compilable.symbolTable }; } @@ -99,8 +100,22 @@ export interface CompilerDelegate { resolvePartialSpecifier(partialName: string, referer: Specifier): Specifier; } -class BundlingLookup implements CompileTimeLookup { - constructor(private delegate: CompilerDelegate) {} +class BundlingLookup implements CompileTimeLookup { + constructor(private delegate: CompilerDelegate, private map: SpecifierMap) { } + + private registerSpecifier(specifier: Specifier): number { + let { bySpecifier, byHandle } = this.map; + + let handle = bySpecifier.get(specifier); + + if (handle === undefined) { + handle = byHandle.size; + byHandle.set(handle, specifier); + bySpecifier.set(specifier, handle); + } + + return handle; + } getCapabilities(meta: Specifier): ComponentCapabilities { return this.delegate.getComponentCapabilities(meta); @@ -110,14 +125,23 @@ class BundlingLookup implements CompileTimeLookup { throw new Error("Method not implemented."); } - lookupHelper(name: string, referer: Specifier): Option { + lookupHelper(name: string, referer: Specifier): Option { if (this.delegate.hasHelperInScope(name, referer)) { - return this.delegate.resolveHelperSpecifier(name, referer); + let specifier = this.delegate.resolveHelperSpecifier(name, referer); + return this.registerSpecifier(specifier); } else { return null; } } + lookupComponentSpec(name: string, referer: Specifier): Option { + if (this.delegate.hasComponentInScope(name, referer)) { + let specifier = this.delegate.resolveHelperSpecifier(name, referer); + return this.registerSpecifier(specifier); + } else { + return null; + } } + lookupModifier(name: string, referer: Specifier): Option { if (this.delegate.hasModifierInScope(name, referer)) { return this.delegate.resolveModifierSpecifier(name, referer); diff --git a/packages/@glimmer/bundle-compiler/test/initial-render-test.ts b/packages/@glimmer/bundle-compiler/test/initial-render-test.ts index 9685e1deb9..654ffb63ec 100644 --- a/packages/@glimmer/bundle-compiler/test/initial-render-test.ts +++ b/packages/@glimmer/bundle-compiler/test/initial-render-test.ts @@ -5,13 +5,16 @@ import { AbstractRenderTest, TestDynamicScope, strip, - UserHelper + UserHelper, + HelperReference, + assertNodeTagName, + TestMacros } from "@glimmer/test-helpers"; import { AbstractTestEnvironment } from "@glimmer/test-helpers/lib/environment"; -import { BundleCompiler, CompilerDelegate, Specifier } from "@glimmer/bundle-compiler"; -import { Macros, EagerOpcodeBuilder, CompileTimeLookup } from "@glimmer/opcode-compiler"; -import { Program, Constants } from "@glimmer/program"; -import { elementBuilder, LowLevelVM, TemplateIterator, RenderResult } from "@glimmer/runtime"; +import { BundleCompiler, CompilerDelegate, Specifier, SpecifierMap } from "@glimmer/bundle-compiler"; +import { Macros, EagerOpcodeBuilder } from "@glimmer/opcode-compiler"; +import { Program, RuntimeProgram, WriteOnlyProgram, RuntimeConstants } from "@glimmer/program"; +import { elementBuilder, LowLevelVM, TemplateIterator, RenderResult, Helper, SVG_NAMESPACE } from "@glimmer/runtime"; import { UpdatableReference } from "@glimmer/object-reference"; import { renderSync } from "@glimmer/test-helpers/lib/abstract-test-case"; import { dict } from "@glimmer/util"; @@ -43,7 +46,9 @@ class BundledClientEnvironment extends AbstractTestEnvironment { } } -class RuntimeResolver implements Resolver { +class RuntimeResolver implements Resolver { + constructor(private map: SpecifierMap, private modules: Modules) {} + lookupHelper(name: string, meta: Opaque): Option { throw new Error("Method not implemented."); } @@ -57,48 +62,54 @@ class RuntimeResolver implements Resolver { throw new Error("Method not implemented."); } resolve(specifier: number): U { - throw new Error("Method not implemented."); + let module = this.map.byHandle.get(specifier)!; + return this.modules.get(module.module).get('default') as U; } } -class BundlingLookup implements CompileTimeLookup { - public registry = dict>(); - - getCapabilities(handle: number): ComponentCapabilities { - throw new Error("Method not implemented."); - } - getLayout(handle: number): Option> { - throw new Error("Method not implemented."); +class Module { + constructor(private dict: Dict) { + Object.freeze(this.dict); } - lookupHelper(name: string, referer: Specifier): Option { - if (referer.module === 'main') { - let module = this.registry[name]; - } - throw new Error("Method not implemented."); + has(key: string) { + return key in this.dict; } - lookupModifier(name: string, referer: Specifier): Option { - throw new Error("Method not implemented."); - } - lookupComponentSpec(name: string, referer: Specifier): Option { - throw new Error("Method not implemented."); + get(key: string): Opaque { + return this.dict[key]; } - lookupPartial(name: string, referer: Specifier): Option { - throw new Error("Method not implemented."); +} + +class Modules { + private registry = dict(); + + has(name: string): boolean { + return name in this.registry; } - registerModule(name: string, value: Opaque): void { - let module = this.registry[name]; + get(name: string): Module { + return this.registry[name]; + } - if (!module) module = this.registry[name] = dict(); + register(name: string, value: Dict) { + this.registry[name] = new Module(value); + } - module.default = value; + resolve(name: string, referer: Specifier): Option { + let local = referer.module.replace(/^((.*)\/)?([^\/]*)$/, `$1${name}`); + if (this.registry[local]) { + return local; + } else if (this.registry[name]) { + return name; + } else { + return null; + } } } class BundlingDelegate implements CompilerDelegate { - constructor(private lookup: BundlingLookup) {} + constructor(private modules: Modules) {} hasComponentInScope(componentName: string, referrer: Specifier): boolean { return false; @@ -109,12 +120,16 @@ class BundlingDelegate implements CompilerDelegate { getComponentCapabilities(specifier: Specifier): ComponentCapabilities { throw new Error("Method not implemented."); } + hasHelperInScope(helperName: string, referer: Specifier): boolean { - return !!this.lookup.lookupHelper(name, referer); + return this.modules.resolve(helperName, referer) !== null; } + resolveHelperSpecifier(helperName: string, referer: Specifier): Specifier { - throw new Error("Method not implemented."); + let path = this.modules.resolve(helperName, referer); + return { module: path!, name: 'default' }; } + hasModifierInScope(modifierName: string, referer: Specifier): boolean { return false; } @@ -131,7 +146,7 @@ class BundlingDelegate implements CompilerDelegate { class RenderingTest extends AbstractRenderTest { protected element: HTMLElement; - protected lookup = new BundlingLookup(); + protected modules = new Modules(); constructor() { super(); @@ -139,7 +154,9 @@ class RenderingTest extends AbstractRenderTest { } registerHelper(name: string, helper: UserHelper): void { - this.lookup.registerModule(name, helper); + let glimmerHelper: Helper = (_vm, args) => new HelperReference(helper, args); + + this.modules.register(name, { default: glimmerHelper }); } registerComponent(type: "Glimmer" | "Curly" | "Dynamic" | "Basic" | "Fragment", name: string, layout: string): void { @@ -151,12 +168,11 @@ class RenderingTest extends AbstractRenderTest { } protected renderTemplate(template: string): RenderResult { - let macros = new Macros(); - let resolver = new RuntimeResolver(); - let program = new Program(new Constants(resolver)); - let delegate = new BundlingDelegate(this.lookup); - + let macros = new TestMacros(); + let delegate = new BundlingDelegate(this.modules); + let program = new WriteOnlyProgram(); let compiler = new BundleCompiler(macros, EagerOpcodeBuilder, delegate, program); + let { symbolTable, handle } = compiler.compile(template, { module: 'main', name: 'default' }); let { env } = this; @@ -166,7 +182,11 @@ class RenderingTest extends AbstractRenderTest { let builder = elementBuilder({ mode: 'client', env, cursor }); let self = new UpdatableReference(this.context); let dynamicScope = new TestDynamicScope(); - let vm = LowLevelVM.initial(program, env, self, null, dynamicScope, builder, symbolTable, handle); + let resolver = new RuntimeResolver(compiler.getSpecifierMap(), this.modules); + let pool = program.constants.toPool(); + let runtimeProgram = new RuntimeProgram(new RuntimeConstants(resolver, pool), program.heap); + + let vm = LowLevelVM.initial(runtimeProgram, env, self, null, dynamicScope, builder, symbolTable, handle); let iterator = new TemplateIterator(vm); return renderSync(env, iterator); @@ -997,211 +1017,211 @@ class RenderingTest extends AbstractRenderTest { this.assertHTML('
tomdale - Thomas Dalewycats - Yehuda Katz
'); } - // @test "Simple helpers"() { - // this.registerHelper('testing', ([id]) => id); - // this.render('
{{testing title}}
', { title: 'hello' }); - // this.assertHTML('
hello
'); - // this.assertStableRerender(); - // } - - // @test "GH#13999 The compiler can handle simple helpers with inline null parameter"() { - // let value; - // this.registerHelper('say-hello', function (params) { - // value = params[0]; - // return 'hello'; - // }); - // this.render('
{{say-hello null}}
'); - // this.assertHTML('
hello
'); - // this.assert.strictEqual(value, null, 'is null'); - // this.assertStableRerender(); - // } - - // @test "GH#13999 The compiler can handle simple helpers with inline string literal null parameter"() { - // let value; - // this.registerHelper('say-hello', function (params) { - // value = params[0]; - // return 'hello'; - // }); - - // this.render('
{{say-hello "null"}}
'); - // this.assertHTML('
hello
'); - // this.assert.strictEqual(value, 'null', 'is null string literal'); - // this.assertStableRerender(); - // } - - // @test "GH#13999 The compiler can handle simple helpers with inline undefined parameter"() { - // let value: Opaque = 'PLACEHOLDER'; - // let length; - // this.registerHelper('say-hello', function (params) { - // length = params.length; - // value = params[0]; - // return 'hello'; - // }); - - // this.render('
{{say-hello undefined}}
'); - // this.assertHTML('
hello
'); - // this.assert.strictEqual(length, 1); - // this.assert.strictEqual(value, undefined, 'is undefined'); - // this.assertStableRerender(); - // } - - // @test "GH#13999 The compiler can handle simple helpers with positional parameter undefined string literal"() { - // let value: Opaque = 'PLACEHOLDER'; - // let length; - // this.registerHelper('say-hello', function (params) { - // length = params.length; - // value = params[0]; - // return 'hello'; - // }); - - // this.render('
{{say-hello "undefined"}} undefined
'); - // this.assertHTML('
hello undefined
'); - // this.assert.strictEqual(length, 1); - // this.assert.strictEqual(value, 'undefined', 'is undefined string literal'); - // this.assertStableRerender(); - // } - - // @test "GH#13999 The compiler can handle components with undefined named arguments"() { - // let value: Opaque = 'PLACEHOLDER'; - // this.registerHelper('say-hello', function (_, hash) { - // value = hash['foo']; - // return 'hello'; - // }); - - // this.render('
{{say-hello foo=undefined}}
'); - // this.assertHTML('
hello
'); - // this.assert.strictEqual(value, undefined, 'is undefined'); - // this.assertStableRerender(); - // } - - // @test "GH#13999 The compiler can handle components with undefined string literal named arguments"() { - // let value: Opaque = 'PLACEHOLDER'; - // this.registerHelper('say-hello', function (_, hash) { - // value = hash['foo']; - // return 'hello'; - // }); - - // this.render('
{{say-hello foo="undefined"}}
'); - // this.assertHTML('
hello
'); - // this.assert.strictEqual(value, 'undefined', 'is undefined string literal'); - // this.assertStableRerender(); - // } - - // @test "GH#13999 The compiler can handle components with null named arguments"() { - // let value; - // this.registerHelper('say-hello', function (_, hash) { - // value = hash['foo']; - // return 'hello'; - // }); - - // this.render('
{{say-hello foo=null}}
'); - // this.assertHTML('
hello
'); - // this.assert.strictEqual(value, null, 'is null'); - // this.assertStableRerender(); - // } - - // @test "GH#13999 The compiler can handle components with null string literal named arguments"() { - // let value; - // this.registerHelper('say-hello', function (_, hash) { - // value = hash['foo']; - // return 'hello'; - // }); - - // this.render('
{{say-hello foo="null"}}
'); - // this.assertHTML('
hello
'); - // this.assert.strictEqual(value, 'null', 'is null string literal'); - // this.assertStableRerender(); - // } - - // @test "Null curly in attributes"() { - // this.render('
hello
'); - // this.assertHTML('
hello
'); - // this.assertStableRerender(); - // } - - // @test "Null in primitive syntax"() { - // this.render('{{#if null}}NOPE{{else}}YUP{{/if}}'); - // this.assertHTML('YUP'); - // this.assertStableRerender(); - // } - - // @test "Sexpr helpers"() { - // this.registerHelper('testing', function (params) { - // return params[0] + "!"; - // }); - - // this.render('
{{testing (testing "hello")}}
'); - // this.assertHTML('
hello!!
'); - // this.assertStableRerender(); - // } - - // @test "The compiler can handle multiple invocations of sexprs"() { - // this.registerHelper('testing', function (params) { - // return "" + params[0] + params[1]; - // }); - - // this.render('
{{testing (testing "hello" foo) (testing (testing bar "lol") baz)}}
', { foo: "FOO", bar: "BAR", baz: "BAZ" }); - // this.assertHTML('
helloFOOBARlolBAZ
'); - // this.assertStableRerender(); - // } - - // @test "The compiler passes along the hash arguments"() { - // this.registerHelper('testing', function (_, hash) { - // return hash['first'] + '-' + hash['second']; - // }); - - // this.render('
{{testing first="one" second="two"}}
'); - // this.assertHTML('
one-two
'); - // this.assertStableRerender(); - // } - - // @test "Attributes can be populated with helpers that generate a string"() { - // this.registerHelper('testing', function (params) { - // return params[0]; - // }); - - // this.render('linky', { url: 'linky.html' }); - // this.assertHTML('linky'); - // this.assertStableRerender(); - // } - - // @test "Attribute helpers take a hash"() { - // this.registerHelper('testing', function (_, hash) { - // return hash['path']; - // }); - - // this.render('linky', { url: 'linky.html' }); - // this.assertHTML('linky'); - // this.assertStableRerender(); - // } - - // @test "Attributes containing multiple helpers are treated like a block"() { - // this.registerHelper('testing', function (params) { - // return params[0]; - // }); - - // this.render('linky', { foo: 'foo.com', bar: 'bar' }); - // this.assertHTML('linky'); - // this.assertStableRerender(); - // } - - // @test "Elements inside a yielded block"() { - // this.render('{{#identity}}
123
{{/identity}}'); - // this.assertHTML('
123
'); - // this.assertStableRerender(); - // } - - // @test "A simple block helper can return text"() { - // this.render('{{#identity}}test{{else}}not shown{{/identity}}'); - // this.assertHTML('test'); - // this.assertStableRerender(); - // } - - // @test "A block helper can have an else block"() { - // this.render('{{#render-inverse}}Nope{{else}}
123
{{/render-inverse}}'); - // this.assertHTML('
123
'); - // this.assertStableRerender(); - // } + @test "Simple helpers"() { + this.registerHelper('testing', ([id]) => id); + this.render('
{{testing title}}
', { title: 'hello' }); + this.assertHTML('
hello
'); + this.assertStableRerender(); + } + + @test "GH#13999 The compiler can handle simple helpers with inline null parameter"() { + let value; + this.registerHelper('say-hello', function (params) { + value = params[0]; + return 'hello'; + }); + this.render('
{{say-hello null}}
'); + this.assertHTML('
hello
'); + this.assert.strictEqual(value, null, 'is null'); + this.assertStableRerender(); + } + + @test "GH#13999 The compiler can handle simple helpers with inline string literal null parameter"() { + let value; + this.registerHelper('say-hello', function (params) { + value = params[0]; + return 'hello'; + }); + + this.render('
{{say-hello "null"}}
'); + this.assertHTML('
hello
'); + this.assert.strictEqual(value, 'null', 'is null string literal'); + this.assertStableRerender(); + } + + @test "GH#13999 The compiler can handle simple helpers with inline undefined parameter"() { + let value: Opaque = 'PLACEHOLDER'; + let length; + this.registerHelper('say-hello', function (params) { + length = params.length; + value = params[0]; + return 'hello'; + }); + + this.render('
{{say-hello undefined}}
'); + this.assertHTML('
hello
'); + this.assert.strictEqual(length, 1); + this.assert.strictEqual(value, undefined, 'is undefined'); + this.assertStableRerender(); + } + + @test "GH#13999 The compiler can handle simple helpers with positional parameter undefined string literal"() { + let value: Opaque = 'PLACEHOLDER'; + let length; + this.registerHelper('say-hello', function (params) { + length = params.length; + value = params[0]; + return 'hello'; + }); + + this.render('
{{say-hello "undefined"}} undefined
'); + this.assertHTML('
hello undefined
'); + this.assert.strictEqual(length, 1); + this.assert.strictEqual(value, 'undefined', 'is undefined string literal'); + this.assertStableRerender(); + } + + @test "GH#13999 The compiler can handle components with undefined named arguments"() { + let value: Opaque = 'PLACEHOLDER'; + this.registerHelper('say-hello', function (_, hash) { + value = hash['foo']; + return 'hello'; + }); + + this.render('
{{say-hello foo=undefined}}
'); + this.assertHTML('
hello
'); + this.assert.strictEqual(value, undefined, 'is undefined'); + this.assertStableRerender(); + } + + @test "GH#13999 The compiler can handle components with undefined string literal named arguments"() { + let value: Opaque = 'PLACEHOLDER'; + this.registerHelper('say-hello', function (_, hash) { + value = hash['foo']; + return 'hello'; + }); + + this.render('
{{say-hello foo="undefined"}}
'); + this.assertHTML('
hello
'); + this.assert.strictEqual(value, 'undefined', 'is undefined string literal'); + this.assertStableRerender(); + } + + @test "GH#13999 The compiler can handle components with null named arguments"() { + let value; + this.registerHelper('say-hello', function (_, hash) { + value = hash['foo']; + return 'hello'; + }); + + this.render('
{{say-hello foo=null}}
'); + this.assertHTML('
hello
'); + this.assert.strictEqual(value, null, 'is null'); + this.assertStableRerender(); + } + + @test "GH#13999 The compiler can handle components with null string literal named arguments"() { + let value; + this.registerHelper('say-hello', function (_, hash) { + value = hash['foo']; + return 'hello'; + }); + + this.render('
{{say-hello foo="null"}}
'); + this.assertHTML('
hello
'); + this.assert.strictEqual(value, 'null', 'is null string literal'); + this.assertStableRerender(); + } + + @test "Null curly in attributes"() { + this.render('
hello
'); + this.assertHTML('
hello
'); + this.assertStableRerender(); + } + + @test "Null in primitive syntax"() { + this.render('{{#if null}}NOPE{{else}}YUP{{/if}}'); + this.assertHTML('YUP'); + this.assertStableRerender(); + } + + @test "Sexpr helpers"() { + this.registerHelper('testing', function (params) { + return params[0] + "!"; + }); + + this.render('
{{testing (testing "hello")}}
'); + this.assertHTML('
hello!!
'); + this.assertStableRerender(); + } + + @test "The compiler can handle multiple invocations of sexprs"() { + this.registerHelper('testing', function (params) { + return "" + params[0] + params[1]; + }); + + this.render('
{{testing (testing "hello" foo) (testing (testing bar "lol") baz)}}
', { foo: "FOO", bar: "BAR", baz: "BAZ" }); + this.assertHTML('
helloFOOBARlolBAZ
'); + this.assertStableRerender(); + } + + @test "The compiler passes along the hash arguments"() { + this.registerHelper('testing', function (_, hash) { + return hash['first'] + '-' + hash['second']; + }); + + this.render('
{{testing first="one" second="two"}}
'); + this.assertHTML('
one-two
'); + this.assertStableRerender(); + } + + @test "Attributes can be populated with helpers that generate a string"() { + this.registerHelper('testing', function (params) { + return params[0]; + }); + + this.render('linky', { url: 'linky.html' }); + this.assertHTML('linky'); + this.assertStableRerender(); + } + + @test "Attribute helpers take a hash"() { + this.registerHelper('testing', function (_, hash) { + return hash['path']; + }); + + this.render('linky', { url: 'linky.html' }); + this.assertHTML('linky'); + this.assertStableRerender(); + } + + @test "Attributes containing multiple helpers are treated like a block"() { + this.registerHelper('testing', function (params) { + return params[0]; + }); + + this.render('linky', { foo: 'foo.com', bar: 'bar' }); + this.assertHTML('linky'); + this.assertStableRerender(); + } + + @test "Elements inside a yielded block"() { + this.render('{{#identity}}
123
{{/identity}}'); + this.assertHTML('
123
'); + this.assertStableRerender(); + } + + @test "A simple block helper can return text"() { + this.render('{{#identity}}test{{else}}not shown{{/identity}}'); + this.assertHTML('test'); + this.assertStableRerender(); + } + + @test "A block helper can have an else block"() { + this.render('{{#render-inverse}}Nope{{else}}
123
{{/render-inverse}}'); + this.assertHTML('
123
'); + this.assertStableRerender(); + } } // module("[Bundle Compiler] Rehydration Tests", Rehydration); diff --git a/packages/@glimmer/opcode-compiler/lib/opcode-builder.ts b/packages/@glimmer/opcode-compiler/lib/opcode-builder.ts index d67edb1140..1cff5c923e 100644 --- a/packages/@glimmer/opcode-compiler/lib/opcode-builder.ts +++ b/packages/@glimmer/opcode-compiler/lib/opcode-builder.ts @@ -777,7 +777,6 @@ export abstract class OpcodeBuilder : null; this.primitive(handle); } + resolveBlock(): void { return; } + pushLayout(layout: Option): void { throw new Error("Method not implemented."); } + resolveLayout(): void { - throw new Error("Method not implemented."); + return; } - pushSymbolTable(block: Option): void { - throw new Error("Method not implemented."); + + pushSymbolTable(table: Option): void { + if (table) { + let constant = this.constants.table(table); + this.primitive(constant); + } else { + this.primitive(null); + } } } diff --git a/packages/@glimmer/opcode-compiler/lib/syntax.ts b/packages/@glimmer/opcode-compiler/lib/syntax.ts index 73d8810aae..f60c1cd70e 100644 --- a/packages/@glimmer/opcode-compiler/lib/syntax.ts +++ b/packages/@glimmer/opcode-compiler/lib/syntax.ts @@ -4,7 +4,7 @@ import { Register } from '@glimmer/vm'; import * as WireFormat from '@glimmer/wire-format'; import * as ClientSide from './client-side'; import OpcodeBuilder, { CompileTimeLookup, OpcodeBuilderConstructor } from "./opcode-builder"; -import { CompilableBlock, CompileTimeProgram } from './interfaces'; +import { CompilableBlock } from './interfaces'; import Ops = WireFormat.Ops; @@ -252,6 +252,7 @@ const EXPRESSIONS = new Compilers(); import E = WireFormat.Expressions; import C = WireFormat.Core; +import { Program } from "@glimmer/program"; export function expr(expression: WireFormat.Expression, builder: OpcodeBuilder): void { if (Array.isArray(expression)) { @@ -267,7 +268,7 @@ EXPRESSIONS.add(Ops.Unknown, (sexp: E.Unknown, builder) => { let specifier = lookup.lookupHelper(name, meta); - if (specifier) { + if (specifier !== null) { builder.compileArgs(null, null, true); builder.helper(specifier); } else if (asPartial) { @@ -301,7 +302,7 @@ EXPRESSIONS.add(Ops.Helper, (sexp: E.Helper, builder) => { let specifier = lookup.lookupHelper(name, meta); - if (specifier) { + if (specifier !== null) { builder.compileArgs(params, hash, true); builder.helper(specifier); } else { @@ -787,7 +788,7 @@ export function compileStatement(statement: WireFormat.Statement, bui export interface TemplateOptions { // already in compilation options - program: CompileTimeProgram; + program: Program; macros: Macros; Builder: OpcodeBuilderConstructor; diff --git a/packages/@glimmer/program/lib/constants.ts b/packages/@glimmer/program/lib/constants.ts index 584162a925..bdbda315b8 100644 --- a/packages/@glimmer/program/lib/constants.ts +++ b/packages/@glimmer/program/lib/constants.ts @@ -3,6 +3,14 @@ import { CompileTimeConstants } from "@glimmer/opcode-compiler"; const UNRESOLVED = {}; +export interface ConstantPool { + strings: string[]; + arrays: number[][]; + tables: SymbolTable[]; + handles: number[]; + serializables: Opaque[]; +} + export class WriteOnlyConstants implements CompileTimeConstants { // `0` means NULL @@ -53,11 +61,90 @@ export class WriteOnlyConstants implements CompileTimeConstants { this.serializables.push(value); return index + 1; } + + toPool(): ConstantPool { + return { + strings: this.strings, + arrays: this.arrays, + tables: this.tables, + handles: this.handles, + serializables: this.serializables + }; + } +} + +export class RuntimeConstants { + protected strings: string[]; + protected arrays: number[][]; + protected tables: SymbolTable[]; + protected handles: number[]; + protected serializables: Opaque[]; + protected resolved: Opaque[]; + + constructor(public resolver: Resolver, pool: ConstantPool) { + this.strings = pool.strings; + this.arrays = pool.arrays; + this.tables = pool.tables; + this.handles = pool.handles; + this.serializables = pool.serializables; + this.resolved = this.handles.map(() => UNRESOLVED); + } + + // `0` means NULL + + getString(value: number): string { + return this.strings[value - 1]; + } + + getStringArray(value: number): string[] { + let names = this.getArray(value); + let _names: string[] = new Array(names.length); + + for (let i = 0; i < names.length; i++) { + let n = names[i]; + _names[i] = this.getString(n); + } + + return _names; + } + + getArray(value: number): number[] { + return this.arrays[value - 1]; + } + + getSymbolTable(value: number): T { + return this.tables[value - 1] as T; + } + + resolveHandle(s: number): T { + let index = s - 1; + let resolved = this.resolved[index]; + + if (resolved === UNRESOLVED) { + let handle = this.handles[index]; + resolved = this.resolved[index] = this.resolver.resolve(handle); + } + + return resolved as T; + } + + getSerializable(s: number): T { + return this.serializables[s - 1] as T; + } } export class Constants extends WriteOnlyConstants { - constructor(public resolver: Resolver) { + constructor(public resolver: Resolver, pool?: ConstantPool) { super(); + + if (pool) { + this.strings = pool.strings; + this.arrays = pool.arrays; + this.tables = pool.tables; + this.handles = pool.handles; + this.serializables = pool.serializables; + this.resolved = this.handles.map(() => UNRESOLVED); + } } // `0` means NULL diff --git a/packages/@glimmer/program/lib/program.ts b/packages/@glimmer/program/lib/program.ts index 62977d17ef..52dc77611c 100644 --- a/packages/@glimmer/program/lib/program.ts +++ b/packages/@glimmer/program/lib/program.ts @@ -1,7 +1,7 @@ import { Recast } from "@glimmer/interfaces"; import { DEBUG } from "@glimmer/local-debug-flags"; -import { Constants, WriteOnlyConstants } from './constants'; +import { Constants, WriteOnlyConstants, RuntimeConstants } from './constants'; import { Opcode } from './opcode'; import { VMHandle, CompileTimeProgram } from "@glimmer/opcode-compiler"; @@ -123,10 +123,23 @@ export class WriteOnlyProgram implements CompileTimeProgram { [key: number]: never; private _opcode: Opcode; - public heap: Heap; - constructor(public constants: WriteOnlyConstants) { - this.heap = new Heap(); + constructor(public constants: WriteOnlyConstants = new WriteOnlyConstants(), public heap = new Heap()) { + this._opcode = new Opcode(this.heap); + } + + opcode(offset: number): Opcode { + this._opcode.offset = offset; + return this._opcode; + } +} + +export class RuntimeProgram { + [key: number]: never; + + private _opcode: Opcode; + + constructor(public constants: RuntimeConstants, public heap: Heap) { this._opcode = new Opcode(this.heap); } diff --git a/packages/@glimmer/runtime/lib/vm/append.ts b/packages/@glimmer/runtime/lib/vm/append.ts index ae3f55297e..28fe86ee01 100644 --- a/packages/@glimmer/runtime/lib/vm/append.ts +++ b/packages/@glimmer/runtime/lib/vm/append.ts @@ -15,7 +15,7 @@ import { } from '../opcodes'; import { ProgramSymbolTable, Opcode } from "@glimmer/interfaces"; -import { Constants, Heap, Program } from "@glimmer/program"; +import { Heap, RuntimeProgram as Program, RuntimeConstants } from "@glimmer/program"; import { VMHandle as VMHandle } from "@glimmer/opcode-compiler"; export interface PublicVM { @@ -101,7 +101,7 @@ export default class VM implements PublicVM { public updatingOpcodeStack = new Stack>(); public cacheGroups = new Stack>(); public listBlockStack = new Stack(); - public constants: Constants; + public constants: RuntimeConstants; public heap: Heap; public stack = EvaluationStack.empty(); diff --git a/packages/@glimmer/runtime/lib/vm/render-result.ts b/packages/@glimmer/runtime/lib/vm/render-result.ts index e19d3106ca..c599380b6f 100644 --- a/packages/@glimmer/runtime/lib/vm/render-result.ts +++ b/packages/@glimmer/runtime/lib/vm/render-result.ts @@ -4,12 +4,12 @@ import { DestroyableBounds, clear } from '../bounds'; import UpdatingVM, { ExceptionHandler } from './update'; import { UpdatingOpcode } from '../opcodes'; import { Simple, Opaque } from '@glimmer/interfaces'; -import { Program } from "@glimmer/program"; +import { RuntimeProgram } from "@glimmer/program"; export default class RenderResult implements DestroyableBounds, ExceptionHandler { constructor( public env: Environment, - private program: Program, + private program: RuntimeProgram, private updating: LinkedList, private bounds: DestroyableBounds ) {} diff --git a/packages/@glimmer/runtime/lib/vm/update.ts b/packages/@glimmer/runtime/lib/vm/update.ts index 811f957bd0..71eecf8766 100644 --- a/packages/@glimmer/runtime/lib/vm/update.ts +++ b/packages/@glimmer/runtime/lib/vm/update.ts @@ -23,7 +23,7 @@ import { DOMChanges } from '../dom/helper'; import { Simple } from '@glimmer/interfaces'; import VM, { CapturedStack, EvaluationStack } from './append'; -import { Constants, Program } from "@glimmer/program"; +import { RuntimeConstants as Constants, RuntimeProgram as Program } from "@glimmer/program"; import { VMHandle } from "@glimmer/opcode-compiler"; export default class UpdatingVM { diff --git a/packages/@glimmer/test-helpers/index.ts b/packages/@glimmer/test-helpers/index.ts index 9ed8f1b66b..a34eef67dc 100644 --- a/packages/@glimmer/test-helpers/index.ts +++ b/packages/@glimmer/test-helpers/index.ts @@ -25,8 +25,10 @@ export { TestEnvironment, TestDynamicScope, TestSpecifier, + TestMacros, LookupType, UserHelper, + HelperReference, equalsElement, inspectHooks, regex, diff --git a/packages/@glimmer/test-helpers/lib/environment.ts b/packages/@glimmer/test-helpers/lib/environment.ts index c5337342f1..93d2cc3b87 100644 --- a/packages/@glimmer/test-helpers/lib/environment.ts +++ b/packages/@glimmer/test-helpers/lib/environment.ts @@ -369,7 +369,7 @@ class StaticTaglessComponentManager extends BasicComponentManager { return resolver.compileTemplate(handle, name, (source, options) => { let template = createTemplate(source, {}); - let compileOptions = { ...options, asPartial: false }; + let compileOptions = { ...options, asPartial: false, referer: null }; return new WrappedBuilder(compileOptions, template, capabilities); }); } @@ -554,7 +554,7 @@ class EmberishCurlyComponentManager extends GenericComponentManager implements W return resolver.compileTemplate(handle, layout.name, (source, options) => { let template = createTemplate(source); - return new WrappedBuilder({ ...options, asPartial: false }, template, CURLY_CAPABILITIES); + return new WrappedBuilder({ ...options, asPartial: false, referer: null }, template, CURLY_CAPABILITIES); }); } @@ -665,7 +665,7 @@ export class SimplePathReference implements PathReference { export type UserHelper = (args: ReadonlyArray, named: Dict) => any; -class HelperReference implements PathReference { +export class HelperReference implements PathReference { private helper: UserHelper; private args: CapturedArguments; public tag = VOLATILE_TAG; @@ -867,7 +867,7 @@ export class TestResolver implements Resolver { } } -class TestMacros extends Macros { +export class TestMacros extends Macros { constructor() { super(); @@ -888,7 +888,7 @@ class TestMacros extends Macros { let lookup = builder.lookup; - let specifier = lookup.lookupComponentSpec(name, builder.meta); + let specifier = lookup.lookupComponentSpec(name, {}); if (specifier) { builder.component.static(specifier, [params, hashToArgs(hash), template, inverse]); @@ -900,7 +900,7 @@ class TestMacros extends Macros { inlines.addMissing((name, params, hash, builder) => { let lookup = builder.lookup; - let specifier = lookup.lookupComponentSpec(name, builder.meta); + let specifier = lookup.lookupComponentSpec(name, {}); if (specifier) { builder.component.static(specifier, [params!, hashToArgs(hash), null, null]); From ffb0f209d1115f119cf2331525202da4f1326416 Mon Sep 17 00:00:00 2001 From: Chad Hietala Date: Tue, 15 Aug 2017 12:46:38 -0400 Subject: [PATCH 14/42] Cleanup some type errors --- .../test/initial-render-test.ts | 26 +++++++++---------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/packages/@glimmer/bundle-compiler/test/initial-render-test.ts b/packages/@glimmer/bundle-compiler/test/initial-render-test.ts index 654ffb63ec..cbe72f9f5a 100644 --- a/packages/@glimmer/bundle-compiler/test/initial-render-test.ts +++ b/packages/@glimmer/bundle-compiler/test/initial-render-test.ts @@ -12,7 +12,7 @@ import { } from "@glimmer/test-helpers"; import { AbstractTestEnvironment } from "@glimmer/test-helpers/lib/environment"; import { BundleCompiler, CompilerDelegate, Specifier, SpecifierMap } from "@glimmer/bundle-compiler"; -import { Macros, EagerOpcodeBuilder } from "@glimmer/opcode-compiler"; +import { EagerOpcodeBuilder, ComponentCapabilities } from "@glimmer/opcode-compiler"; import { Program, RuntimeProgram, WriteOnlyProgram, RuntimeConstants } from "@glimmer/program"; import { elementBuilder, LowLevelVM, TemplateIterator, RenderResult, Helper, SVG_NAMESPACE } from "@glimmer/runtime"; import { UpdatableReference } from "@glimmer/object-reference"; @@ -49,16 +49,16 @@ class BundledClientEnvironment extends AbstractTestEnvironment { class RuntimeResolver implements Resolver { constructor(private map: SpecifierMap, private modules: Modules) {} - lookupHelper(name: string, meta: Opaque): Option { + lookupHelper(_name: string, _meta: Opaque): Option { throw new Error("Method not implemented."); } - lookupModifier(name: string, meta: Opaque): Option { + lookupModifier(_name: string, _meta: Opaque): Option { throw new Error("Method not implemented."); } - lookupComponent(name: string, meta: Opaque): Option { + lookupComponent(_name: string, _meta: Opaque): Option { throw new Error("Method not implemented."); } - lookupPartial(name: string, meta: Opaque): Option { + lookupPartial(_name: string, _meta: Opaque): Option { throw new Error("Method not implemented."); } resolve(specifier: number): U { @@ -111,13 +111,13 @@ class Modules { class BundlingDelegate implements CompilerDelegate { constructor(private modules: Modules) {} - hasComponentInScope(componentName: string, referrer: Specifier): boolean { + hasComponentInScope(_componentName: string, _referrer: Specifier): boolean { return false; } - resolveComponentSpecifier(componentName: string, referrer: Specifier): Specifier { + resolveComponentSpecifier(_componentName: string, _referrer: Specifier): Specifier { throw new Error("Method not implemented."); } - getComponentCapabilities(specifier: Specifier): ComponentCapabilities { + getComponentCapabilities(_specifier: Specifier): ComponentCapabilities { throw new Error("Method not implemented."); } @@ -130,16 +130,16 @@ class BundlingDelegate implements CompilerDelegate { return { module: path!, name: 'default' }; } - hasModifierInScope(modifierName: string, referer: Specifier): boolean { + hasModifierInScope(_modifierName: string, _referer: Specifier): boolean { return false; } - resolveModifierSpecifier(modifierName: string, referer: Specifier): Specifier { + resolveModifierSpecifier(_modifierName: string, _referer: Specifier): Specifier { throw new Error("Method not implemented."); } - hasPartialInScope(partialName: string, referer: Specifier): boolean { + hasPartialInScope(_partialName: string, _referer: Specifier): boolean { return false; } - resolvePartialSpecifier(partialName: string, referer: Specifier): Specifier { + resolvePartialSpecifier(_partialName: string, _referer: Specifier): Specifier { throw new Error("Method not implemented."); } } @@ -159,7 +159,7 @@ class RenderingTest extends AbstractRenderTest { this.modules.register(name, { default: glimmerHelper }); } - registerComponent(type: "Glimmer" | "Curly" | "Dynamic" | "Basic" | "Fragment", name: string, layout: string): void { + registerComponent(_type: "Glimmer" | "Curly" | "Dynamic" | "Basic" | "Fragment", _name: string, _layout: string): void { throw new Error("Method not implemented."); } From a4a57f9e031259a367d390434628d47916fa74ba Mon Sep 17 00:00:00 2001 From: Yehuda Katz Date: Wed, 16 Aug 2017 01:57:55 -0700 Subject: [PATCH 15/42] Reorganize tests to share more between envs This commit restructures the tests (and some code) to allow the rendering test suite to be written once and used with rehydration, using the lazy builder, and using the bundle compiler. This is accomplished by moving the environment specific questions into a RenderDelegate, which is responsible for: - constructing the initial element to render into - registering components - registering helpers - rendering a template for a context into an element At the moment, three rehydration tests fail, but I'm not entirely sure how they were ever passing. They involve quirks of browser parsing (for example,
is parsed as
, which mis-nests rehydration markers. We also have tests that confirm that