From 65e91d52e40476b6e85ea19c5596df0b7d2713c4 Mon Sep 17 00:00:00 2001 From: Chad Hietala Date: Wed, 5 Apr 2017 19:13:58 -0700 Subject: [PATCH] Revert "Merge pull request #444 from tildeio/artisinal-code-stripping" This reverts commit 82a2ab8400b54f118e2944f3cf6fed24dd8bd40c, reversing changes made to c56ef681d433483ebad8f1025e58e5fe6394367b. --- .../compiler/lib/javascript-compiler.ts | 9 +- .../compiler/lib/template-compiler.ts | 5 +- .../@glimmer/compiler/lib/template-visitor.ts | 5 +- packages/@glimmer/reference/lib/iterable.ts | 4 +- packages/@glimmer/runtime/index.ts | 1 + packages/@glimmer/runtime/lib/builder.ts | 18 +- .../runtime/lib/compat/svg-inner-html-fix.ts | 4 +- .../runtime/lib/compiled/opcodes/builder.ts | 6 +- .../runtime/lib/compiled/opcodes/component.ts | 8 + .../runtime/lib/compiled/opcodes/content.ts | 25 ++ .../runtime/lib/compiled/opcodes/dom.ts | 50 +++- .../runtime/lib/compiled/opcodes/vm.ts | 35 +++ packages/@glimmer/runtime/lib/compiler.ts | 4 +- packages/@glimmer/runtime/lib/environment.ts | 5 +- packages/@glimmer/runtime/lib/opcodes.ts | 220 ++++++++++++++++++ packages/@glimmer/runtime/lib/scanner.ts | 7 + .../@glimmer/runtime/lib/syntax/functions.ts | 34 ++- packages/@glimmer/runtime/lib/upsert.ts | 4 +- packages/@glimmer/runtime/lib/vm/append.ts | 14 +- packages/@glimmer/runtime/lib/vm/update.ts | 46 +++- packages/@glimmer/syntax/lib/parser.ts | 7 +- .../@glimmer/test-helpers/lib/environment.ts | 4 + packages/@glimmer/util/index.ts | 2 + packages/@glimmer/util/lib/assert.ts | 18 ++ packages/@glimmer/util/lib/logger.ts | 68 ++++++ packages/@glimmer/util/lib/platform-utils.ts | 16 +- 26 files changed, 573 insertions(+), 46 deletions(-) create mode 100644 packages/@glimmer/util/lib/logger.ts diff --git a/packages/@glimmer/compiler/lib/javascript-compiler.ts b/packages/@glimmer/compiler/lib/javascript-compiler.ts index de9ab3254c..9e98ebcebe 100644 --- a/packages/@glimmer/compiler/lib/javascript-compiler.ts +++ b/packages/@glimmer/compiler/lib/javascript-compiler.ts @@ -1,6 +1,6 @@ import * as WireFormat from '@glimmer/wire-format'; import { assert } from "@glimmer/util"; -import { Stack, DictSet, Option } from "@glimmer/util"; +import { Stack, DictSet, Option, expect } from "@glimmer/util"; import { AST } from '@glimmer/syntax'; import { BlockSymbolTable, ProgramSymbolTable } from './template-visitor'; @@ -169,7 +169,7 @@ export default class JavaScriptCompiler { } get currentBlock(): Block { - return this.blocks.current; + return expect(this.blocks.current, 'Expected a block on the stack'); } process(): Template { @@ -228,6 +228,8 @@ export default class JavaScriptCompiler { let hash = this.popValue(); let blocks = this.template.block.blocks; + assert(typeof template !== 'number' || blocks[template] !== null, 'missing block in the compiler'); + assert(typeof inverse !== 'number' || blocks[inverse] !== null, 'missing block in the compiler'); this.push([Ops.Block, name, params, hash, blocks[template], blocks[inverse]]); } @@ -350,6 +352,7 @@ export default class JavaScriptCompiler { endComponent(): [WireFormat.Statements.Attribute[], WireFormat.Core.Hash, Option] { let component = this.blocks.pop(); + assert(component instanceof ComponentBlock, "Compiler bug: endComponent() should end a component"); return (component as ComponentBlock).toJSON(); } @@ -364,6 +367,7 @@ export default class JavaScriptCompiler { } prepareObject(size: number) { + assert(this.values.length >= size, `Expected ${size} values on the stack, found ${this.values.length}`); let keys: string[] = new Array(size); let values: Expression[] = new Array(size); @@ -391,6 +395,7 @@ export default class JavaScriptCompiler { } popValue(): T { + assert(this.values.length, "No expression found on stack"); return this.values.pop() as T; } } diff --git a/packages/@glimmer/compiler/lib/template-compiler.ts b/packages/@glimmer/compiler/lib/template-compiler.ts index 94bfffdf0c..a6a5662323 100644 --- a/packages/@glimmer/compiler/lib/template-compiler.ts +++ b/packages/@glimmer/compiler/lib/template-compiler.ts @@ -1,6 +1,7 @@ import TemplateVisitor, { SymbolTable, Action } from "./template-visitor"; import JavaScriptCompiler, { Template } from "./javascript-compiler"; import { Stack, getAttrNamespace } from "@glimmer/util"; +import { assert, expect } from "@glimmer/util"; import { TemplateMeta } from "@glimmer/wire-format"; import { AST, isLiteral } from '@glimmer/syntax'; @@ -34,7 +35,7 @@ export default class TemplateCompiler { } get symbols(): SymbolTable { - return this.symbolStack.current; + return expect(this.symbolStack.current, 'Expected a symbol table on the stack'); } process(actions: Action[]): Action[] { @@ -306,6 +307,7 @@ export default class TemplateCompiler { for (let i = params.length - 1; i >= 0; i--) { let param = params[i]; + assert(this[param.type], `Unimplemented ${param.type} on TemplateCompiler`); (this[param.type] as any)(param); } @@ -323,6 +325,7 @@ export default class TemplateCompiler { for (let i = pairs.length - 1; i >= 0; i--) { let { key, value } = pairs[i]; + assert(this[value.type], `Unimplemented ${value.type} on TemplateCompiler`); (this[value.type] as any)(value); this.opcode('literal', null, key); } diff --git a/packages/@glimmer/compiler/lib/template-visitor.ts b/packages/@glimmer/compiler/lib/template-visitor.ts index df5df5cc33..6952952949 100644 --- a/packages/@glimmer/compiler/lib/template-visitor.ts +++ b/packages/@glimmer/compiler/lib/template-visitor.ts @@ -1,6 +1,6 @@ import { AST } from '@glimmer/syntax'; import { Core } from '@glimmer/wire-format'; -import { Dict, Option, dict } from '@glimmer/util'; +import { Dict, Option, dict, unreachable, expect } from '@glimmer/util'; export abstract class SymbolTable { static top(): ProgramSymbolTable { @@ -35,6 +35,7 @@ export class ProgramSymbolTable extends SymbolTable { } get(_name: string): never { + throw unreachable(); } getLocalsMap(): Dict { @@ -344,7 +345,7 @@ export default class TemplateVisitor { // Frame helpers private get currentFrame(): Frame { - return this.getCurrentFrame(); + return expect(this.getCurrentFrame(), "Expected a current frame"); } private getCurrentFrame(): Option { diff --git a/packages/@glimmer/reference/lib/iterable.ts b/packages/@glimmer/reference/lib/iterable.ts index bd06e2cdc0..52be573f6a 100644 --- a/packages/@glimmer/reference/lib/iterable.ts +++ b/packages/@glimmer/reference/lib/iterable.ts @@ -1,4 +1,4 @@ -import { LinkedList, ListNode, Opaque, Option, dict } from '@glimmer/util'; +import { LinkedList, ListNode, Opaque, Option, dict, expect } from '@glimmer/util'; import { VersionedPathReference as PathReference, Tag } from './validators'; export interface IterationItem { @@ -247,6 +247,8 @@ export class IteratorSynchronizer { private nextRetain(item: OpaqueIterationItem) { let { artifacts, current } = this; + current = expect(current, 'BUG: current is empty'); + current.update(item); this.current = artifacts.nextNode(current); this.target.retain(item.key, current.value, current.memo); diff --git a/packages/@glimmer/runtime/index.ts b/packages/@glimmer/runtime/index.ts index d62bc61873..3728c64543 100644 --- a/packages/@glimmer/runtime/index.ts +++ b/packages/@glimmer/runtime/index.ts @@ -38,6 +38,7 @@ export { export { Register, + debugSlice } from './lib/opcodes'; export { diff --git a/packages/@glimmer/runtime/lib/builder.ts b/packages/@glimmer/runtime/lib/builder.ts index 5649cf842a..46691d9460 100644 --- a/packages/@glimmer/runtime/lib/builder.ts +++ b/packages/@glimmer/runtime/lib/builder.ts @@ -2,7 +2,7 @@ import Bounds, { Cursor, DestroyableBounds, clear } from './bounds'; import { DOMChanges, DOMTreeConstruction } from './dom/helper'; -import { Option, Destroyable, Stack, LinkedList, LinkedListNode, } from '@glimmer/util'; +import { Option, Destroyable, Stack, LinkedList, LinkedListNode, assert, expect } from '@glimmer/util'; import { Environment } from './environment'; @@ -115,15 +115,15 @@ export class ElementStack implements Cursor { } expectConstructing(_: string): Simple.Element { - return this.constructing!; + return expect(this.constructing!, `${method} should only be called while constructing an element`); } expectOperations(_: string): ElementOperations { - return this.operations!; + return expect(this.operations!, `${method} should only be called while constructing an element`); } block(): Tracker { - return this.blockStack.current!; + return expect(this.blockStack.current!, "Expected a current block tracker"); } popElement() { @@ -133,7 +133,7 @@ export class ElementStack implements Cursor { nextSiblingStack.pop(); // LOGGER.debug(`-> element stack ${this.elementStack.toArray().map(e => e.tagName).join(', ')}`); - this.element = elementStack.current!; + this.element = expect(elementStack.current, "can't pop past the last element"); this.nextSibling = nextSiblingStack.current; return topElement; @@ -181,8 +181,7 @@ export class ElementStack implements Cursor { popBlock(): Tracker { this.block().finalize(this); - - return this.blockStack.pop()!; + return expect(this.blockStack.pop(), "Expected popBlock to return a block"); } openElement(tag: string, _operations?: ElementOperations): Simple.Element { @@ -198,7 +197,7 @@ export class ElementStack implements Cursor { flushElement() { let parent = this.element; - let element = this.constructing!; + let element = expect(this.constructing, `flushElement should only be called when constructing an element`); this.dom.insertBefore(parent, element, this.nextSibling); @@ -414,12 +413,15 @@ class BlockListTracker implements Tracker { } openElement(_element: Element) { + assert(false, 'Cannot openElement directly inside a block list'); } closeElement() { + assert(false, 'Cannot closeElement directly inside a block list'); } newNode(_node: Node) { + assert(false, 'Cannot create a new node directly inside a block list'); } newBounds(_bounds: Bounds) { diff --git a/packages/@glimmer/runtime/lib/compat/svg-inner-html-fix.ts b/packages/@glimmer/runtime/lib/compat/svg-inner-html-fix.ts index 38968a863b..be6b0456cd 100644 --- a/packages/@glimmer/runtime/lib/compat/svg-inner-html-fix.ts +++ b/packages/@glimmer/runtime/lib/compat/svg-inner-html-fix.ts @@ -1,6 +1,6 @@ import { Bounds, ConcreteBounds } from '../bounds'; import { moveNodesBefore, DOMChanges, DOMTreeConstruction } from '../dom/helper'; -import { Option } from '@glimmer/util'; +import { Option, unwrap } from '@glimmer/util'; export const SVG_NAMESPACE = 'http://www.w3.org/2000/svg'; export type SVG_NAMESPACE = typeof SVG_NAMESPACE; @@ -85,7 +85,7 @@ function shouldApplyFix(document: Document, svgNamespace: SVG_NAMESPACE) { // Safari: Will throw, insertAdjacentHTML is not present on SVG } finally { // FF: Old versions will create a node in the wrong namespace - if (svg.childNodes.length === 1 && svg.firstChild.namespaceURI === SVG_NAMESPACE) { + if (svg.childNodes.length === 1 && unwrap(svg.firstChild).namespaceURI === SVG_NAMESPACE) { // The test worked as expected, no fix required return false; } diff --git a/packages/@glimmer/runtime/lib/compiled/opcodes/builder.ts b/packages/@glimmer/runtime/lib/compiled/opcodes/builder.ts index eddaa1cee1..5aacb91d1c 100644 --- a/packages/@glimmer/runtime/lib/compiled/opcodes/builder.ts +++ b/packages/@glimmer/runtime/lib/compiled/opcodes/builder.ts @@ -4,7 +4,7 @@ import * as vm from './vm'; import { Insertion } from '../../upsert'; import { Register } from '../../opcodes'; import * as WireFormat from '@glimmer/wire-format'; -import { Option, Stack, Opaque, dict, fillNulls, EMPTY_ARRAY } from '@glimmer/util'; +import { Option, Stack, Opaque, dict, expect, fillNulls, EMPTY_ARRAY } from '@glimmer/util'; import { Constants, ConstantString, @@ -98,7 +98,7 @@ export abstract class BasicOpcodeBuilder { // helpers private get labels(): Labels { - return this.labelsStack.current; + return expect(this.labelsStack.current, 'bug: not in a label stack'); } startLabels() { @@ -106,7 +106,7 @@ export abstract class BasicOpcodeBuilder { } stopLabels() { - let label = this.labelsStack.pop(); + let label = expect(this.labelsStack.pop(), 'unbalanced push and pop labels'); label.patch(this.program); } diff --git a/packages/@glimmer/runtime/lib/compiled/opcodes/component.ts b/packages/@glimmer/runtime/lib/compiled/opcodes/component.ts index 3773954e62..8e6cbe95b5 100644 --- a/packages/@glimmer/runtime/lib/compiled/opcodes/component.ts +++ b/packages/@glimmer/runtime/lib/compiled/opcodes/component.ts @@ -183,6 +183,14 @@ export class UpdateComponentOpcode extends UpdatingOpcode { manager.update(component, dynamicScope); } + + toJSON(): OpcodeJSON { + return { + guid: this._guid, + type: this.type, + args: [JSON.stringify(this.name)] + }; + } } export class DidUpdateLayoutOpcode extends UpdatingOpcode { diff --git a/packages/@glimmer/runtime/lib/compiled/opcodes/content.ts b/packages/@glimmer/runtime/lib/compiled/opcodes/content.ts index f13ef11039..d9c28b0291 100644 --- a/packages/@glimmer/runtime/lib/compiled/opcodes/content.ts +++ b/packages/@glimmer/runtime/lib/compiled/opcodes/content.ts @@ -214,6 +214,16 @@ abstract class UpdateOpcode extends UpdatingOpcode { bounds.update(upsert.bounds); } } + + toJSON(): OpcodeJSON { + let { _guid: guid, type, cache } = this; + + return { + guid, + type, + details: { lastValue: JSON.stringify(cache.peek()) } + }; + } } abstract class GuardedUpdateOpcode extends UpdateOpcode { @@ -275,6 +285,21 @@ abstract class GuardedUpdateOpcode extends UpdateOpcode return null as any; } + + toJSON(): OpcodeJSON { + let { _guid: guid, type, deopted } = this; + + if (deopted) { + return { + guid, + type, + deopted: true, + children: [deopted.toJSON()] + }; + } else { + return super.toJSON(); + } + } } export class OptimizedCautiousAppendOpcode extends AppendDynamicOpcode { diff --git a/packages/@glimmer/runtime/lib/compiled/opcodes/dom.ts b/packages/@glimmer/runtime/lib/compiled/opcodes/dom.ts index e44188f556..1fa25d9491 100644 --- a/packages/@glimmer/runtime/lib/compiled/opcodes/dom.ts +++ b/packages/@glimmer/runtime/lib/compiled/opcodes/dom.ts @@ -1,10 +1,10 @@ -import { UpdatingOpcode } from '../../opcodes'; +import { OpcodeJSON, UpdatingOpcode } from '../../opcodes'; import { VM, UpdatingVM } from '../../vm'; import { Arguments } from '../../vm/arguments'; import * as Simple from '../../dom/interfaces'; import { FIX_REIFICATION } from '../../dom/interfaces'; import { Environment } from '../../environment'; -import { FIXME, Option, Opaque } from '@glimmer/util'; +import { FIXME, Option, Opaque, Dict, unwrap, expect } from '@glimmer/util'; import { CachedReference, Reference, @@ -299,7 +299,7 @@ export class ComponentElementOperations implements ElementOperations { } attributeNames.push(name); - attributes.push(attribute); + unwrap(attributes).push(attribute); } } @@ -371,6 +371,13 @@ export class UpdateModifierOpcode extends UpdatingOpcode { this.lastUpdated = tag.value(); } } + + toJSON(): OpcodeJSON { + return { + guid: this._guid, + type: this.type + }; + } } export interface Attribute { @@ -410,7 +417,7 @@ export class DynamicAttribute implements Attribute { patch(env: Environment) { let { element, cache } = this; - let value = cache.revalidate(); + let value = expect(cache, 'must patch after flush').revalidate(); if (isModified(value)) { this.attributeManager.updateAttribute(env, element as FIXME, value, this.namespace); @@ -431,6 +438,31 @@ export class DynamicAttribute implements Attribute { return new PatchElementOpcode(this); } } + + toJSON(): Dict> { + let { element, namespace, name, cache } = this; + + let formattedElement = formatElement(element); + let lastValue = expect(cache, 'must serialize after flush').peek() as string; + + if (namespace) { + return { + element: formattedElement, + type: 'attribute', + namespace, + name, + lastValue + }; + } + + return { + element: formattedElement, + type: 'attribute', + namespace: namespace === undefined ? null : namespace, + name, + lastValue + }; + } } function formatElement(element: Simple.Element): string { @@ -464,4 +496,14 @@ export class PatchElementOpcode extends UpdatingOpcode { evaluate(vm: UpdatingVM) { this.operation.patch(vm.env); } + + toJSON(): OpcodeJSON { + let { _guid, type, operation } = this; + + return { + guid: _guid, + type, + details: operation.toJSON() + }; + } } diff --git a/packages/@glimmer/runtime/lib/compiled/opcodes/vm.ts b/packages/@glimmer/runtime/lib/compiled/opcodes/vm.ts index e53172c064..2d55b88d9a 100644 --- a/packages/@glimmer/runtime/lib/compiled/opcodes/vm.ts +++ b/packages/@glimmer/runtime/lib/compiled/opcodes/vm.ts @@ -184,6 +184,25 @@ export class Assert extends UpdatingOpcode { vm.throw(); } } + + toJSON(): OpcodeJSON { + let { type, _guid, cache } = this; + + let expected; + + try { + expected = JSON.stringify(cache.peek()); + } catch(e) { + expected = String(cache.peek()); + } + + return { + guid: _guid, + type, + args: [], + details: { expected } + }; + } } export class JumpIfNotModifiedOpcode extends UpdatingOpcode { @@ -208,6 +227,14 @@ export class JumpIfNotModifiedOpcode extends UpdatingOpcode { didModify() { this.lastRevision = this.tag.value(); } + + toJSON(): OpcodeJSON { + return { + guid: this._guid, + type: this.type, + args: [JSON.stringify(this.target.inspect())] + }; + } } export class DidModifyOpcode extends UpdatingOpcode { @@ -242,4 +269,12 @@ export class LabelOpcode implements UpdatingOpcode { inspect(): string { return `${this.label} [${this._guid}]`; } + + toJSON(): OpcodeJSON { + return { + guid: this._guid, + type: this.type, + args: [JSON.stringify(this.inspect())] + }; + } } diff --git a/packages/@glimmer/runtime/lib/compiler.ts b/packages/@glimmer/runtime/lib/compiler.ts index 01ac057548..80d8b7328f 100644 --- a/packages/@glimmer/runtime/lib/compiler.ts +++ b/packages/@glimmer/runtime/lib/compiler.ts @@ -5,7 +5,7 @@ import { Maybe, Option } from '@glimmer/util'; import { Ops, TemplateMeta } from '@glimmer/wire-format'; import { Template } from './template'; -import { Register } from './opcodes'; +import { Register, debugSlice } from './opcodes'; import { ATTRS_BLOCK, ClientSide, compileStatement } from './scanner'; @@ -173,6 +173,8 @@ class WrappedBuilder implements InnerLayoutBuilder { let start = b.start; let end = b.finalize(); + debugSlice(env, start, end); + return new CompiledDynamicTemplate(start, end, { meta, hasEval: layout.hasEval, diff --git a/packages/@glimmer/runtime/lib/environment.ts b/packages/@glimmer/runtime/lib/environment.ts index 7b052ba565..63056ba0b0 100644 --- a/packages/@glimmer/runtime/lib/environment.ts +++ b/packages/@glimmer/runtime/lib/environment.ts @@ -32,7 +32,9 @@ import { Destroyable, Opaque, HasGuid, + assert, ensureGuid, + expect } from '@glimmer/util'; import { @@ -328,11 +330,12 @@ export abstract class Environment { } begin() { + assert(!this._transaction, 'Cannot start a nested transaction'); this._transaction = new Transaction(); } private get transaction(): Transaction { - return this._transaction!; + return expect(this._transaction!, 'must be in a transaction'); } didCreate(component: T, manager: ComponentManager) { diff --git a/packages/@glimmer/runtime/lib/opcodes.ts b/packages/@glimmer/runtime/lib/opcodes.ts index 5905535ead..73df280947 100644 --- a/packages/@glimmer/runtime/lib/opcodes.ts +++ b/packages/@glimmer/runtime/lib/opcodes.ts @@ -902,6 +902,157 @@ export const enum Op { Size } +export function debugSlice(env: Environment, start: number, end: number) { + if (window['GLIMMER_DEBUG'] !== true) { return; } + + let { program, constants } = env; + + // 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<=end; i+=4) { + let { type, op1, op2, op3 } = program.opcode(i); + let [name, params] = debug(constants, type, op1, op2, op3); + console.log(`${i}. ${logOpcode(name, params)}`); + } + + console.groupEnd(); +} + +function logOpcode(type: string, params: Option): string { + 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 = JSON.stringify(param); + if (string === undefined) return 'undefined'; + + let debug = JSON.parse(string); + if (typeof debug === 'object' && debug && debug['GlimmerDebug']) { + return debug['GlimmerDebug']; + } + + return string; +} + +function debug(c: Constants, op: Op, op1: number, op2: number, op3: number): [string, object] { + switch (op) { + case Op.Bug: throw unreachable(); + + case Op.Helper: return ['Helper', { helper: c.getFunction(op1) }]; + case Op.Function: return ['Function', { function: c.getFunction(op1) }]; + case Op.SetVariable: return ['SetVariable', { symbol: op1 }]; + case Op.GetVariable: return ['GetVariable', { symbol: op1 }]; + case Op.GetProperty: return ['GetProperty', { key: c.getString(op1) }]; + case Op.PushBlock: return ['PushBlock', { block: c.getBlock(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.Immediate: return ['Immediate', { value: op1 }]; + case Op.Constant: return ['Constant', { value: c.getOther(op1) }]; + case Op.PrimitiveReference: return ['PrimitiveReference', { primitive: op1 }]; + 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', {}]; + + /// HTML + case Op.Text: return ['Text', { text: c.getString(op1) }]; + case Op.Comment: return ['Comment', { comment: c.getString(op1) }]; + case Op.DynamicContent: return ['DynamicContent', { value: c.getOther(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 }]; + case Op.DynamicAttrNS: return ['DynamicAttrNS', { name: c.getString(op1), ns: c.getString(op2), trusting: !!op2 }]; + 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.CompileDynamicBlock: return ['CompileDynamicBlock', {}]; + case Op.InvokeStatic: return ['InvokeStatic', { block: c.getBlock(op1) }]; + case Op.InvokeDynamic: return ['InvokeDynamic', { invoker: c.getOther(op1) }]; + 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.Test: 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.PushComponentManager: return ['PushComponentManager', { definition: c.getOther(op1) }]; + case Op.PushDynamicComponentManager: return ['PushDynamicComponentManager', {}]; + case Op.InitializeComponentState: return ['InitializeComponentState', {}]; + case Op.PushArgs: return ['PushArgs', { positional: op1, synthetic: !!op2 }]; + 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.PushComponentOperations: return ['PushComponentOperations', {}]; + case Op.GetComponentSelf: return ['GetComponentSelf', { state: Register[op1] }]; + case Op.GetComponentLayout: return ['GetComponentLayout', { state: Register[op1] }]; + 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.GetPartialTemplate: return ['CompilePartial', {}]; + case Op.ResolveMaybeLocal: return ['ResolveMaybeLocal', { name: c.getString(op1)} ]; + + /// DEBUGGER + case Op.Debugger: return ['Debugger', { symbols: c.getOther(op1), evalInfo: c.getArray(op2) }]; + + /// STATEMENTS + + case Op.Size: throw unreachable(); + } + + throw unreachable(); +} + export type Operand1 = number; export type Operand2 = number; export type Operand3 = number; @@ -917,8 +1068,22 @@ export class AppendOpcodes { evaluate(vm: VM, opcode: Opcode, type: number) { let func = this.evaluateOpcode[type]; + let [name, params] = debug(vm.constants, opcode.type, opcode.op1, opcode.op2, opcode.op3); + let verbose = window['GLIMMER_DEBUG']; + + if (verbose === true) { + console.log(`${vm['pc'] - 4}. ${logOpcode(name, params)}`); + } + // console.log(...debug(vm.constants, type, opcode.op1, opcode.op2, opcode.op3)); func(vm, opcode); + + if (verbose === true) { + console.log('%c -> pc: %d, ra: %d, fp: %d, sp: %d, s0: %O, s1: %O, t0: %O, t1: %O', 'color: orange', vm['pc'], vm['ra'], vm['fp'], vm['sp'], vm['s0'], vm['s1'], vm['t0'], vm['t1']); + console.log('%c -> eval stack', 'color: red', vm.stack.toArray()); + console.log('%c -> scope', 'color: green', vm.scope()['slots'].map(s => s && s['value'] ? s['value']() : s)); + console.log('%c -> elements', 'color: blue', vm.elements()['elementStack'].toArray()); + } } } @@ -931,6 +1096,10 @@ export abstract class AbstractOpcode { constructor() { initializeGuid(this); } + + toJSON(): OpcodeJSON { + return { guid: this._guid, type: this.type }; + } } export abstract class UpdatingOpcode extends AbstractOpcode { @@ -943,3 +1112,54 @@ export abstract class UpdatingOpcode extends AbstractOpcode { } export type UpdatingOpSeq = ListSlice; + +export function inspect(opcodes: ReadonlyArray): string { + let buffer: string[] = []; + + opcodes.forEach((opcode, i) => { + _inspect(opcode.toJSON(), buffer, 0, i); + }); + + return buffer.join(''); +} + +function _inspect(opcode: OpcodeJSON, buffer: string[], level: number, index: number) { + let indentation: string[] = []; + + for (let i=0; i `${key}=${opcode.details && opcode.details[key]}`).join(', ')); + } + } + + buffer.push(')'); + } + + buffer.push('\n'); + + if (opcode.children && opcode.children.length) { + for (let i=0; i implements ScannedTemplat let start = builder.start; let end = builder.finalize(); + debugSlice(env, start, end); + compiledStatic = this.compiledStatic = new CompiledStaticTemplate(start, end); } @@ -53,6 +56,10 @@ export class CompilableTemplate implements ScannedTemplat return compiledDynamic; } + + toJSON() { + return { GlimmerDebug: '