diff --git a/packages/@glimmer-workspace/integration-tests/test/compiler/compile-options-test.ts b/packages/@glimmer-workspace/integration-tests/test/compiler/compile-options-test.ts index eb087331de..b88d7a3337 100644 --- a/packages/@glimmer-workspace/integration-tests/test/compiler/compile-options-test.ts +++ b/packages/@glimmer-workspace/integration-tests/test/compiler/compile-options-test.ts @@ -53,8 +53,8 @@ module('[glimmer-compiler] precompile', ({ test }) => { let [statements] = wire.block; let [[, componentNameExpr], ...divExpr] = statements as [ - WireFormat.Statements.Component, - ...WireFormat.Statement[], + WireFormat.Contents.Component, + ...WireFormat.Content[], ]; assert.deepEqual(wire.scope?.(), [hello]); @@ -80,8 +80,8 @@ module('[glimmer-compiler] precompile', ({ test }) => { let [statements] = wire.block; let [[, componentNameExpr], ...divExpr] = statements as [ - WireFormat.Statements.Component, - ...WireFormat.Statement[], + WireFormat.Contents.Component, + ...WireFormat.Content[], ]; assert.deepEqual(wire.scope?.(), [f]); @@ -108,7 +108,7 @@ module('[glimmer-compiler] precompile', ({ test }) => { ); let block: WireFormat.SerializedTemplateBlock = JSON.parse(wire.block); - let [[, componentNameExpr]] = block[0] as [WireFormat.Statements.Component]; + let [[, componentNameExpr]] = block[0] as [WireFormat.Contents.Component]; localAssert( Array.isArray(componentNameExpr) && @@ -131,8 +131,8 @@ module('[glimmer-compiler] precompile', ({ test }) => { let block: WireFormat.SerializedTemplateBlock = JSON.parse(wire.block); - let [[, , letBlock]] = block[0] as [WireFormat.Statements.Let]; - let [[, componentNameExpr]] = letBlock[0] as [WireFormat.Statements.Component]; + let [[, , letBlock]] = block[0] as [WireFormat.Contents.Let]; + let [[, componentNameExpr]] = letBlock[0] as [WireFormat.Contents.Component]; localAssert( Array.isArray(componentNameExpr) && @@ -155,7 +155,7 @@ module('[glimmer-compiler] precompile', ({ test }) => { let block: WireFormat.SerializedTemplateBlock = JSON.parse(wire.block); - let [[, componentNameExpr]] = block[0] as [WireFormat.Statements.ResolvedBlock]; + let [[, componentNameExpr]] = block[0] as [WireFormat.Contents.ResolvedBlock]; localAssert( Array.isArray(componentNameExpr) && @@ -178,7 +178,7 @@ module('[glimmer-compiler] precompile', ({ test }) => { let block: WireFormat.SerializedTemplateBlock = JSON.parse(wire.block); - let [[, componentNameExpr]] = block[0] as [WireFormat.Statements.ResolvedBlock]; + let [[, componentNameExpr]] = block[0] as [WireFormat.Contents.ResolvedBlock]; localAssert( Array.isArray(componentNameExpr) && @@ -214,17 +214,21 @@ module('[glimmer-compiler] precompile', ({ test }) => { test('when "this" in in locals, it compiles to GetLexicalSymbol', (assert) => { let target = { message: 'hello' }; let _wire: ReturnType; - (function() { + (function () { _wire = compile(`{{this.message}}`, ['this'], (source) => eval(source)); - }).call(target) + }).call(target); let wire = _wire!; assert.deepEqual(wire.scope?.(), [target]); - assert.deepEqual(wire.block[0], [[SexpOpcodes.Append,[SexpOpcodes.GetLexicalSymbol,0,["message"]]]]) + assert.deepEqual(wire.block[0], [ + [SexpOpcodes.Append, [SexpOpcodes.GetLexicalSymbol, 0, ['message']]], + ]); }); - test('when "this" is not in locals, it compiles to GetSymbol', (assert) => { + test('when "this" is not in locals, it compiles to GetSymbolOrPath', (assert) => { let wire = compile(`{{this.message}}`, [], (source) => eval(source)); assert.strictEqual(wire.scope, undefined); - assert.deepEqual(wire.block[0], [[SexpOpcodes.Append,[SexpOpcodes.GetSymbol,0,["message"]]]]) + assert.deepEqual(wire.block[0], [ + [SexpOpcodes.Append, [SexpOpcodes.GetLocalSymbol, 0, ['message']]], + ]); }); }); diff --git a/packages/@glimmer/compiler/index.ts b/packages/@glimmer/compiler/index.ts index 72dbcad463..6593b74daf 100644 --- a/packages/@glimmer/compiler/index.ts +++ b/packages/@glimmer/compiler/index.ts @@ -1,14 +1,14 @@ +export { ProgramSymbols } from './lib/builder/builder'; +export { defaultId, precompile, precompileJSON, type PrecompileOptions } from './lib/compiler'; + +// exported only for tests! +export type { BuilderStatement } from './lib/builder/test-support/builder-interface'; export { buildStatement, buildStatements, c, NEWLINE, - ProgramSymbols, s, unicode, -} from './lib/builder/builder'; -export { type BuilderStatement } from './lib/builder/builder-interface'; -export { defaultId, precompile, precompileJSON, type PrecompileOptions } from './lib/compiler'; - -// exported only for tests! +} from './lib/builder/test-support/test-support'; export { default as WireFormatDebugger } from './lib/wire-format-debug'; diff --git a/packages/@glimmer/compiler/lib/builder/builder.ts b/packages/@glimmer/compiler/lib/builder/builder.ts index 4a8a1cfe87..a8b77fc8ea 100644 --- a/packages/@glimmer/compiler/lib/builder/builder.ts +++ b/packages/@glimmer/compiler/lib/builder/builder.ts @@ -1,76 +1,22 @@ -import type { VariableKind } from '@glimmer/constants'; import type { - AttrNamespace, + AppendLexicalOpcode, + AppendResolvedOpcode, CallLexicalOpcode, CallResolvedOpcode, Dict, - DynamicBlockOpcode, Expressions, - GetContextualFreeOpcode, - LexicalBlockComponentOpcode, - Nullable, + LexicalModifierOpcode, Optional, - PresentArray, - ResolvedBlockOpcode, - UnknownInvokeOpcode, + ResolvedModifierOpcode, WireFormat, } from '@glimmer/interfaces'; import type { RequireAtLeastOne, Simplify } from 'type-fest'; -import { - APPEND_EXPR_HEAD, - APPEND_PATH_HEAD, - ARG_VAR, - BLOCK_HEAD, - BLOCK_VAR, - BUILDER_COMMENT, - BUILDER_LITERAL, - CALL_EXPR, - CALL_HEAD, - COMMENT_HEAD, - CONCAT_EXPR, - DYNAMIC_COMPONENT_HEAD, - ELEMENT_HEAD, - FREE_VAR, - GET_PATH_EXPR, - GET_VAR_EXPR, - HAS_BLOCK_EXPR, - HAS_BLOCK_PARAMS_EXPR, - KEYWORD_HEAD, - LITERAL_EXPR, - LITERAL_HEAD, - LOCAL_VAR, - MODIFIER_HEAD, - NS_XLINK, - NS_XML, - NS_XMLNS, - SPLAT_HEAD, - THIS_VAR, -} from '@glimmer/constants'; -import { exhausted, expect, isPresentArray, localAssert } from '@glimmer/debug-util'; +import { localAssert } from '@glimmer/debug-util'; import { LOCAL_DEBUG } from '@glimmer/local-debug-flags'; -import { assertNever, dict, values } from '@glimmer/util'; -import { SexpOpcodes as Op, VariableResolutionContext } from '@glimmer/wire-format'; +import { dict, values } from '@glimmer/util'; +import { SexpOpcodes as Op } from '@glimmer/wire-format'; -import type { - BuilderComment, - BuilderStatement, - NormalizedAttrs, - NormalizedBlock, - NormalizedBlocks, - NormalizedElement, - NormalizedExpression, - NormalizedHash, - NormalizedHead, - NormalizedKeywordStatement, - NormalizedParams, - NormalizedPath, - NormalizedStatement, - Variable, -} from './builder-interface'; - -import { normalizeStatement } from './builder-interface'; - -interface Symbols { +export interface Symbols { top: ProgramSymbols; freeVar(name: string): number; arg(name: string): number; @@ -208,36 +154,10 @@ export interface BuilderGetFree { tail: string[]; } -function unimpl(message: string): Error { - return new Error(`unimplemented ${message}`); -} - -export function buildStatements( - statements: BuilderStatement[], - symbols: Symbols -): WireFormat.Statement[] { - let out: WireFormat.Statement[] = []; - - statements.forEach((s) => out.push(...buildStatement(normalizeStatement(s), symbols))); - - return out; -} - -export function buildNormalizedStatements( - statements: NormalizedStatement[], - symbols: Symbols -): WireFormat.Statement[] { - let out: WireFormat.Statement[] = []; - - statements.forEach((s) => out.push(...buildStatement(s, symbols))); - - return out; -} - export function buildAppend( trusted: boolean, expr: Expressions.Expression -): WireFormat.Statements.SomeAppend { +): WireFormat.Contents.SomeAppend { if (Array.isArray(expr)) { if (expr[0] === Op.GetFreeAsComponentOrHelperHead) { return trusted @@ -257,615 +177,74 @@ export function buildAppend( } } -export function buildStatement( - normalized: NormalizedStatement, - symbols: Symbols = new ProgramSymbols() -): WireFormat.Statement[] { - switch (normalized.kind) { - case APPEND_PATH_HEAD: { - return [buildAppend(normalized.trusted, buildGetPath(normalized.path, symbols))]; - } - - case APPEND_EXPR_HEAD: { - return [ - buildAppend( - normalized.trusted, - buildExpression(normalized.expr, normalized.trusted ? 'TrustedAppend' : 'Append', symbols) - ), - ]; - } - - case CALL_HEAD: { - let { head: path, params, hash, trusted } = normalized; - let builtParams: Optional = params - ? buildParams(params, symbols) - : undefined; - let builtHash: Optional = hash ? buildHash(hash, symbols) : undefined; - let builtExpr = buildCallHead( - path, - trusted - ? VariableResolutionContext.ResolveAsHelperHead - : VariableResolutionContext.ResolveAsComponentOrHelperHead, - symbols - ) as WireFormat.Expressions.GetUnknownAppend; - - const type = headType(builtExpr); - const call: WireFormat.Expressions.SomeInvoke = [ - CALL_TYPES[type], - builtExpr, - buildArgs(builtParams, builtHash), - ]; - - return [ - [ - trusted ? Op.TrustingAppend : type === 'lexical' ? Op.AppendLexical : Op.AppendResolved, - call, - ], - ]; - } - - case LITERAL_HEAD: { - return [[Op.AppendStatic, normalized.value]]; - } - - case COMMENT_HEAD: { - return [[Op.Comment, normalized.value]]; - } - - case BLOCK_HEAD: { - let blocks = buildBlocks(normalized.blocks, normalized.blockParams, symbols); - let hash = buildHash(normalized.hash, symbols); - let params = buildParams(normalized.params, symbols); - let path = buildCallHead( - normalized.head, - VariableResolutionContext.ResolveAsComponentHead, - symbols - ); - - const args = buildBlockArgs(params, hash, blocks, { path }); - - return [[...blockType(path), args]]; - } - - case KEYWORD_HEAD: { - return [buildKeyword(normalized, symbols)]; - } - - case ELEMENT_HEAD: - return buildElement(normalized, symbols); - - case MODIFIER_HEAD: - throw unimpl('modifier'); - - case DYNAMIC_COMPONENT_HEAD: - throw unimpl('dynamic component'); - - default: - assertNever(normalized); - } -} - -export function s( - arr: TemplateStringsArray, - ...interpolated: unknown[] -): [BUILDER_LITERAL, string] { - let result = arr.reduce( - // eslint-disable-next-line @typescript-eslint/no-base-to-string -- @fixme - (result, string, i) => result + `${string}${interpolated[i] ? String(interpolated[i]) : ''}`, - '' - ); - - return [BUILDER_LITERAL, result]; -} - -export function c(arr: TemplateStringsArray, ...interpolated: unknown[]): BuilderComment { - let result = arr.reduce( - // eslint-disable-next-line @typescript-eslint/no-base-to-string -- @fixme - (result, string, i) => result + `${string}${interpolated[i] ? String(interpolated[i]) : ''}`, - '' - ); - - return [BUILDER_COMMENT, result]; -} - -export function unicode(charCode: string): string { - return String.fromCharCode(parseInt(charCode, 16)); -} - -export const NEWLINE = '\n'; - -function buildKeyword( - normalized: NormalizedKeywordStatement, - symbols: Symbols -): WireFormat.Statement { - let { name } = normalized; - let params = buildParams(normalized.params, symbols); - let childSymbols = symbols.child(normalized.blockParams || []); - - let block = buildBlock( - normalized.blocks['default'] as NormalizedBlock, - childSymbols, - childSymbols.paramSymbols - ); - let inverse = normalized.blocks['else'] - ? buildBlock(normalized.blocks['else'], symbols, []) - : null; - - switch (name) { - case 'let': - return [Op.Let, expect(params, 'let requires params'), block]; - case 'if': - return [Op.If, expect(params, 'if requires params')[0], block, inverse]; - case 'each': { - let keyExpr = normalized.hash ? normalized.hash['key'] : null; - let key = keyExpr ? buildExpression(keyExpr, 'Strict', symbols) : null; - return [Op.Each, expect(params, 'if requires params')[0], key, block, inverse]; - } - - default: - throw new Error('unimplemented keyword'); - } -} - -function buildElement( - { name, attrs, block }: NormalizedElement, - symbols: Symbols -): WireFormat.Statement[] { - let out: WireFormat.Statement[] = [ - hasSplat(attrs) ? [Op.OpenElementWithSplat, name] : [Op.OpenElement, name], - ]; - if (attrs) { - let { params, named } = buildElementParams(attrs, symbols); - if (params) out.push(...params); - localAssert(named === undefined, `Can't pass args to a simple element`); - } - out.push([Op.FlushElement]); - - if (Array.isArray(block)) { - block.forEach((s) => out.push(...buildStatement(s, symbols))); - } else { - // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition - localAssert(block === null, `The only remaining type of 'block' is 'null'`); - } - - out.push([Op.CloseElement]); - - return out; -} - -function hasSplat(attrs: Nullable): boolean { - if (attrs === null) return false; - - return Object.keys(attrs).some((a) => attrs[a] === SPLAT_HEAD); -} - -export function buildElementParams( - attrs: NormalizedAttrs, - symbols: Symbols -): { params: Optional; named: Optional } { - let params: Optional; - let keys: string[] = []; - let values: WireFormat.Expression[] = []; - - for (const [key, value] of Object.entries(attrs)) { - if (value === SPLAT_HEAD) { - const statement: WireFormat.ElementParameter = [Op.AttrSplat, symbols.block('&attrs')]; - params = upsert(params, statement); - } else if (key[0] === '@') { - keys.push(key); - values.push(buildExpression(value, 'Strict', symbols)); - } else { - const statements = buildAttributeValue( - key, - value, - // TODO: extract namespace from key - extractNamespace(key), - symbols - ); - - if (statements) { - params = upsert(params, ...statements); - } - } - } - - return { - params, - named: isPresentArray(keys) && isPresentArray(values) ? [keys, values] : undefined, - }; -} - -export function extractNamespace(name: string): Nullable { - if (name === 'xmlns') { - return NS_XMLNS; - } - - let match = /^([^:]*):([^:]*)$/u.exec(name); - - if (match === null) { - return null; - } - - let namespace = match[1]; - - switch (namespace) { - case 'xlink': - return NS_XLINK; - case 'xml': - return NS_XML; - case 'xmlns': - return NS_XMLNS; - } - - return null; -} - -export function buildAttributeValue( - name: string, - value: NormalizedExpression, - namespace: Nullable, - symbols: Symbols -): Optional> { - switch (value.type) { - case LITERAL_EXPR: { - let val = value.value; - - if (val === false) { - return; - } else if (val === true) { - return [[Op.StaticAttr, name, '', namespace ?? undefined]]; - } else if (typeof val === 'string') { - return [[Op.StaticAttr, name, val, namespace ?? undefined]]; - } else { - throw new Error(`Unexpected/unimplemented literal attribute ${JSON.stringify(val)}`); - } - } - - default: - return [ - [ - Op.DynamicAttr, - name, - buildExpression(value, 'AttrValue', symbols), - namespace ?? undefined, - ], - ]; - } -} - -type ExprResolution = - | VariableResolutionContext - | 'Append' - | 'TrustedAppend' - | 'AttrValue' - | 'SubExpression' - | 'Strict'; - -function varContext(context: ExprResolution, bare: boolean): VarResolution { - switch (context) { - case 'Append': - return bare ? 'AppendBare' : 'AppendInvoke'; - case 'TrustedAppend': - return bare ? 'TrustedAppendBare' : 'TrustedAppendInvoke'; - case 'AttrValue': - return bare ? 'AttrValueBare' : 'AttrValueInvoke'; - default: - return context; - } -} - -export function buildExpression( - expr: NormalizedExpression, - context: ExprResolution, - symbols: Symbols -): WireFormat.Expression { - switch (expr.type) { - case GET_PATH_EXPR: { - return buildGetPath(expr, symbols); - } - - case GET_VAR_EXPR: { - return buildVar(expr.variable, varContext(context, true), symbols); - } - - case CONCAT_EXPR: { - return [Op.Concat, buildConcat(expr.params, symbols)]; - } - - case CALL_EXPR: { - let builtParams = buildParams(expr.params, symbols); - let builtHash = buildHash(expr.hash, symbols); - let builtExpr = buildCallHead( - expr.head, - context === 'Strict' ? 'SubExpression' : varContext(context, false), - symbols - ); - - return [CALL_TYPES[headType(builtExpr)], builtExpr, buildArgs(builtParams, builtHash)]; - } - - case HAS_BLOCK_EXPR: { - return [ - Op.HasBlock, - buildVar( - { kind: BLOCK_VAR, name: expr.name, mode: 'loose' }, - VariableResolutionContext.Strict, - symbols - ), - ]; - } - - case HAS_BLOCK_PARAMS_EXPR: { - return [ - Op.HasBlockParams, - buildVar( - { kind: BLOCK_VAR, name: expr.name, mode: 'loose' }, - VariableResolutionContext.Strict, - symbols - ), - ]; - } - - case LITERAL_EXPR: { - if (expr.value === undefined) { - return [Op.Undefined]; - } else { - return expr.value; - } - } - - default: - assertNever(expr); - } -} - -export function buildCallHead( - callHead: NormalizedHead, - context: VarResolution, - symbols: Symbols -): Expressions.GetVar | Expressions.GetPath { - if (callHead.type === GET_VAR_EXPR) { - return buildVar(callHead.variable, context, symbols); - } else { - return buildGetPath(callHead, symbols); - } -} - -export function buildGetPath(head: NormalizedPath, symbols: Symbols): Expressions.GetPath { - return buildVar(head.path.head, VariableResolutionContext.Strict, symbols, head.path.tail); -} - -type VarResolution = - | VariableResolutionContext - | 'AppendBare' - | 'AppendInvoke' - | 'TrustedAppendBare' - | 'TrustedAppendInvoke' - | 'AttrValueBare' - | 'AttrValueInvoke' - | 'SubExpression' - | 'Strict'; - -export function buildVar( - head: Variable, - context: VarResolution, - symbols: Symbols, - path: PresentArray -): Expressions.GetPath; -export function buildVar( - head: Variable, - context: VarResolution, - symbols: Symbols -): Expressions.GetVar; -export function buildVar( - head: Variable, - context: VarResolution, - symbols: Symbols, - path?: PresentArray -): Expressions.GetPath | Expressions.GetVar { - let op: Expressions.GetPath[0] | Expressions.GetVar[0] = Op.GetSymbol; - let sym: number; - switch (head.kind) { - case FREE_VAR: - if (context === 'Strict') { - op = Op.GetStrictKeyword; - } else if (context === 'AppendBare') { - op = Op.GetFreeAsComponentOrHelperHead; - } else if (context === 'AppendInvoke') { - op = Op.GetFreeAsComponentOrHelperHead; - } else if (context === 'TrustedAppendBare') { - op = Op.GetFreeAsHelperHead; - } else if (context === 'TrustedAppendInvoke') { - op = Op.GetFreeAsHelperHead; - } else if (context === 'AttrValueBare') { - op = Op.GetFreeAsHelperHead; - } else if (context === 'AttrValueInvoke') { - op = Op.GetFreeAsHelperHead; - } else if (context === 'SubExpression') { - op = Op.GetFreeAsHelperHead; - } else { - op = expressionContextOp(context); - } - sym = symbols.freeVar(head.name); - break; - default: - op = Op.GetSymbol; - sym = getSymbolForVar(head.kind, symbols, head.name); - } - - if (path === undefined || path.length === 0) { - return [op, sym]; - } else { - localAssert(op !== Op.GetStrictKeyword, '[BUG] keyword with a path'); - return [op, sym, path]; - } -} - -function getSymbolForVar(kind: Exclude, symbols: Symbols, name: string) { - switch (kind) { - case ARG_VAR: - return symbols.arg(name); - case BLOCK_VAR: - return symbols.block(name); - case LOCAL_VAR: - return symbols.local(name); - case THIS_VAR: - return symbols.this(); - default: - return exhausted(kind); - } -} - -export function expressionContextOp(context: VariableResolutionContext): GetContextualFreeOpcode { - switch (context) { - case VariableResolutionContext.Strict: - return Op.GetStrictKeyword; - case VariableResolutionContext.ResolveAsComponentOrHelperHead: - return Op.GetFreeAsComponentOrHelperHead; - case VariableResolutionContext.ResolveAsHelperHead: - return Op.GetFreeAsHelperHead; - case VariableResolutionContext.ResolveAsModifierHead: - return Op.GetFreeAsModifierHead; - case VariableResolutionContext.ResolveAsComponentHead: - return Op.GetFreeAsComponentHead; - default: - return exhausted(context); - } -} - -export function buildParams( - exprs: Nullable, - symbols: Symbols -): Optional { - if (exprs === null || !isPresentArray(exprs)) return; - - return exprs.map((e) => buildExpression(e, 'Strict', symbols)) as WireFormat.Core.ConcatParams; -} - -export function buildConcat( - exprs: [NormalizedExpression, ...NormalizedExpression[]], - symbols: Symbols -): WireFormat.Core.ConcatParams { - return exprs.map((e) => buildExpression(e, 'AttrValue', symbols)) as WireFormat.Core.ConcatParams; -} - -export function buildHash( - exprs: Nullable, - symbols: Symbols -): Optional { - if (exprs === null) return; - - let keys: Optional>; - let values: Optional>; - - for (const [key, value] of Object.entries(exprs)) { - keys = upsert(keys, key); - values = upsert(values, buildExpression(value, 'Strict', symbols)); - } - - return keys && values ? [keys, values] : undefined; -} - -export function buildBlock( - block: NormalizedBlock, - symbols: Symbols, - locals: number[] = [] -): WireFormat.SerializedInlineBlock { - return [buildNormalizedStatements(block, symbols), locals]; -} - -export function buildBlocks( - blocks: NormalizedBlocks, - blockParams: Nullable, - parent: Symbols -): Optional { - let keys: Optional>; - let values: Optional>; - - for (const [name, block] of Object.entries(blocks)) { - keys = upsert(keys, name); - - if (name === 'default') { - let symbols = parent.child(blockParams || []); - - values = upsert(values, buildBlock(block, symbols, symbols.paramSymbols)); - } else { - values = upsert(values, buildBlock(block, parent, [])); - } - } - - return keys && values ? [keys, values] : undefined; -} - -/** - * Returns true if the expression is a call with a simple name (i.e. `(hello world)` or - * `{{hello world}}`) and the name needs to be resolved via the resolver. - * - * If this function returns `false`, then it either has a non-simple callee (i.e. `this.hello`, - * `@hello` or a nested `(hello)`) and we don't need special resolution machinery to invoke it. - */ -export function invokeType( - expr: Expressions.Expression -): CallLexicalOpcode | CallResolvedOpcode | UnknownInvokeOpcode { - if (!Array.isArray(expr)) - throw Error('Something is suspicious (expected invoke type to be an array) @fixme'); - - let type = expr[0]; - - switch (type) { - case Op.GetLexicalSymbol: - case Op.GetSymbol: - return Op.CallLexical; - case Op.GetFreeAsComponentOrHelperHead: - return Op.UnknownInvoke; - case Op.GetFreeAsHelperHead: - case Op.GetStrictKeyword: - return Op.CallResolved; - default: - throw Error(`Something is suspicious (unexpected ${type} opcode in append) @fixme`); - } -} - -type CallType = 'lexical' | 'resolved' | 'keyword'; +type CallType = + /** + * Dynamic means the value is dynamic (i.e. a local variable or expression and has no special + * opcode compilation behavior). + * + * Resolved or lexical values have special cases in the opcode compiler since their value is known + * at opcode compilation time. As a result, their compiled templates can be specialized based on + * their specified capabilities. + */ + | 'dynamic' + /** + * Resolver means that the value needs to be resolved via the resolver (i.e. it's a "resolved + * component" or a "resolved helper"). + */ + | 'resolver' + /** + * Lexical means a reference to the JavaScript environment outside of the template. + */ + | 'lexical' + /** + * Keyword means that the callee is a keyword (e.g. `{{if ...}}` or `(if ...)`). + */ + | 'keyword'; export const APPEND_TYPES = { - resolved: Op.AppendResolved, + resolver: Op.AppendResolved, keyword: Op.AppendResolved, lexical: Op.AppendLexical, -}; + dynamic: Op.AppendLexical, +} satisfies Record; export const CALL_TYPES = { - resolved: Op.CallResolved, + resolver: Op.CallResolved, keyword: Op.CallResolved, lexical: Op.CallLexical, -}; + dynamic: Op.CallLexical, +} satisfies Record; export const MODIFIER_TYPES = { - resolved: Op.ResolvedModifier, + resolver: Op.ResolvedModifier, keyword: Op.ResolvedModifier, lexical: Op.LexicalModifier, -}; - -export function headType(expr: Expressions.Expression): CallType { + dynamic: Op.LexicalModifier, +} satisfies Record; + +const HEAD_TYPES_MAP = { + [Op.GetLexicalSymbol]: 'lexical', + [Op.CallResolved]: 'dynamic', + [Op.GetLocalSymbol]: 'dynamic', + [Op.GetFreeAsComponentOrHelperHead]: 'resolver', + [Op.GetFreeAsHelperHead]: 'resolver', + [Op.GetFreeAsModifierHead]: 'resolver', + [Op.Curry]: 'dynamic', + [Op.IfInline]: 'dynamic', +} as const; + +type HeadType = keyof typeof HEAD_TYPES_MAP; + +export function headType(expr: Expressions.Expression, from: string): CallType { if (!Array.isArray(expr)) { throw Error('Something is suspicious @fixme'); } let type = expr[0]; - switch (type) { - case Op.GetFreeAsHelperHead: - case Op.GetFreeAsModifierHead: - case Op.GetFreeAsComponentHead: - case Op.GetFreeAsComponentOrHelperHead: - return 'resolved'; + localAssert(type in HEAD_TYPES_MAP, `Unexpected opcode ${type} in ${from}`); - case Op.GetStrictKeyword: - return 'keyword'; - - default: - return 'lexical'; - } + return HEAD_TYPES_MAP[type as HeadType]; } export function buildComponentArgs( @@ -880,54 +259,6 @@ export function buildComponentArgs( }); } -export function buildBlockArgs( - params: Optional, - rawHash: Optional, - blocks: Optional, - { path }: { path: WireFormat.Core.Expression } -): Optional { - const hash: Optional = - isGet(path) && needsAtNames(path) ? addAtNames(rawHash) : rawHash; - - return compact({ - params, - hash, - blocks, - }); -} - -function addAtNames(hash: Optional): Optional { - if (!hash) return; - - const [keys, values] = hash; - - return [keys.map((key) => `@${key}`) as PresentArray, values]; -} - -function buildArgs( - params: Optional, - hash: Optional -): Optional { - if (!params && !hash) return undefined; - - const args: Partial = {}; - - if (params) args.params = params; - if (hash) args.hash = hash; - - return args as WireFormat.Core.Args; -} - -export function upsert(array: Optional>, ...values: PresentArray) { - if (array) { - array.push(...values); - } else { - array = [...values]; - } - - return array; -} - type CompactObject = Simplify< RequireAtLeastOne< { @@ -995,7 +326,7 @@ export function isGetPath(path: Expressions.Expression): path is WireFormat.Expr if (!Array.isArray(path) || path.length !== 3) return false; switch (path[0]) { - case Op.GetSymbol: + case Op.GetLocalSymbol: case Op.GetLexicalSymbol: return true; default: @@ -1019,17 +350,17 @@ export function isInvokeResolved( return expr[0] === Op.CallResolved; } -export function isGetSymbol( +export function isGetSymbolOrPath( path: Expressions.Expression -): path is WireFormat.Expressions.GetSymbol | WireFormat.Expressions.GetPathSymbol { - return isTupleExpression(path) && path[0] === Op.GetSymbol; +): path is WireFormat.Expressions.GetLocalSymbol | WireFormat.Expressions.GetPathSymbol { + return isTupleExpression(path) && path[0] === Op.GetLocalSymbol; } export function isGetVar(path: Expressions.Expression): path is WireFormat.Expressions.GetVar[0] { if (!Array.isArray(path) || path.length !== 2) return false; switch (path[0]) { - case Op.GetSymbol: + case Op.GetLocalSymbol: case Op.GetLexicalSymbol: case Op.GetStrictKeyword: return true; @@ -1046,7 +377,7 @@ export function isTupleExpression( export function isGetContextualFree( path: Expressions.TupleExpression -): path is WireFormat.Expressions.GetContextualFree { +): path is WireFormat.Expressions.GetResolved { switch (path[0]) { case Op.GetFreeAsComponentOrHelperHead: case Op.GetFreeAsHelperHead: @@ -1071,18 +402,3 @@ export function assertGet(expr: Expressions.Expression): asserts expr is Express export function needsAtNames(path: Expressions.Get): boolean { return isGetLexical(path) || path.length === 2; } - -export function blockType( - path: Expressions.Get -): - | [LexicalBlockComponentOpcode, WireFormat.Expressions.GetLexicalSymbol] - | [ResolvedBlockOpcode, WireFormat.Expressions.GetContextualFree] - | [DynamicBlockOpcode, WireFormat.Expressions.GetPath] { - if (isGetLexical(path)) { - return [Op.LexicalBlockComponent, path]; - } else if (path.length === 2) { - return [Op.ResolvedBlock, path as WireFormat.Expressions.GetContextualFree]; - } else { - return [Op.DynamicBlock, path]; - } -} diff --git a/packages/@glimmer/compiler/lib/builder/builder-interface.ts b/packages/@glimmer/compiler/lib/builder/test-support/builder-interface.ts similarity index 100% rename from packages/@glimmer/compiler/lib/builder/builder-interface.ts rename to packages/@glimmer/compiler/lib/builder/test-support/builder-interface.ts diff --git a/packages/@glimmer/compiler/lib/builder/test-support/test-support.ts b/packages/@glimmer/compiler/lib/builder/test-support/test-support.ts new file mode 100644 index 0000000000..3e358254a3 --- /dev/null +++ b/packages/@glimmer/compiler/lib/builder/test-support/test-support.ts @@ -0,0 +1,740 @@ +import type { VariableKind } from '@glimmer/constants'; +import type { + AttrNamespace, + CallLexicalOpcode, + CallResolvedOpcode, + DynamicBlockOpcode, + Expressions, + GetResolvedOrKeywordOpcode, + InvokeLexicalComponentOpcode, + Nullable, + Optional, + PresentArray, + ResolvedBlockOpcode, + UnknownInvokeOpcode, + WireFormat, +} from '@glimmer/interfaces'; +import { + APPEND_EXPR_HEAD, + APPEND_PATH_HEAD, + ARG_VAR, + BLOCK_HEAD, + BLOCK_VAR, + BUILDER_COMMENT, + BUILDER_LITERAL, + CALL_EXPR, + CALL_HEAD, + COMMENT_HEAD, + CONCAT_EXPR, + DYNAMIC_COMPONENT_HEAD, + ELEMENT_HEAD, + FREE_VAR, + GET_PATH_EXPR, + GET_VAR_EXPR, + HAS_BLOCK_EXPR, + HAS_BLOCK_PARAMS_EXPR, + KEYWORD_HEAD, + LITERAL_EXPR, + LITERAL_HEAD, + LOCAL_VAR, + MODIFIER_HEAD, + NS_XLINK, + NS_XML, + NS_XMLNS, + SPLAT_HEAD, + THIS_VAR, +} from '@glimmer/constants'; +import { exhausted, expect, isPresentArray, localAssert } from '@glimmer/debug-util'; +import { assertNever } from '@glimmer/util'; +import { isGetLexical, SexpOpcodes as Op, VariableResolutionContext } from '@glimmer/wire-format'; + +import type { Symbols } from '../builder'; +import type { + BuilderComment, + BuilderStatement, + NormalizedAttrs, + NormalizedBlock, + NormalizedBlocks, + NormalizedElement, + NormalizedExpression, + NormalizedHash, + NormalizedHead, + NormalizedKeywordStatement, + NormalizedParams, + NormalizedPath, + NormalizedStatement, + Variable, +} from './builder-interface'; + +import { + buildAppend, + CALL_TYPES, + compact, + headType, + isGet, + needsAtNames, + ProgramSymbols, +} from '../builder'; +import { normalizeStatement } from './builder-interface'; + +export function buildStatements( + statements: BuilderStatement[], + symbols: Symbols +): WireFormat.Content[] { + let out: WireFormat.Content[] = []; + + statements.forEach((s) => out.push(...buildStatement(normalizeStatement(s), symbols))); + + return out; +} + +export function buildNormalizedStatements( + statements: NormalizedStatement[], + symbols: Symbols +): WireFormat.Content[] { + let out: WireFormat.Content[] = []; + + statements.forEach((s) => out.push(...buildStatement(s, symbols))); + + return out; +} + +export function buildStatement( + normalized: NormalizedStatement, + symbols: Symbols = new ProgramSymbols() +): WireFormat.Content[] { + switch (normalized.kind) { + case APPEND_PATH_HEAD: { + return [buildAppend(normalized.trusted, buildGetPath(normalized.path, symbols))]; + } + + case APPEND_EXPR_HEAD: { + return [ + buildAppend( + normalized.trusted, + buildExpression(normalized.expr, normalized.trusted ? 'TrustedAppend' : 'Append', symbols) + ), + ]; + } + + case CALL_HEAD: { + let { head: path, params, hash, trusted } = normalized; + let builtParams: Optional = params + ? buildParams(params, symbols) + : undefined; + let builtHash: Optional = hash ? buildHash(hash, symbols) : undefined; + let builtExpr = buildCallHead( + path, + trusted + ? VariableResolutionContext.ResolveAsHelperHead + : VariableResolutionContext.ResolveAsComponentOrHelperHead, + symbols + ) as WireFormat.Expressions.GetUnknownAppend; + + const type = headType(builtExpr, 'stmt:call-head'); + const call: WireFormat.Expressions.SomeInvoke = [ + CALL_TYPES[type], + builtExpr, + buildArgs(builtParams, builtHash), + ]; + + return [ + [ + trusted ? Op.TrustingAppend : type === 'lexical' ? Op.AppendLexical : Op.AppendResolved, + call, + ], + ]; + } + + case LITERAL_HEAD: { + return [[Op.AppendStatic, normalized.value]]; + } + + case COMMENT_HEAD: { + return [[Op.Comment, normalized.value]]; + } + + case BLOCK_HEAD: { + let blocks = buildBlocks(normalized.blocks, normalized.blockParams, symbols); + let hash = buildHash(normalized.hash, symbols); + let params = buildParams(normalized.params, symbols); + let path = buildCallHead( + normalized.head, + VariableResolutionContext.ResolveAsComponentHead, + symbols + ); + + const args = buildBlockArgs(params, hash, blocks, { path }); + + return [[...blockType(path), args]]; + } + + case KEYWORD_HEAD: { + return [buildKeyword(normalized, symbols)]; + } + + case ELEMENT_HEAD: + return buildElement(normalized, symbols); + + case MODIFIER_HEAD: + throw unimpl('modifier'); + + case DYNAMIC_COMPONENT_HEAD: + throw unimpl('dynamic component'); + + default: + assertNever(normalized); + } +} + +export function s( + arr: TemplateStringsArray, + ...interpolated: unknown[] +): [BUILDER_LITERAL, string] { + let result = arr.reduce( + // eslint-disable-next-line @typescript-eslint/no-base-to-string -- @fixme + (result, string, i) => result + `${string}${interpolated[i] ? String(interpolated[i]) : ''}`, + '' + ); + + return [BUILDER_LITERAL, result]; +} + +export function c(arr: TemplateStringsArray, ...interpolated: unknown[]): BuilderComment { + let result = arr.reduce( + // eslint-disable-next-line @typescript-eslint/no-base-to-string -- @fixme + (result, string, i) => result + `${string}${interpolated[i] ? String(interpolated[i]) : ''}`, + '' + ); + + return [BUILDER_COMMENT, result]; +} + +function buildKeyword( + normalized: NormalizedKeywordStatement, + symbols: Symbols +): WireFormat.Content { + let { name } = normalized; + let params = buildParams(normalized.params, symbols); + let childSymbols = symbols.child(normalized.blockParams || []); + + let block = buildBlock( + normalized.blocks['default'] as NormalizedBlock, + childSymbols, + childSymbols.paramSymbols + ); + let inverse = normalized.blocks['else'] + ? buildBlock(normalized.blocks['else'], symbols, []) + : null; + + switch (name) { + case 'let': + return [Op.Let, expect(params, 'let requires params'), block]; + case 'if': + return [Op.If, expect(params, 'if requires params')[0], block, inverse]; + case 'each': { + let keyExpr = normalized.hash ? normalized.hash['key'] : null; + let key = keyExpr ? buildExpression(keyExpr, 'Strict', symbols) : null; + return [Op.Each, expect(params, 'if requires params')[0], key, block, inverse]; + } + + default: + throw new Error('unimplemented keyword'); + } +} + +function buildElement( + { name, attrs, block }: NormalizedElement, + symbols: Symbols +): WireFormat.Content[] { + let out: WireFormat.Content[] = [ + hasSplat(attrs) ? [Op.OpenElementWithSplat, name] : [Op.OpenElement, name], + ]; + if (attrs) { + let { params, named } = buildElementParams(attrs, symbols); + if (params) out.push(...params); + localAssert(named === undefined, `Can't pass args to a simple element`); + } + out.push([Op.FlushElement]); + + if (Array.isArray(block)) { + block.forEach((s) => out.push(...buildStatement(s, symbols))); + } else { + // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition + localAssert(block === null, `The only remaining type of 'block' is 'null'`); + } + + out.push([Op.CloseElement]); + + return out; +} + +function hasSplat(attrs: Nullable): boolean { + if (attrs === null) return false; + + return Object.keys(attrs).some((a) => attrs[a] === SPLAT_HEAD); +} + +export function buildElementParams( + attrs: NormalizedAttrs, + symbols: Symbols +): { params: Optional; named: Optional } { + let params: Optional; + let keys: string[] = []; + let values: WireFormat.Expression[] = []; + + for (const [key, value] of Object.entries(attrs)) { + if (value === SPLAT_HEAD) { + const statement: WireFormat.ElementParameter = [Op.AttrSplat, symbols.block('&attrs')]; + params = upsert(params, statement); + } else if (key[0] === '@') { + keys.push(key); + values.push(buildExpression(value, 'Strict', symbols)); + } else { + const statements = buildAttributeValue( + key, + value, + // TODO: extract namespace from key + extractNamespace(key), + symbols + ); + + if (statements) { + params = upsert(params, ...statements); + } + } + } + + return { + params, + named: isPresentArray(keys) && isPresentArray(values) ? [keys, values] : undefined, + }; +} + +export function extractNamespace(name: string): Nullable { + if (name === 'xmlns') { + return NS_XMLNS; + } + + let match = /^([^:]*):([^:]*)$/u.exec(name); + + if (match === null) { + return null; + } + + let namespace = match[1]; + + switch (namespace) { + case 'xlink': + return NS_XLINK; + case 'xml': + return NS_XML; + case 'xmlns': + return NS_XMLNS; + } + + return null; +} + +export function buildAttributeValue( + name: string, + value: NormalizedExpression, + namespace: Nullable, + symbols: Symbols +): Optional> { + switch (value.type) { + case LITERAL_EXPR: { + let val = value.value; + + if (val === false) { + return; + } else if (val === true) { + return [[Op.StaticAttr, name, '', namespace ?? undefined]]; + } else if (typeof val === 'string') { + return [[Op.StaticAttr, name, val, namespace ?? undefined]]; + } else { + throw new Error(`Unexpected/unimplemented literal attribute ${JSON.stringify(val)}`); + } + } + + default: + return [ + [ + Op.DynamicAttr, + name, + buildExpression(value, 'AttrValue', symbols), + namespace ?? undefined, + ], + ]; + } +} + +export function buildExpression( + expr: NormalizedExpression, + context: ExprResolution, + symbols: Symbols +): WireFormat.Expression { + switch (expr.type) { + case GET_PATH_EXPR: { + return buildGetPath(expr, symbols); + } + + case GET_VAR_EXPR: { + return buildVar(expr.variable, varContext(context, true), symbols); + } + + case CONCAT_EXPR: { + return [Op.Concat, buildConcat(expr.params, symbols)]; + } + + case CALL_EXPR: { + let builtParams = buildParams(expr.params, symbols); + let builtHash = buildHash(expr.hash, symbols); + let builtExpr = buildCallHead( + expr.head, + context === 'Strict' ? 'SubExpression' : varContext(context, false), + symbols + ); + + return [ + CALL_TYPES[headType(builtExpr, 'stmt:call-expr')], + builtExpr, + buildArgs(builtParams, builtHash), + ]; + } + + case HAS_BLOCK_EXPR: { + return [ + Op.HasBlock, + buildVar( + { kind: BLOCK_VAR, name: expr.name, mode: 'loose' }, + VariableResolutionContext.Strict, + symbols + ), + ]; + } + + case HAS_BLOCK_PARAMS_EXPR: { + return [ + Op.HasBlockParams, + buildVar( + { kind: BLOCK_VAR, name: expr.name, mode: 'loose' }, + VariableResolutionContext.Strict, + symbols + ), + ]; + } + + case LITERAL_EXPR: { + if (expr.value === undefined) { + return [Op.Undefined]; + } else { + return expr.value; + } + } + + default: + assertNever(expr); + } +} + +export function buildCallHead( + callHead: NormalizedHead, + context: VarResolution, + symbols: Symbols +): Expressions.GetVar | Expressions.GetPath { + if (callHead.type === GET_VAR_EXPR) { + return buildVar(callHead.variable, context, symbols); + } else { + return buildGetPath(callHead, symbols); + } +} + +function varContext(context: ExprResolution, bare: boolean): VarResolution { + switch (context) { + case 'Append': + return bare ? 'AppendBare' : 'AppendInvoke'; + case 'TrustedAppend': + return bare ? 'TrustedAppendBare' : 'TrustedAppendInvoke'; + case 'AttrValue': + return bare ? 'AttrValueBare' : 'AttrValueInvoke'; + default: + return context; + } +} + +export function buildVar( + head: Variable, + context: VarResolution, + symbols: Symbols, + path: PresentArray +): Expressions.GetPath; +export function buildVar( + head: Variable, + context: VarResolution, + symbols: Symbols +): Expressions.GetVar; +export function buildVar( + head: Variable, + context: VarResolution, + symbols: Symbols, + path?: PresentArray +): Expressions.GetPath | Expressions.GetVar { + let op: Expressions.GetPath[0] | Expressions.GetVar[0] = Op.GetLocalSymbol; + let sym: number; + switch (head.kind) { + case FREE_VAR: + if (context === 'Strict') { + op = Op.GetStrictKeyword; + } else if (context === 'AppendBare') { + op = Op.GetFreeAsComponentOrHelperHead; + } else if (context === 'AppendInvoke') { + op = Op.GetFreeAsComponentOrHelperHead; + } else if (context === 'TrustedAppendBare') { + op = Op.GetFreeAsHelperHead; + } else if (context === 'TrustedAppendInvoke') { + op = Op.GetFreeAsHelperHead; + } else if (context === 'AttrValueBare') { + op = Op.GetFreeAsHelperHead; + } else if (context === 'AttrValueInvoke') { + op = Op.GetFreeAsHelperHead; + } else if (context === 'SubExpression') { + op = Op.GetFreeAsHelperHead; + } else { + op = expressionContextOp(context); + } + sym = symbols.freeVar(head.name); + break; + default: + op = Op.GetLocalSymbol; + sym = getSymbolForVar(head.kind, symbols, head.name); + } + + if (path === undefined || path.length === 0) { + return [op, sym]; + } else { + localAssert(op !== Op.GetStrictKeyword, '[BUG] keyword with a path'); + return [op, sym, path]; + } +} + +function getSymbolForVar(kind: Exclude, symbols: Symbols, name: string) { + switch (kind) { + case ARG_VAR: + return symbols.arg(name); + case BLOCK_VAR: + return symbols.block(name); + case LOCAL_VAR: + return symbols.local(name); + case THIS_VAR: + return symbols.this(); + default: + return exhausted(kind); + } +} + +export function expressionContextOp(context: VariableResolutionContext): GetResolvedOrKeywordOpcode { + switch (context) { + case VariableResolutionContext.Strict: + return Op.GetStrictKeyword; + case VariableResolutionContext.ResolveAsComponentOrHelperHead: + return Op.GetFreeAsComponentOrHelperHead; + case VariableResolutionContext.ResolveAsHelperHead: + return Op.GetFreeAsHelperHead; + case VariableResolutionContext.ResolveAsModifierHead: + return Op.GetFreeAsModifierHead; + case VariableResolutionContext.ResolveAsComponentHead: + return Op.GetFreeAsComponentHead; + default: + return exhausted(context); + } +} + +type ExprResolution = + | VariableResolutionContext + | 'Append' + | 'TrustedAppend' + | 'AttrValue' + | 'SubExpression' + | 'Strict'; + +type VarResolution = + | VariableResolutionContext + | 'AppendBare' + | 'AppendInvoke' + | 'TrustedAppendBare' + | 'TrustedAppendInvoke' + | 'AttrValueBare' + | 'AttrValueInvoke' + | 'SubExpression' + | 'Strict'; + +export function buildParams( + exprs: Nullable, + symbols: Symbols +): Optional { + if (exprs === null || !isPresentArray(exprs)) return; + + return exprs.map((e) => buildExpression(e, 'Strict', symbols)) as WireFormat.Core.ConcatParams; +} + +export function buildConcat( + exprs: [NormalizedExpression, ...NormalizedExpression[]], + symbols: Symbols +): WireFormat.Core.ConcatParams { + return exprs.map((e) => buildExpression(e, 'AttrValue', symbols)) as WireFormat.Core.ConcatParams; +} + +export function buildHash( + exprs: Nullable, + symbols: Symbols +): Optional { + if (exprs === null) return; + + let keys: Optional>; + let values: Optional>; + + for (const [key, value] of Object.entries(exprs)) { + keys = upsert(keys, key); + values = upsert(values, buildExpression(value, 'Strict', symbols)); + } + + return keys && values ? [keys, values] : undefined; +} + +export function buildBlock( + block: NormalizedBlock, + symbols: Symbols, + locals: number[] = [] +): WireFormat.SerializedInlineBlock { + return [buildNormalizedStatements(block, symbols), locals]; +} + +export function buildBlocks( + blocks: NormalizedBlocks, + blockParams: Nullable, + parent: Symbols +): Optional { + let keys: Optional>; + let values: Optional>; + + for (const [name, block] of Object.entries(blocks)) { + keys = upsert(keys, name); + + if (name === 'default') { + let symbols = parent.child(blockParams || []); + + values = upsert(values, buildBlock(block, symbols, symbols.paramSymbols)); + } else { + values = upsert(values, buildBlock(block, parent, [])); + } + } + + return keys && values ? [keys, values] : undefined; +} + +/** + * Returns true if the expression is a call with a simple name (i.e. `(hello world)` or + * `{{hello world}}`) and the name needs to be resolved via the resolver. + * + * If this function returns `false`, then it either has a non-simple callee (i.e. `this.hello`, + * `@hello` or a nested `(hello)`) and we don't need special resolution machinery to invoke it. + */ +export function invokeType( + expr: Expressions.Expression +): CallLexicalOpcode | CallResolvedOpcode | UnknownInvokeOpcode { + if (!Array.isArray(expr)) + throw Error('Something is suspicious (expected invoke type to be an array) @fixme'); + + let type = expr[0]; + + switch (type) { + case Op.GetLexicalSymbol: + case Op.GetLocalSymbol: + return Op.CallLexical; + case Op.GetFreeAsComponentOrHelperHead: + return Op.UnknownInvoke; + case Op.GetFreeAsHelperHead: + case Op.GetStrictKeyword: + return Op.CallResolved; + default: + throw Error(`Something is suspicious (unexpected ${type} opcode in append) @fixme`); + } +} + +export function buildBlockArgs( + params: Optional, + rawHash: Optional, + blocks: Optional, + { path }: { path: WireFormat.Core.Expression } +): Optional { + const hash: Optional = + isGet(path) && needsAtNames(path) ? addAtNames(rawHash) : rawHash; + + return compact({ + params, + hash, + blocks, + }); +} + +function addAtNames(hash: Optional): Optional { + if (!hash) return; + + const [keys, values] = hash; + + return [keys.map((key) => `@${key}`) as PresentArray, values]; +} + +function buildArgs( + params: Optional, + hash: Optional +): Optional { + if (!params && !hash) return undefined; + + const args: Partial = {}; + + if (params) args.params = params; + if (hash) args.hash = hash; + + return args as WireFormat.Core.Args; +} + +export function buildGetPath(head: NormalizedPath, symbols: Symbols): Expressions.GetPath { + return buildVar(head.path.head, VariableResolutionContext.Strict, symbols, head.path.tail); +} + +function unimpl(message: string): Error { + return new Error(`unimplemented ${message}`); +} + +export function unicode(charCode: string): string { + return String.fromCharCode(parseInt(charCode, 16)); +} + +export function upsert(array: Optional>, ...values: PresentArray) { + if (array) { + array.push(...values); + } else { + array = [...values]; + } + + return array; +} + +export function blockType( + path: Expressions.Get +): + | [InvokeLexicalComponentOpcode, WireFormat.Expressions.GetLexicalSymbol] + | [ResolvedBlockOpcode, WireFormat.Expressions.GetResolved] + | [DynamicBlockOpcode, WireFormat.Expressions.GetPath] { + if (isGetLexical(path)) { + return [Op.InvokeLexicalComponent, path]; + } else if (path.length === 2) { + return [Op.ResolvedBlock, path as WireFormat.Expressions.GetResolved]; + } else { + return [Op.DynamicBlock, path]; + } +} + +export const NEWLINE = '\n'; diff --git a/packages/@glimmer/compiler/lib/passes/1-normalization/context.ts b/packages/@glimmer/compiler/lib/passes/1-normalization/context.ts index 8bf73e8394..4f15a1053f 100644 --- a/packages/@glimmer/compiler/lib/passes/1-normalization/context.ts +++ b/packages/@glimmer/compiler/lib/passes/1-normalization/context.ts @@ -28,7 +28,7 @@ export class NormalizationState { return this._currentScope; } - visitBlock(block: ASTv2.Block): Result> { + visitBlock(block: ASTv2.Block): Result> { let oldBlock = this._currentScope; this._currentScope = block.scope; diff --git a/packages/@glimmer/compiler/lib/passes/1-normalization/keywords/append.ts b/packages/@glimmer/compiler/lib/passes/1-normalization/keywords/append.ts index 6f232571a7..81a595d828 100644 --- a/packages/@glimmer/compiler/lib/passes/1-normalization/keywords/append.ts +++ b/packages/@glimmer/compiler/lib/passes/1-normalization/keywords/append.ts @@ -61,7 +61,7 @@ export const APPEND_KEYWORDS = keywords('Append') target: src.SourceSlice; positional: ASTv2.PositionalArguments; } - ): Result { + ): Result { return VISIT_EXPRS.Positional(positional, state).mapOk( (positional) => new mir.Yield({ @@ -97,7 +97,7 @@ export const APPEND_KEYWORDS = keywords('Append') }: { node: ASTv2.AppendContent; state: NormalizationState; - }): Result { + }): Result { return Ok(new mir.Debugger({ loc: node.loc, scope })); }, }) @@ -107,19 +107,38 @@ export const APPEND_KEYWORDS = keywords('Append') translate( { node, state }: { node: ASTv2.AppendContent; state: NormalizationState }, { definition, args }: { definition: ASTv2.ExpressionNode; args: ASTv2.Args } - ): Result { + ): Result { let definitionResult = VISIT_EXPRS.visit(definition, state); let argsResult = VISIT_EXPRS.Args(args, state); - return Result.all(definitionResult, argsResult).mapOk( - ([definition, args]) => - new mir.InvokeComponent({ + return Result.all(definitionResult, argsResult).andThen(([definition, args]) => { + if (definition.type === 'Literal') { + if (typeof definition.value !== 'string') { + return Err( + generateSyntaxError( + `Expected literal component name to be a string, but received ${definition.value}`, + definition.loc + ) + ); + } + + return Ok( + new mir.InvokeResolvedComponentKeyword({ + loc: node.loc, + definition: definition.value, + args, + }) + ); + } + + return Ok( + new mir.InvokeComponentKeyword({ loc: node.loc, definition, args, - blocks: null, }) - ); + ); + }); }, }) .kw('helper', { diff --git a/packages/@glimmer/compiler/lib/passes/1-normalization/keywords/block.ts b/packages/@glimmer/compiler/lib/passes/1-normalization/keywords/block.ts index fabb323dd7..1d5700636d 100644 --- a/packages/@glimmer/compiler/lib/passes/1-normalization/keywords/block.ts +++ b/packages/@glimmer/compiler/lib/passes/1-normalization/keywords/block.ts @@ -132,7 +132,7 @@ export const BLOCK_KEYWORDS = keywords('Block') translate( { node, state }: { node: ASTv2.InvokeBlock; state: NormalizationState }, { condition }: { condition: ASTv2.ExpressionNode } - ): Result { + ): Result { let block = node.blocks.get('default'); let inverse = node.blocks.get('else'); @@ -142,7 +142,7 @@ export const BLOCK_KEYWORDS = keywords('Block') return Result.all(conditionResult, blockResult, inverseResult).mapOk( ([condition, block, inverse]) => - new mir.If({ + new mir.IfContent({ loc: node.loc, condition, block, @@ -194,7 +194,7 @@ export const BLOCK_KEYWORDS = keywords('Block') translate( { node, state }: { node: ASTv2.InvokeBlock; state: NormalizationState }, { condition }: { condition: ASTv2.ExpressionNode } - ): Result { + ): Result { let block = node.blocks.get('default'); let inverse = node.blocks.get('else'); @@ -204,7 +204,7 @@ export const BLOCK_KEYWORDS = keywords('Block') return Result.all(conditionResult, blockResult, inverseResult).mapOk( ([condition, block, inverse]) => - new mir.If({ + new mir.IfContent({ loc: node.loc, condition: new mir.Not({ value: condition, loc: node.loc }), block, @@ -367,19 +367,42 @@ export const BLOCK_KEYWORDS = keywords('Block') translate( { node, state }: { node: ASTv2.InvokeBlock; state: NormalizationState }, { definition, args }: { definition: ASTv2.ExpressionNode; args: ASTv2.Args } - ): Result { + ): Result { let definitionResult = VISIT_EXPRS.visit(definition, state); let argsResult = VISIT_EXPRS.Args(args, state); let blocksResult = VISIT_STMTS.NamedBlocks(node.blocks, state); - return Result.all(definitionResult, argsResult, blocksResult).mapOk( - ([definition, args, blocks]) => - new mir.InvokeComponent({ - loc: node.loc, - definition, - args, - blocks, - }) + return Result.all(definitionResult, argsResult, blocksResult).andThen( + ([definition, args, blocks]) => { + if (definition.type === 'Literal') { + if (typeof definition.value !== 'string') { + return Err( + generateSyntaxError( + `Expected literal component name to be a string, but received ${definition.value}`, + definition.loc + ) + ); + } + + return Ok( + new mir.InvokeResolvedComponentKeyword({ + loc: node.loc, + definition: definition.value, + args, + blocks, + }) + ); + } + + return Ok( + new mir.InvokeComponentKeyword({ + loc: node.loc, + definition, + args, + blocks, + }) + ); + } ); }, }); diff --git a/packages/@glimmer/compiler/lib/passes/1-normalization/keywords/impl.ts b/packages/@glimmer/compiler/lib/passes/1-normalization/keywords/impl.ts index e27e65cf4f..f1acbecacd 100644 --- a/packages/@glimmer/compiler/lib/passes/1-normalization/keywords/impl.ts +++ b/packages/@glimmer/compiler/lib/passes/1-normalization/keywords/impl.ts @@ -48,7 +48,7 @@ class KeywordImpl< let path = getCalleeExpression(node); - if (path !== null && path.type === 'Path' && path.ref.type === 'Free') { + if (path !== null && path.type === 'Path' && path.ref.type === 'Resolved') { return path.ref.name === this.keyword; } else { return false; @@ -170,7 +170,7 @@ export class Keywords = ne let path = getCalleeExpression(node); - if (path && path.type === 'Path' && path.ref.type === 'Free' && isKeyword(path.ref.name)) { + if (path && path.type === 'Path' && path.ref.type === 'Resolved' && isKeyword(path.ref.name)) { let { name } = path.ref as { name: keyof typeof KEYWORDS_TYPES }; let usedType = this._type; diff --git a/packages/@glimmer/compiler/lib/passes/1-normalization/keywords/utils/if-unless.ts b/packages/@glimmer/compiler/lib/passes/1-normalization/keywords/utils/if-unless.ts index 756d554ceb..3c9421f327 100644 --- a/packages/@glimmer/compiler/lib/passes/1-normalization/keywords/utils/if-unless.ts +++ b/packages/@glimmer/compiler/lib/passes/1-normalization/keywords/utils/if-unless.ts @@ -94,7 +94,7 @@ function translateIfUnlessInlineKeyword(type: string) { truthy: ASTv2.ExpressionNode; falsy: ASTv2.ExpressionNode | null; } - ): Result => { + ): Result => { let conditionResult = VISIT_EXPRS.visit(condition, state); let truthyResult = VISIT_EXPRS.visit(truthy, state); let falsyResult = falsy ? VISIT_EXPRS.visit(falsy, state) : Ok(null); @@ -105,7 +105,7 @@ function translateIfUnlessInlineKeyword(type: string) { condition = new mir.Not({ value: condition, loc: node.loc }); } - return new mir.IfInline({ + return new mir.IfExpression({ loc: node.loc, condition, truthy, @@ -123,7 +123,7 @@ export function ifUnlessInlineKeyword(type: string): KeywordDelegate< truthy: ASTv2.ExpressionNode; falsy: ASTv2.ExpressionNode | null; }, - mir.IfInline + mir.IfExpression > { return { assert: assertIfUnlessInlineKeyword(type), diff --git a/packages/@glimmer/compiler/lib/passes/1-normalization/visitors/element/classified.ts b/packages/@glimmer/compiler/lib/passes/1-normalization/visitors/element/classified.ts index 9baa4b9113..74826acbb8 100644 --- a/packages/@glimmer/compiler/lib/passes/1-normalization/visitors/element/classified.ts +++ b/packages/@glimmer/compiler/lib/passes/1-normalization/visitors/element/classified.ts @@ -20,7 +20,7 @@ export interface Classified { readonly dynamicFeatures: boolean; arg(attr: ASTv2.AttrNode, classified: ClassifiedElement): Result; - toStatement(classified: ClassifiedElement, prepared: PreparedArgs): Result; + toStatement(classified: ClassifiedElement, prepared: PreparedArgs): Result; } export class ClassifiedElement { @@ -34,7 +34,7 @@ export class ClassifiedElement { this.delegate = delegate; } - toStatement(): Result { + toStatement(): Result { return this.prepare().andThen((prepared) => this.delegate.toStatement(this, prepared)); } diff --git a/packages/@glimmer/compiler/lib/passes/1-normalization/visitors/element/component.ts b/packages/@glimmer/compiler/lib/passes/1-normalization/visitors/element/component.ts index 1bfdcdeb93..5ce2cbc309 100644 --- a/packages/@glimmer/compiler/lib/passes/1-normalization/visitors/element/component.ts +++ b/packages/@glimmer/compiler/lib/passes/1-normalization/visitors/element/component.ts @@ -29,7 +29,7 @@ export class ClassifiedComponent implements Classified { ); } - toStatement(component: ClassifiedElement, { args, params }: PreparedArgs): Result { + toStatement(component: ClassifiedElement, { args, params }: PreparedArgs): Result { let { element, state } = component; return this.blocks(state).mapOk( diff --git a/packages/@glimmer/compiler/lib/passes/1-normalization/visitors/element/simple-element.ts b/packages/@glimmer/compiler/lib/passes/1-normalization/visitors/element/simple-element.ts index 35d7455d13..8914164cdd 100644 --- a/packages/@glimmer/compiler/lib/passes/1-normalization/visitors/element/simple-element.ts +++ b/packages/@glimmer/compiler/lib/passes/1-normalization/visitors/element/simple-element.ts @@ -26,7 +26,7 @@ export class ClassifiedSimpleElement implements Classified { ); } - toStatement(classified: ClassifiedElement, { params }: PreparedArgs): Result { + toStatement(classified: ClassifiedElement, { params }: PreparedArgs): Result { let { state, element } = classified; let body = VISIT_STMTS.visitList(this.element.body, state); diff --git a/packages/@glimmer/compiler/lib/passes/1-normalization/visitors/expressions.ts b/packages/@glimmer/compiler/lib/passes/1-normalization/visitors/expressions.ts index 3ba59f0d64..a983e0293b 100644 --- a/packages/@glimmer/compiler/lib/passes/1-normalization/visitors/expressions.ts +++ b/packages/@glimmer/compiler/lib/passes/1-normalization/visitors/expressions.ts @@ -163,7 +163,7 @@ export class NormalizeExpressions { } export function convertPathToCallIfKeyword(path: ASTv2.ExpressionNode): ASTv2.ExpressionNode { - if (path.type === 'Path' && path.ref.type === 'Free' && path.ref.name in KEYWORDS_TYPES) { + if (path.type === 'Path' && path.ref.type === 'Resolved' && path.ref.name in KEYWORDS_TYPES) { return new ASTv2.CallExpression({ callee: path, args: ASTv2.Args.empty(path.loc), diff --git a/packages/@glimmer/compiler/lib/passes/1-normalization/visitors/statements.ts b/packages/@glimmer/compiler/lib/passes/1-normalization/visitors/statements.ts index 714223dbab..bde9330cfd 100644 --- a/packages/@glimmer/compiler/lib/passes/1-normalization/visitors/statements.ts +++ b/packages/@glimmer/compiler/lib/passes/1-normalization/visitors/statements.ts @@ -16,13 +16,13 @@ class NormalizationStatements { visitList( nodes: readonly ASTv2.ContentNode[], state: NormalizationState - ): Result> { + ): Result> { return new ResultArray(nodes.map((e) => VISIT_STMTS.visit(e, state))) .toOptionalList() - .mapOk((list) => list.filter((s: mir.Statement | null): s is mir.Statement => s !== null)); + .mapOk((list) => list.filter((s: mir.Content | null): s is mir.Content => s !== null)); } - visit(node: ASTv2.ContentNode, state: NormalizationState): Result { + visit(node: ASTv2.ContentNode, state: NormalizationState): Result { switch (node.type) { case 'GlimmerComment': return Ok(null); @@ -35,13 +35,13 @@ class NormalizationStatements { case 'InvokeBlock': return this.InvokeBlock(node, state); case 'InvokeComponent': - return this.Component(node, state); + return this.InvokeComponent(node, state); case 'SimpleElement': return this.SimpleElement(node, state); } } - InvokeBlock(node: ASTv2.InvokeBlock, state: NormalizationState): Result { + InvokeBlock(node: ASTv2.InvokeBlock, state: NormalizationState): Result { let translated = BLOCK_KEYWORDS.translate(node, state); if (translated !== null) { @@ -94,7 +94,7 @@ class NormalizationStatements { }); } - SimpleElement(element: ASTv2.SimpleElement, state: NormalizationState): Result { + SimpleElement(element: ASTv2.SimpleElement, state: NormalizationState): Result { return new ClassifiedElement( element, new ClassifiedSimpleElement(element.tag, element, hasDynamicFeatures(element)), @@ -102,7 +102,7 @@ class NormalizationStatements { ).toStatement(); } - Component(component: ASTv2.InvokeComponent, state: NormalizationState): Result { + InvokeComponent(component: ASTv2.InvokeComponent, state: NormalizationState): Result { return VISIT_EXPRS.visit(component.callee, state).andThen((callee) => new ClassifiedElement( component, @@ -112,7 +112,7 @@ class NormalizationStatements { ); } - AppendContent(append: ASTv2.AppendContent, state: NormalizationState): Result { + AppendContent(append: ASTv2.AppendContent, state: NormalizationState): Result { let translated = APPEND_KEYWORDS.translate(append, state); if (translated !== null) { @@ -136,14 +136,14 @@ class NormalizationStatements { }); } - TextNode(text: ASTv2.HtmlText): mir.Statement { + TextNode(text: ASTv2.HtmlText): mir.Content { return new mir.AppendValue({ loc: text.loc, value: new ASTv2.LiteralExpression({ loc: text.loc, value: text.chars }), }); } - HtmlComment(comment: ASTv2.HtmlComment): mir.Statement { + HtmlComment(comment: ASTv2.HtmlComment): mir.Content { return new mir.AppendComment({ loc: comment.loc, value: comment.text, diff --git a/packages/@glimmer/compiler/lib/passes/1-normalization/visitors/strict-mode.ts b/packages/@glimmer/compiler/lib/passes/1-normalization/visitors/strict-mode.ts index 2233b85a46..dd0f5aa722 100644 --- a/packages/@glimmer/compiler/lib/passes/1-normalization/visitors/strict-mode.ts +++ b/packages/@glimmer/compiler/lib/passes/1-normalization/visitors/strict-mode.ts @@ -33,7 +33,7 @@ export default class StrictModeValidationPass { return this.Statements(this.template.body).mapOk(() => this.template); } - Statements(statements: mir.Statement[]): Result { + Statements(statements: mir.Content[]): Result { let result = Ok(null); for (let statement of statements) { @@ -57,7 +57,7 @@ export default class StrictModeValidationPass { return this.Statements(block.body); } - Statement(statement: mir.Statement): Result { + Statement(statement: mir.Content): Result { switch (statement.type) { case 'InElement': return this.InElement(statement); @@ -86,8 +86,8 @@ export default class StrictModeValidationPass { case 'AppendComment': return Ok(null); - case 'If': - return this.If(statement); + case 'IfContent': + return this.IfContent(statement); case 'Each': return this.Each(statement); @@ -98,8 +98,11 @@ export default class StrictModeValidationPass { case 'WithDynamicVars': return this.WithDynamicVars(statement); - case 'InvokeComponent': - return this.InvokeComponent(statement); + case 'InvokeComponentKeyword': + return this.InvokeComponentKeyword(statement); + + case 'InvokeResolvedComponentKeyword': + return this.InvokeResolvedComponentKeyword(statement); } } @@ -133,7 +136,7 @@ export default class StrictModeValidationPass { case 'PathExpression': return this.Expression(expression.head, span, resolution); - case 'Free': + case 'Resolved': return this.errorFor(expression.name, span, resolution); case 'InterpolateExpression': @@ -145,8 +148,8 @@ export default class StrictModeValidationPass { case 'Not': return this.Expression(expression.value, span, resolution); - case 'IfInline': - return this.IfInline(expression); + case 'IfExpression': + return this.IfExpression(expression); case 'Curry': return this.Curry(expression); @@ -274,7 +277,7 @@ export default class StrictModeValidationPass { .andThen(() => this.NamedBlocks(statement.blocks)); } - If(statement: mir.If): Result { + IfContent(statement: mir.IfContent): Result { return this.Expression(statement.condition, statement) .andThen(() => this.NamedBlock(statement.block)) .andThen(() => { @@ -313,16 +316,17 @@ export default class StrictModeValidationPass { return this.NamedArguments(statement.named).andThen(() => this.NamedBlock(statement.block)); } - InvokeComponent(statement: mir.InvokeComponent): Result { - return this.Expression(statement.definition, statement, COMPONENT_RESOLUTION) - .andThen(() => this.Args(statement.args)) - .andThen(() => { - if (statement.blocks) { - return this.NamedBlocks(statement.blocks); - } else { - return Ok(null); - } - }); + InvokeComponentKeyword(statement: mir.InvokeComponentKeyword): Result { + return this.Expression(statement.definition, statement, COMPONENT_RESOLUTION).andThen(() => + this.Args(statement.args) + ); + } + + InvokeResolvedComponentKeyword(statement: mir.InvokeResolvedComponentKeyword): Result { + return this.Args(statement.args).andThen(() => { + if (statement.blocks) this.NamedBlocks(statement.blocks); + return Ok(null); + }); } InterpolateExpression( @@ -349,7 +353,7 @@ export default class StrictModeValidationPass { ); } - IfInline(expression: mir.IfInline): Result { + IfExpression(expression: mir.IfExpression): Result { return this.Expression(expression.condition) .andThen(() => this.Expression(expression.truthy)) .andThen(() => { diff --git a/packages/@glimmer/compiler/lib/passes/2-encoding/content.ts b/packages/@glimmer/compiler/lib/passes/2-encoding/content.ts index e03af5a633..e398ee7613 100644 --- a/packages/@glimmer/compiler/lib/passes/2-encoding/content.ts +++ b/packages/@glimmer/compiler/lib/passes/2-encoding/content.ts @@ -2,9 +2,7 @@ import type { AttrOpcode, ComponentAttrOpcode, DynamicAttrOpcode, - Nullable, Optional, - SerializedInlineBlock, StaticAttrOpcode, StaticComponentAttrOpcode, TrustingComponentAttrOpcode, @@ -16,27 +14,26 @@ import type { BlockSymbolTable } from '@glimmer/syntax'; import { assertPresentArray, exhausted, localAssert } from '@glimmer/debug-util'; import { LOCAL_TRACE_LOGGING } from '@glimmer/local-debug-flags'; import { LOCAL_LOGGER } from '@glimmer/util'; -import { isGetLexical, isGetVar, SexpOpcodes as Op } from '@glimmer/wire-format'; +import { isGetLexical, SexpOpcodes as Op } from '@glimmer/wire-format'; import type { OptionalList } from '../../shared/list'; import type * as mir from './mir'; import { - blockType, buildAppend, buildComponentArgs, compact, headType, isGet, - isGetSymbol, + isGetSymbolOrPath, isTupleExpression, MODIFIER_TYPES, needsAtNames, } from '../../builder/builder'; import { deflateAttrName, deflateTagName } from '../../utils'; -import { EXPR } from './expressions'; +import { Args, expr, NamedArguments, Positional } from './expressions'; -class WireStatements { +class WireContent { constructor(private statements: readonly S[]) {} toArray(): readonly S[] { @@ -44,292 +41,323 @@ class WireStatements { } } -export class ContentEncoder { - list(statements: mir.Statement[]): WireFormat.Statement[] { - let out: WireFormat.Statement[] = []; +export function ContentList(statements: mir.Content[]): WireFormat.Content[] { + let out: WireFormat.Content[] = []; - for (let statement of statements) { - let result = CONTENT.content(statement); + for (let statement of statements) { + let result = Content(statement); - if (result instanceof WireStatements) { - out.push(...result.toArray()); - } else { - out.push(result); - } + if (result instanceof WireContent) { + out.push(...result.toArray()); + } else { + out.push(result); } - - return out; } - content(stmt: mir.Statement): WireFormat.Statement | WireStatements { - if (LOCAL_TRACE_LOGGING) { - LOCAL_LOGGER.debug(`encoding`, stmt); - } + return out; +} - return this.visitContent(stmt); +function visitContent(stmt: mir.Content): WireFormat.Content | WireContent { + switch (stmt.type) { + case 'Debugger': + return Debugger(stmt); + case 'AppendComment': + return AppendComment(stmt); + case 'AppendValue': + return AppendValue(stmt); + case 'AppendTrustedHTML': + return AppendTrustedHTML(stmt); + case 'Yield': + return Yield(stmt); + case 'Component': + return Component(stmt); + case 'SimpleElement': + return SimpleElement(stmt); + case 'InElement': + return InElement(stmt); + case 'InvokeBlock': + return InvokeBlock(stmt); + case 'IfContent': + return IfContent(stmt); + case 'Each': + return Each(stmt); + case 'Let': + return Let(stmt); + case 'WithDynamicVars': + return WithDynamicVars(stmt); + case 'InvokeComponentKeyword': + return InvokeComponentKeyword(stmt); + case 'InvokeResolvedComponentKeyword': + return InvokeResolvedComponentKeyword(stmt); + default: + return exhausted(stmt); } +} - private visitContent(stmt: mir.Statement): WireFormat.Statement | WireStatements { - switch (stmt.type) { - case 'Debugger': - return [Op.Debugger, ...stmt.scope.getDebugInfo(), {}]; - case 'AppendComment': - return this.AppendComment(stmt); - case 'AppendValue': - return this.AppendValue(stmt); - case 'AppendTrustedHTML': - return this.AppendTrustedHTML(stmt); - case 'Yield': - return this.Yield(stmt); - case 'Component': - return this.Component(stmt); - case 'SimpleElement': - return this.SimpleElement(stmt); - case 'InElement': - return this.InElement(stmt); - case 'InvokeBlock': - return this.InvokeBlock(stmt); - case 'If': - return this.If(stmt); - case 'Each': - return this.Each(stmt); - case 'Let': - return this.Let(stmt); - case 'WithDynamicVars': - return this.WithDynamicVars(stmt); - case 'InvokeComponent': - return this.InvokeComponent(stmt); - default: - return exhausted(stmt); - } +export function Content(stmt: mir.Content): WireFormat.Content | WireContent { + if (LOCAL_TRACE_LOGGING) { + LOCAL_LOGGER.debug(`encoding`, stmt); } - Yield({ to, positional }: mir.Yield): WireFormat.Statements.Yield { - return [Op.Yield, to, EXPR.Positional(positional)]; - } + return visitContent(stmt); +} - InElement({ - guid, - insertBefore, - destination, - block, - }: mir.InElement): WireFormat.Statements.InElement { - let wireBlock = CONTENT.NamedBlock(block)[1]; - // let guid = args.guid; - let wireDestination = EXPR.expr(destination); - let wireInsertBefore = EXPR.expr(insertBefore); - - if (wireInsertBefore === undefined) { - return [Op.InElement, wireBlock, guid, wireDestination]; - } else { - return [Op.InElement, wireBlock, guid, wireDestination, wireInsertBefore]; - } - } +export function Debugger({ scope }: mir.Debugger): WireFormat.Contents.Debugger { + return [Op.Debugger, ...scope.getDebugInfo(), {}]; +} - BlockArgs( - argsNode: Pick, - blocksNode: Nullable, - { insertAtPrefix }: { insertAtPrefix: boolean } - ): Optional { - return compact({ - ...EXPR.Args(argsNode, insertAtPrefix), - blocks: blocksNode ? this.NamedBlocks(blocksNode) : undefined, - }); +export function Yield({ to, positional }: mir.Yield): WireFormat.Contents.Yield { + return [Op.Yield, to, Positional(positional)]; +} + +export function InElement({ + guid, + insertBefore, + destination, + block, +}: mir.InElement): WireFormat.Contents.InElement { + let wireBlock = NamedBlock(block)[1]; + // let guid = args.guid; + let wireDestination = expr(destination); + let wireInsertBefore = expr(insertBefore); + + if (wireInsertBefore === undefined) { + return [Op.InElement, wireBlock, guid, wireDestination]; + } else { + return [Op.InElement, wireBlock, guid, wireDestination, wireInsertBefore]; } +} + +export function BlockArgs( + argsNode: Pick, + blocksNode: Optional, + { insertAtPrefix }: { insertAtPrefix: boolean } +): Optional { + return compact({ + ...Args(argsNode, insertAtPrefix), + blocks: blocksNode ? NamedBlocks(blocksNode) : undefined, + }); +} - InvokeBlock({ head, args, blocks }: mir.InvokeBlock): WireFormat.Statements.SomeBlock { - const path = EXPR.expr(head); +export function InvokeBlock({ + head, + args, + blocks, +}: mir.InvokeBlock): WireFormat.Contents.SomeBlock { + const path = expr(head); - localAssert(isGet(path), `Expected ${JSON.stringify(path)} to be a Get`); + localAssert(isGet(path), `Expected ${JSON.stringify(path)} to be a Get`); + if (head.type === 'Local' && head.referenceType === 'lexical') { return [ - ...blockType(path), - this.BlockArgs(args, blocks, { insertAtPrefix: needsAtNames(path) }), + Op.InvokeLexicalComponent, + expr(head), + BlockArgs(args, blocks, { insertAtPrefix: needsAtNames(path) }), + ]; + } else if (head.type === 'Resolved') { + return [ + Op.ResolvedBlock, + expr(head), + BlockArgs(args, blocks, { insertAtPrefix: needsAtNames(path) }), ]; } - AppendTrustedHTML({ html }: mir.AppendTrustedHTML): WireFormat.Statements.TrustingAppend { - return [Op.TrustingAppend, EXPR.expr(html)]; - } - - AppendValue({ value }: mir.AppendValue): WireFormat.Statements.SomeAppend { - return buildAppend(false, EXPR.expr(value)); - } + return [Op.DynamicBlock, path, BlockArgs(args, blocks, { insertAtPrefix: needsAtNames(path) })]; +} - AppendComment({ value }: mir.AppendComment): WireFormat.Statements.Comment { - return [Op.Comment, value.chars]; - } +export function AppendTrustedHTML({ + html, +}: mir.AppendTrustedHTML): WireFormat.Contents.TrustingAppend { + return [Op.TrustingAppend, expr(html)]; +} - SimpleElement({ tag, params, body, dynamicFeatures }: mir.SimpleElement): WireStatements { - let op = dynamicFeatures ? Op.OpenElementWithSplat : Op.OpenElement; - return new WireStatements([ - [op, deflateTagName(tag.chars)], - ...CONTENT.ElementParameters(params).toArray(), - [Op.FlushElement], - ...CONTENT.list(body), - [Op.CloseElement], - ]); - } +export function AppendValue({ value }: mir.AppendValue): WireFormat.Contents.SomeAppend { + return buildAppend(false, expr(value)); +} - // This is used by the `{{component}}` keyword - Component({ - tag, - params, - args: named, - blocks, - }: mir.Component): - | WireFormat.Statements.InvokeLexicalComponent - | WireFormat.Statements.ResolvedComponent - | WireFormat.Statements.DynamicComponent { - let wireTag = EXPR.expr(tag); - let wirePositional = CONTENT.ElementParameters(params); - let wireNamed = EXPR.NamedArguments(named, false); +export function AppendComment({ value }: mir.AppendComment): WireFormat.Contents.Comment { + return [Op.Comment, value.chars]; +} - let wireNamedBlocks = CONTENT.NamedBlocks(blocks); +export function SimpleElement({ + tag, + params, + body, + dynamicFeatures, +}: mir.SimpleElement): WireContent { + let op = dynamicFeatures ? Op.OpenElementWithSplat : Op.OpenElement; + return new WireContent([ + [op, deflateTagName(tag.chars)], + ...ElementParameters(params).toArray(), + [Op.FlushElement], + ...ContentList(body), + [Op.CloseElement], + ]); +} - const args = buildComponentArgs(wirePositional.toPresentArray(), wireNamed, wireNamedBlocks); +// This is used by the `{{component}}` keyword +export function Component({ + tag, + params, + args: named, + blocks, +}: mir.Component): + | WireFormat.Contents.InvokeLexicalComponent + | WireFormat.Contents.ResolvedComponent + | WireFormat.Contents.DynamicComponent { + let wireTag = expr(tag); + let wirePositional = ElementParameters(params); + let wireNamed = NamedArguments(named, false); + + let wireNamedBlocks = NamedBlocks(blocks); + + const args = buildComponentArgs(wirePositional.toPresentArray(), wireNamed, wireNamedBlocks); + + localAssert( + isTupleExpression(wireTag), + `Expected ${JSON.stringify(wireTag)} to be a tuple expression` + ); + + // There are special cases for non-path expressions that refer to out-of-template variables: + // lexical variables and resolved variables. + + if (isGetLexical(wireTag)) { + // if the expression is something like `x.Foo`, then the component is dynamic, not + // a lexical variable. + return [ + wireTag.length === 2 ? Op.InvokeLexicalComponent : Op.InvokeDynamicComponent, + wireTag, + args, + ]; + } + if (wireTag[0] === Op.GetFreeAsComponentHead) { localAssert( - isTupleExpression(wireTag), - `Expected ${JSON.stringify(wireTag)} to be a tuple expression` + wireTag.length === 2, + `Unexpected free variable as component head with a path tail ${JSON.stringify(wireTag)}` ); - // There are special cases for non-path expressions that refer to out-of-template variables: - // lexical variables and resolved variables. - - if (isGetLexical(wireTag)) { - // if the expression is something like `x.Foo`, then the component is dynamic, not - // a lexical variable. - return [ - wireTag.length === 2 ? Op.InvokeLexicalComponent : Op.DynamicComponent, - wireTag, - args, - ]; - } - - if (wireTag[0] === Op.GetFreeAsComponentHead) { - localAssert( - wireTag.length === 2, - `Unexpected free variable as component head with a path tail ${JSON.stringify(wireTag)}` - ); - - return [Op.ResolvedComponent, wireTag, args]; - } + return [Op.ResolvedComponent, wireTag, args]; + } - // The only remaining case here should be a reference to a local Handlebars variable (possibly a - // path). - localAssert(isGetSymbol(wireTag), `Expected ${JSON.stringify(wireTag)} to be a symbol`); + // The only remaining case here should be a reference to a local Handlebars variable (possibly a + // path). + localAssert(isGetSymbolOrPath(wireTag), `Expected ${JSON.stringify(wireTag)} to be a symbol`); - return [Op.DynamicComponent, wireTag, args]; - } + return [Op.InvokeDynamicComponent, wireTag, args]; +} - ElementParameters({ body }: mir.ElementParameters): OptionalList { - return body.map((p) => CONTENT.ElementParameter(p)); - } +export function ElementParameters({ + body, +}: mir.ElementParameters): OptionalList { + return body.map((p) => ElementParameter(p)); +} - ElementParameter(param: mir.ElementParameter): WireFormat.ElementParameter { - switch (param.type) { - case 'SplatAttr': - return [Op.AttrSplat, param.symbol]; - case 'DynamicAttr': - return [dynamicAttrOp(param.kind), ...dynamicAttr(param)]; - case 'StaticAttr': - return [staticAttrOp(param.kind), ...staticAttr(param)]; - case 'Modifier': { - const expr = EXPR.expr(param.callee); - return [MODIFIER_TYPES[headType(expr)], EXPR.expr(param.callee), EXPR.Args(param.args)]; - } +export function ElementParameter(param: mir.ElementParameter): WireFormat.ElementParameter { + switch (param.type) { + case 'SplatAttr': + return [Op.AttrSplat, param.symbol]; + case 'DynamicAttr': + return [dynamicAttrOp(param.kind), ...dynamicAttr(param)]; + case 'StaticAttr': + return [staticAttrOp(param.kind), ...staticAttr(param)]; + case 'Modifier': { + const expression = expr(param.callee); + return [ + MODIFIER_TYPES[headType(expression, 'modifier')], + expr(param.callee), + Args(param.args), + ]; } } +} - NamedBlocks({ blocks: blocksNode }: mir.NamedBlocks): Optional { - const blocks = blocksNode.toPresentArray(); - if (!blocks) return; - - let names: string[] = []; - let serializedBlocks: WireFormat.SerializedInlineBlock[] = []; - - for (const block of blocks) { - const [name, serializedBlock] = this.NamedBlock(block); +export function NamedBlocks({ + blocks: blocksNode, +}: mir.NamedBlocks): Optional { + const blocks = blocksNode.toPresentArray(); + if (!blocks) return; - names.push(name); - serializedBlocks.push(serializedBlock); - } + let names: string[] = []; + let serializedBlocks: WireFormat.SerializedInlineBlock[] = []; - assertPresentArray(names); - assertPresentArray(serializedBlocks); + for (const block of blocks) { + const [name, serializedBlock] = NamedBlock(block); - return [names, serializedBlocks]; + names.push(name); + serializedBlocks.push(serializedBlock); } - NamedBlock({ name, body, scope }: mir.NamedBlock): WireFormat.Core.NamedBlock { - return [name.chars === 'inverse' ? 'else' : name.chars, this.namedBlock(body, scope)]; - } + assertPresentArray(names); + assertPresentArray(serializedBlocks); - namedBlock(body: mir.Statement[], scope: BlockSymbolTable): SerializedInlineBlock { - return [CONTENT.list(body), scope.slots]; - } + return [names, serializedBlocks]; +} - If({ condition, block, inverse }: mir.If): WireFormat.Statements.If { - return [ - Op.If, - EXPR.expr(condition), - CONTENT.NamedBlock(block)[1], - inverse ? CONTENT.NamedBlock(inverse)[1] : null, - ]; - } +export function NamedBlock({ name, body, scope }: mir.NamedBlock): WireFormat.Core.NamedBlock { + return [name.chars === 'inverse' ? 'else' : name.chars, namedBlock(body, scope)]; +} - Each({ value, key, block, inverse }: mir.Each): WireFormat.Statements.Each { - return [ - Op.Each, - EXPR.expr(value), - key ? EXPR.expr(key) : null, - CONTENT.NamedBlock(block)[1], - inverse ? CONTENT.NamedBlock(inverse)[1] : null, - ]; - } +export function namedBlock( + body: mir.Content[], + scope: BlockSymbolTable +): WireFormat.SerializedInlineBlock { + return [ContentList(body), scope.slots]; +} - Let({ positional, block }: mir.Let): WireFormat.Statements.Let { - return [Op.Let, EXPR.Positional(positional), CONTENT.NamedBlock(block)[1]]; - } +export function IfContent({ condition, block, inverse }: mir.IfContent): WireFormat.Contents.If { + return [Op.If, expr(condition), NamedBlock(block)[1], inverse ? NamedBlock(inverse)[1] : null]; +} - WithDynamicVars({ named, block }: mir.WithDynamicVars): WireFormat.Statements.WithDynamicVars { - return [ - Op.WithDynamicVars, - EXPR.NamedArguments(named, false), - CONTENT.namedBlock(block.body, block.scope), - ]; - } +export function Each({ value, key, block, inverse }: mir.Each): WireFormat.Contents.Each { + return [ + Op.Each, + expr(value), + key ? expr(key) : null, + NamedBlock(block)[1], + inverse ? NamedBlock(inverse)[1] : null, + ]; +} - InvokeComponent({ - definition, - args, - blocks, - }: mir.InvokeComponent): WireFormat.Statements.SomeInvokeComponent { - const expr = EXPR.expr(definition); +export function Let({ positional, block }: mir.Let): WireFormat.Contents.Let { + return [Op.Let, Positional(positional), NamedBlock(block)[1]]; +} - if (typeof expr === 'string' || headType(expr) === 'lexical') { - return [ - Op.InvokeDynamicComponent, - expr, - this.BlockArgs(args, blocks, { insertAtPrefix: false }), - ]; - } else { - localAssert( - Array.isArray(expr) && isGetVar(expr), - `Expected ${JSON.stringify(expr)} to be an array` - ); +export function WithDynamicVars({ + named, + block, +}: mir.WithDynamicVars): WireFormat.Contents.WithDynamicVars { + return [Op.WithDynamicVars, NamedArguments(named, false), namedBlock(block.body, block.scope)]; +} - return [ - Op.InvokeResolvedComponent, - expr, - this.BlockArgs(args, blocks, { insertAtPrefix: true }), - ]; - } - } +export function InvokeComponentKeyword({ + definition, + args, + blocks, +}: mir.InvokeComponentKeyword): WireFormat.Contents.SomeInvokeComponent { + const expression = expr(definition); + const type = headType(expression, 'component'); + + localAssert(type !== 'keyword', `[BUG] {{component }} is not a valid node`); + + return [ + Op.InvokeComponentKeyword, + expression, + BlockArgs(args, blocks, { insertAtPrefix: false }), + ]; } -export const CONTENT = new ContentEncoder(); +export function InvokeResolvedComponentKeyword({ + definition, + args, + blocks, +}: mir.InvokeResolvedComponentKeyword): WireFormat.Contents.SomeInvokeComponent { + return [ + Op.InvokeComponentKeyword, + definition, + BlockArgs(args, blocks, { insertAtPrefix: false }), + ]; +} export type StaticAttrArgs = [name: string | WellKnownAttrName, value: string, namespace?: string]; @@ -350,7 +378,7 @@ export type DynamicAttrArgs = [ ]; function dynamicAttr({ name, value, namespace }: mir.DynamicAttr): DynamicAttrArgs { - let out: DynamicAttrArgs = [deflateAttrName(name.chars), EXPR.expr(value)]; + let out: DynamicAttrArgs = [deflateAttrName(name.chars), expr(value)]; if (namespace) { out.push(namespace); diff --git a/packages/@glimmer/compiler/lib/passes/2-encoding/expressions.ts b/packages/@glimmer/compiler/lib/passes/2-encoding/expressions.ts index b2f0ae5d63..28a6e04b18 100644 --- a/packages/@glimmer/compiler/lib/passes/2-encoding/expressions.ts +++ b/packages/@glimmer/compiler/lib/passes/2-encoding/expressions.ts @@ -1,7 +1,7 @@ import type { Optional, PresentArray, WireFormat } from '@glimmer/interfaces'; import type { ASTv2 } from '@glimmer/syntax'; import { assertPresentArray, localAssert, mapPresentArray } from '@glimmer/debug-util'; -import { SexpOpcodes } from '@glimmer/wire-format'; +import { SexpOpcodes as Op } from '@glimmer/wire-format'; import type * as mir from './mir'; @@ -9,175 +9,201 @@ import { CALL_TYPES, compact, headType } from '../../builder/builder'; export type HashPair = [string, WireFormat.Expression]; -export class ExpressionEncoder { - expr(expr: mir.ExpressionNode): WireFormat.Expression { - switch (expr.type) { - case 'Missing': - return undefined; - case 'Literal': - return this.Literal(expr); - case 'Keyword': - return this.Keyword(expr); - case 'CallExpression': - return this.CallExpression(expr); - case 'PathExpression': - return this.PathExpression(expr); - case 'Arg': - return [SexpOpcodes.GetSymbol, expr.symbol]; - case 'Local': - return this.Local(expr); - case 'This': - return [SexpOpcodes.GetSymbol, 0]; - case 'Free': - return [expr.resolution.resolution(), expr.symbol]; - case 'HasBlock': - return this.HasBlock(expr); - case 'HasBlockParams': - return this.HasBlockParams(expr); - case 'Curry': - return this.Curry(expr); - case 'Not': - return this.Not(expr); - case 'IfInline': - return this.IfInline(expr); - case 'InterpolateExpression': - return this.InterpolateExpression(expr); - case 'GetDynamicVar': - return this.GetDynamicVar(expr); - case 'Log': - return this.Log(expr); - } +export function expr(expr: ASTv2.LocalVarReference): WireFormat.Expressions.GetVar; +export function expr(expr: ASTv2.ResolvedVarReference): WireFormat.Expressions.GetResolved; +export function expr(expr: mir.ExpressionNode): WireFormat.Expression; +export function expr(expr: mir.ExpressionNode): WireFormat.Expression { + switch (expr.type) { + case 'Missing': + return undefined; + case 'Literal': + return Literal(expr); + case 'Keyword': + return Keyword(expr); + case 'CallExpression': + return CallExpression(expr); + case 'PathExpression': + return PathExpression(expr); + case 'Arg': + return Arg(expr); + case 'Local': + return Local(expr); + case 'This': + return This(); + case 'Resolved': + return Resolved(expr); + case 'HasBlock': + return HasBlock(expr); + case 'HasBlockParams': + return HasBlockParams(expr); + case 'Curry': + return Curry(expr); + case 'Not': + return Not(expr); + case 'IfExpression': + return IfInline(expr); + case 'InterpolateExpression': + return InterpolateExpression(expr); + case 'GetDynamicVar': + return GetDynamicVar(expr); + case 'Log': + return Log(expr); } +} - Literal({ - value, - }: ASTv2.LiteralExpression): WireFormat.Expressions.Value | WireFormat.Expressions.Undefined { - if (value === undefined) { - return [SexpOpcodes.Undefined]; - } else { - return value; - } - } +function args( + positionalNode: mir.Positional, + namedNode: mir.NamedArguments, + insertAtPrefix: boolean +): Optional { + const positional = Positional(positionalNode); + const named = NamedArguments(namedNode, insertAtPrefix); - Missing(): undefined { - return undefined; - } + return compact({ params: positional, hash: named }); +} - HasBlock({ symbol }: mir.HasBlock): WireFormat.Expressions.HasBlock { - return [SexpOpcodes.HasBlock, [SexpOpcodes.GetSymbol, symbol]]; - } +export function Resolved({ + resolution, + symbol, +}: ASTv2.ResolvedVarReference): WireFormat.Expressions.GetResolvedOrKeyword { + return [resolution.resolution(), symbol]; +} - HasBlockParams({ symbol }: mir.HasBlockParams): WireFormat.Expressions.HasBlockParams { - return [SexpOpcodes.HasBlockParams, [SexpOpcodes.GetSymbol, symbol]]; - } +export function Positional({ list }: mir.Positional): Optional { + return list.map((l) => expr(l)).toPresentArray(); +} - Curry({ definition, curriedType, args }: mir.Curry): WireFormat.Expressions.Curry { - return [SexpOpcodes.Curry, EXPR.expr(definition), curriedType, this.Args(args)]; - } +export function NamedArgument({ key, value }: mir.NamedArgument): HashPair { + return [key.chars, expr(value)]; +} - Local({ - isTemplateLocal, - symbol, - }: ASTv2.LocalVarReference): - | WireFormat.Expressions.GetSymbol - | WireFormat.Expressions.GetLexicalSymbol { - return [isTemplateLocal ? SexpOpcodes.GetLexicalSymbol : SexpOpcodes.GetSymbol, symbol]; - } +export function NamedArguments( + { entries: pairs }: mir.NamedArguments, + insertAtPrefix: boolean +): Optional { + let list = pairs.toPresentArray(); - Keyword({ symbol }: ASTv2.KeywordExpression): WireFormat.Expressions.GetStrictKeyword { - return [SexpOpcodes.GetStrictKeyword, symbol]; - } + if (list) { + let names: string[] = []; + let values: WireFormat.Expression[] = []; - PathExpression({ head, tail }: mir.PathExpression): WireFormat.Expressions.GetPath { - let getOp = EXPR.expr(head) as WireFormat.Expressions.GetVar; - localAssert(getOp[0] !== SexpOpcodes.GetStrictKeyword, '[BUG] keyword in a PathExpression'); - return [...getOp, EXPR.Tail(tail)]; - } + for (let pair of list) { + let [name, value] = NamedArgument(pair); + names.push(insertAtPrefix ? `@${name}` : name); + values.push(value); + } + + assertPresentArray(names); + assertPresentArray(values); - InterpolateExpression({ parts }: mir.InterpolateExpression): WireFormat.Expressions.Concat { - return [SexpOpcodes.Concat, parts.map((e) => EXPR.expr(e)).toArray()]; + return [names, values]; } +} - CallExpression({ callee, args }: mir.CallExpression): WireFormat.Expressions.SomeInvoke { - const calleeExpr = EXPR.expr(callee); +export function This(): WireFormat.Expressions.GetLocalSymbol { + return [Op.GetLocalSymbol, 0]; +} - return [CALL_TYPES[headType(calleeExpr)], calleeExpr, EXPR.Args(args)]; - } +export function Arg({ symbol }: ASTv2.ArgReference): WireFormat.Expressions.GetLocalSymbol { + return [Op.GetLocalSymbol, symbol]; +} - Tail({ members }: mir.Tail): PresentArray { - return mapPresentArray(members, (member) => member.chars); +export function Literal({ + value, +}: ASTv2.LiteralExpression): WireFormat.Expressions.Value | WireFormat.Expressions.Undefined { + if (value === undefined) { + return [Op.Undefined]; + } else { + return value; } +} - Args( - node: Pick, - insertAtPrefix: boolean = false - ): Optional { - return this.#args(node.positional, node.named, insertAtPrefix); - } +export function Missing(): undefined { + return undefined; +} - Positional({ list }: mir.Positional): Optional { - return list.map((l) => EXPR.expr(l)).toPresentArray(); - } +export function HasBlock({ symbol }: mir.HasBlock): WireFormat.Expressions.HasBlock { + return [Op.HasBlock, [Op.GetLocalSymbol, symbol]]; +} - NamedArgument({ key, value }: mir.NamedArgument): HashPair { - return [key.chars, EXPR.expr(value)]; - } +export function HasBlockParams({ + symbol, +}: mir.HasBlockParams): WireFormat.Expressions.HasBlockParams { + return [Op.HasBlockParams, [Op.GetLocalSymbol, symbol]]; +} - NamedArguments( - { entries: pairs }: mir.NamedArguments, - insertAtPrefix: boolean - ): Optional { - let list = pairs.toPresentArray(); +export function Curry({ definition, curriedType, args }: mir.Curry): WireFormat.Expressions.Curry { + return [Op.Curry, expr(definition), curriedType, Args(args)]; +} - if (list) { - let names: string[] = []; - let values: WireFormat.Expression[] = []; +export function Local({ + referenceType, + symbol, +}: ASTv2.LocalVarReference): + | WireFormat.Expressions.GetLocalSymbol + | WireFormat.Expressions.GetLexicalSymbol { + return [referenceType === 'lexical' ? Op.GetLexicalSymbol : Op.GetLocalSymbol, symbol]; +} - for (let pair of list) { - let [name, value] = EXPR.NamedArgument(pair); - names.push(insertAtPrefix ? `@${name}` : name); - values.push(value); - } +export function Keyword({ + symbol, +}: ASTv2.KeywordExpression): WireFormat.Expressions.GetStrictKeyword { + return [Op.GetStrictKeyword, symbol]; +} - assertPresentArray(names); - assertPresentArray(values); +export function PathExpression({ head, tail }: mir.PathExpression): WireFormat.Expressions.GetPath { + let getOp = expr(head) as WireFormat.Expressions.GetVar; + localAssert(getOp[0] !== Op.GetStrictKeyword, '[BUG] keyword in a PathExpression'); + return [...getOp, Tail(tail)]; +} - return [names, values]; - } - } +export function InterpolateExpression({ + parts, +}: mir.InterpolateExpression): WireFormat.Expressions.Concat { + return [Op.Concat, parts.map((e) => expr(e)).toArray()]; +} - Not({ value }: mir.Not): WireFormat.Expressions.Not { - return [SexpOpcodes.Not, EXPR.expr(value)]; - } +export function CallExpression({ + callee, + args, +}: mir.CallExpression): WireFormat.Expressions.SomeInvoke { + const calleeExpr = expr(callee); + return [CALL_TYPES[headType(calleeExpr, 'expr:call')], calleeExpr, Args(args)]; +} - IfInline({ condition, truthy, falsy }: mir.IfInline): WireFormat.Expressions.IfInline { - let expr = [SexpOpcodes.IfInline, EXPR.expr(condition), EXPR.expr(truthy)]; +export function Tail({ members }: mir.Tail): PresentArray { + return mapPresentArray(members, (member) => member.chars); +} - if (falsy) { - expr.push(EXPR.expr(falsy)); - } +export function Args( + node: Pick, + insertAtPrefix: boolean = false +): Optional { + return args(node.positional, node.named, insertAtPrefix); +} - return expr as WireFormat.Expressions.IfInline; - } +export function Not({ value }: mir.Not): WireFormat.Expressions.Not { + return [Op.Not, expr(value)]; +} - GetDynamicVar({ name }: mir.GetDynamicVar): WireFormat.Expressions.GetDynamicVar { - return [SexpOpcodes.GetDynamicVar, EXPR.expr(name)]; - } +export function IfInline({ + condition, + truthy, + falsy, +}: mir.IfExpression): WireFormat.Expressions.IfInline { + let expression = [Op.IfInline, expr(condition), expr(truthy)]; - Log({ positional }: mir.Log): WireFormat.Expressions.Log { - return [SexpOpcodes.Log, this.Positional(positional)]; + if (falsy) { + expression.push(expr(falsy)); } - #args( - positionalNode: mir.Positional, - namedNode: mir.NamedArguments, - insertAtPrefix: boolean - ): Optional { - const positional = this.Positional(positionalNode); - const named = this.NamedArguments(namedNode, insertAtPrefix); + return expression as WireFormat.Expressions.IfInline; +} - return compact({ params: positional, hash: named }); - } +export function GetDynamicVar({ name }: mir.GetDynamicVar): WireFormat.Expressions.GetDynamicVar { + return [Op.GetDynamicVar, expr(name)]; } -export const EXPR = new ExpressionEncoder(); +export function Log({ positional }: mir.Log): WireFormat.Expressions.Log { + return [Op.Log, Positional(positional)]; +} diff --git a/packages/@glimmer/compiler/lib/passes/2-encoding/index.ts b/packages/@glimmer/compiler/lib/passes/2-encoding/index.ts index ab82fd0218..9162b245a4 100644 --- a/packages/@glimmer/compiler/lib/passes/2-encoding/index.ts +++ b/packages/@glimmer/compiler/lib/passes/2-encoding/index.ts @@ -5,10 +5,10 @@ import { LOCAL_LOGGER } from '@glimmer/util'; import type * as mir from './mir'; import WireFormatDebugger from '../../wire-format-debug'; -import { CONTENT } from './content'; +import { ContentList } from './content'; export function visit(template: mir.Template): WireFormat.SerializedTemplateBlock { - let statements = CONTENT.list(template.body); + let statements = ContentList(template.body); let scope = template.scope; let block: WireFormat.SerializedTemplateBlock = [statements, scope.symbols, scope.upvars]; diff --git a/packages/@glimmer/compiler/lib/passes/2-encoding/mir.ts b/packages/@glimmer/compiler/lib/passes/2-encoding/mir.ts index c006e56607..1197c07631 100644 --- a/packages/@glimmer/compiler/lib/passes/2-encoding/mir.ts +++ b/packages/@glimmer/compiler/lib/passes/2-encoding/mir.ts @@ -1,4 +1,4 @@ -import type { CurriedType, PresentArray } from '@glimmer/interfaces'; +import type { CurriedType, Optional, PresentArray } from '@glimmer/interfaces'; import type { ASTv2, BlockSymbolTable, @@ -10,11 +10,17 @@ import { node } from '@glimmer/syntax'; import type { AnyOptionalList, OptionalList, PresentList } from '../../shared/list'; +/** + * Represents the root of a parsed template. + */ export class Template extends node('Template').fields<{ scope: ProgramSymbolTable; - body: Statement[]; + body: Content[]; }>() {} +/** + * Syntax: `{{#in-element ...}} ... {{/in-element}}` + */ export class InElement extends node('InElement').fields<{ guid: string; insertBefore: ExpressionNode | Missing; @@ -22,20 +28,50 @@ export class InElement extends node('InElement').fields<{ block: NamedBlock; }>() {} +/** + * Used internally in the `unless` keywords. + */ export class Not extends node('Not').fields<{ value: ExpressionNode }>() {} -export class If extends node('If').fields<{ +/** + * Syntaxes + * + * - `{{#if ...}} ... {{/if}}` + * - `{{#unless ...}} ... {{/unless}}` + * + * The `unless` keyword is implemented as a special case of the `if` keyword: + * + * ```hbs + * {{#unless condition}} + * ... + * {{/unless}} + * ``` + * + * is compiled into: + * + * ```hbs + * {{#if (%not condition)}} + * ... + * {{/if}} + * ``` + * + * where `%not` is the above {@linkcode Not} node. + */ +export class IfContent extends node('IfContent').fields<{ condition: ExpressionNode; block: NamedBlock; inverse: NamedBlock | null; }>() {} -export class IfInline extends node('IfInline').fields<{ - condition: ExpressionNode; - truthy: ExpressionNode; - falsy: ExpressionNode | null; -}>() {} - +/** + * Syntax: + * + * ```hbs + * {{#each key= as |y|}} + * ... + * {{/each}} + * ``` + */ export class Each extends node('Each').fields<{ value: ExpressionNode; key: ExpressionNode | null; @@ -43,45 +79,97 @@ export class Each extends node('Each').fields<{ inverse: NamedBlock | null; }>() {} +/** + * Syntax: + * + * ```hbs + * {{#let as |y|}} + * ... + * {{/let}} + * ``` + */ export class Let extends node('Let').fields<{ positional: Positional; block: NamedBlock; }>() {} +/** + * Syntax: + * + * ```hbs + * {{#-with-dynamic-vars }} + * ... + * {{/with}} + * ``` + */ export class WithDynamicVars extends node('WithDynamicVars').fields<{ named: NamedArguments; block: NamedBlock; }>() {} +/** + * Syntax: + * + * - `(-get-dynamic-var )` + * - `{{-get-dynamic-var }}` + */ export class GetDynamicVar extends node('GetDynamicVar').fields<{ name: ExpressionNode; }>() {} +/** + * Syntax: + * + * - `{{log ...}}` + * - `(log ...)` + */ export class Log extends node('Log').fields<{ positional: Positional; }>() {} -export class InvokeComponent extends node('InvokeComponent').fields<{ - definition: ExpressionNode; +/** + * Syntax: + * + * - `{{component }}` + * - `{{#component }}` + */ +export class InvokeComponentKeyword extends node('InvokeComponentKeyword').fields<{ + definition: Exclude; args: Args; - blocks: NamedBlocks | null; + blocks?: Optional; }>() {} -export class NamedBlocks extends node('NamedBlocks').fields<{ - blocks: OptionalList; +/** + * Syntax: + * + * - `{{component "name-to-resolve"}}` + * - `{{#component "name-to-resolve"}}` + */ +export class InvokeResolvedComponentKeyword extends node('InvokeResolvedComponentKeyword').fields<{ + definition: string; + args: Args; + blocks?: Optional; }>() {} -export class NamedBlock extends node('NamedBlock').fields<{ - scope: BlockSymbolTable; - name: SourceSlice; - body: Statement[]; -}>() {} export class AppendTrustedHTML extends node('AppendTrustedHTML').fields<{ html: ExpressionNode; }>() {} + +/** + * Syntax: + * + * - `{{}}` where `expr` is not a resolved or lexical reference. + */ export class AppendValue extends node('AppendValue').fields<{ value: ExpressionNode }>() {} export class AppendComment extends node('AppendComment').fields<{ value: SourceSlice }>() {} +export class Yield extends node('Yield').fields<{ + target: SourceSlice; + to: number; + positional: Positional; +}>() {} +export class Debugger extends node('Debugger').fields<{ scope: SymbolTable }>() {} + export class Component extends node('Component').fields<{ tag: ExpressionNode; params: ElementParameters; @@ -116,7 +204,7 @@ export class DynamicAttr extends node('DynamicAttr').fields<{ export class SimpleElement extends node('SimpleElement').fields<{ tag: SourceSlice; params: ElementParameters; - body: Statement[]; + body: Content[]; dynamicFeatures: boolean; }>() {} @@ -124,18 +212,36 @@ export class ElementParameters extends node('ElementParameters').fields<{ body: AnyOptionalList; }>() {} -export class Yield extends node('Yield').fields<{ - target: SourceSlice; - to: number; - positional: Positional; -}>() {} -export class Debugger extends node('Debugger').fields<{ scope: SymbolTable }>() {} - export class CallExpression extends node('CallExpression').fields<{ callee: ExpressionNode; args: Args; }>() {} +/** + * Syntax: `(if ...)` + * + * The expression form of `unless` is implemented similarly to the block form: + * + * ```hbs + * {{#let (unless x y z) as |z|}} + * ... + * {{/let}} + * ``` + * + * is compiled into: + * + * ```hbs + * {{#let (if (%not x) y z) as |z|}} + * ... + * {{/let}} + * ``` + */ +export class IfExpression extends node('IfExpression').fields<{ + condition: ExpressionNode; + truthy: ExpressionNode; + falsy: ExpressionNode | null; +}>() {} + export class Modifier extends node('Modifier').fields<{ callee: ExpressionNode; args: Args }>() {} export class InvokeBlock extends node('InvokeBlock').fields<{ head: PathExpression | ASTv2.VariableReference; @@ -178,6 +284,16 @@ export class Args extends node('Args').fields<{ }>() {} export class Tail extends node('Tail').fields<{ members: PresentArray }>() {} +export class NamedBlocks extends node('NamedBlocks').fields<{ + blocks: OptionalList; +}>() {} + +export class NamedBlock extends node('NamedBlock').fields<{ + scope: BlockSymbolTable; + name: SourceSlice; + body: Content[]; +}>() {} + export type ExpressionNode = | ASTv2.LiteralExpression | ASTv2.KeywordExpression @@ -187,7 +303,7 @@ export type ExpressionNode = | InterpolateExpression | CallExpression | Not - | IfInline + | IfExpression | HasBlock | HasBlockParams | Curry @@ -205,8 +321,8 @@ export type Internal = | NamedBlock | NamedBlocks | ElementParameters; -export type ExprLike = ExpressionNode | Internal; -export type Statement = + +export type Content = | InElement | Debugger | Yield @@ -216,8 +332,9 @@ export type Statement = | SimpleElement | InvokeBlock | AppendComment - | If + | IfContent | Each | Let | WithDynamicVars - | InvokeComponent; + | InvokeComponentKeyword + | InvokeResolvedComponentKeyword; diff --git a/packages/@glimmer/compiler/lib/wire-format-debug.ts b/packages/@glimmer/compiler/lib/wire-format-debug.ts index 36608bb0f0..8500f44242 100644 --- a/packages/@glimmer/compiler/lib/wire-format-debug.ts +++ b/packages/@glimmer/compiler/lib/wire-format-debug.ts @@ -169,7 +169,7 @@ export default class WireFormatDebugger { case Op.GetFreeAsModifierHead: return ['GetFreeAsModifierHead', this.upvars[opcode[1]], opcode[2]]; - case Op.GetSymbol: { + case Op.GetLocalSymbol: { if (opcode[1] === 0) { return ['get-symbol', 'this', opcode[2]]; } else { @@ -219,25 +219,19 @@ export default class WireFormatDebugger { case Op.InvokeLexicalComponent: return ['component', this.formatOpcode(opcode[1]), this.formatComponentArgs(opcode[2])]; - case Op.InvokeDynamicComponent: - case Op.InvokeResolvedComponent: + case Op.InvokeComponentKeyword: return [ - opcode[0] === Op.InvokeDynamicComponent ? 'component:dynamic' : 'component:resolved', + 'component:keyword', this.formatOpcode(opcode[1]), this.formatBlockArgs(opcode[2]), ]; - case Op.LexicalBlockComponent: case Op.DynamicBlock: { - const [op, path, args] = opcode; - return [ - op === Op.DynamicBlock ? 'block:dynamic' : 'block', - this.formatOpcode(path), - this.formatBlockArgs(args), - ]; + const [, path, args] = opcode; + return ['block', this.formatOpcode(path), this.formatBlockArgs(args)]; } - case Op.DynamicComponent: + case Op.InvokeDynamicComponent: case Op.ResolvedComponent: { const [op, path, args] = opcode; return [ diff --git a/packages/@glimmer/compiler/test/compiler-test.ts b/packages/@glimmer/compiler/test/compiler-test.ts index b7814c932a..146a80a7a3 100644 --- a/packages/@glimmer/compiler/test/compiler-test.ts +++ b/packages/@glimmer/compiler/test/compiler-test.ts @@ -28,8 +28,18 @@ function compile(content: string): SerializedTemplate { return assign({}, parsed, { block }); } -function test(desc: string, template: string, ...expectedStatements: BuilderStatement[]) { - QUnit.test(desc, (assert) => { +function testSyntax({ + desc, + template, + expectedStatements, + testFn, +}: { + desc: string; + template: string; + expectedStatements: BuilderStatement[]; + testFn: (desc: string, block: QUnit.TestFunctionCallback) => void; +}) { + testFn(desc, (assert) => { let actual = compile(template); let symbols = new ProgramSymbols(); @@ -45,6 +55,19 @@ function test(desc: string, template: string, ...expectedStatements: BuilderStat }); } +function test(desc: string, template: string, ...expectedStatements: BuilderStatement[]) { + testSyntax({ desc, template, expectedStatements, testFn: QUnit.test }); +} + +test.todo = (desc: string, template: string, ...expectedStatements: BuilderStatement[]) => { + testSyntax({ + desc, + template, + expectedStatements, + testFn: (desc: string, block: QUnit.TestFunctionCallback) => QUnit.todo(desc, block), + }); +}; + QUnit.test( '@arguments are on regular non-component/regular HTML nodes throws syntax error', (assert) => { @@ -388,7 +411,7 @@ test('double curlies', `
{{title}}
`, ['
', ['^title']]); test('triple curlies', `
{{{title}}}
`, ['
', [[BUILDER_APPEND, '^title', true]]]); -test( +test.todo( 'triple curly helpers', `{{{unescaped "Yolo"}}} {{escaped "Yolo"}}`, [BUILDER_APPEND, ['(^unescaped)', [s`Yolo`]], true], diff --git a/packages/@glimmer/constants/lib/builder-constants.ts b/packages/@glimmer/constants/lib/builder-constants.ts index ea2caaa8cf..b708c599cb 100644 --- a/packages/@glimmer/constants/lib/builder-constants.ts +++ b/packages/@glimmer/constants/lib/builder-constants.ts @@ -80,8 +80,8 @@ export type HeadKind = export type LOCAL_VAR = 'Local'; export const LOCAL_VAR: LOCAL_VAR = 'Local'; -export type FREE_VAR = 'Free'; -export const FREE_VAR: FREE_VAR = 'Free'; +export type FREE_VAR = 'Resolved'; +export const FREE_VAR: FREE_VAR = 'Resolved'; export type ARG_VAR = 'Arg'; export const ARG_VAR: ARG_VAR = 'Arg'; diff --git a/packages/@glimmer/interfaces/lib/compile/wire-format/api.ts b/packages/@glimmer/interfaces/lib/compile/wire-format/api.ts index 3b5e606f73..2420e9e1bf 100644 --- a/packages/@glimmer/interfaces/lib/compile/wire-format/api.ts +++ b/packages/@glimmer/interfaces/lib/compile/wire-format/api.ts @@ -24,32 +24,31 @@ import type { DynamicArgOpcode, DynamicAttrOpcode, DynamicBlockOpcode, - DynamicComponentOpcode, EachOpcode, FlushElementOpcode, GetDynamicVarOpcode, - GetFreeAsComponentHeadOpcode, - GetFreeAsComponentOrHelperHeadOpcode, - GetFreeAsHelperHeadOpcode, - GetFreeAsModifierHeadOpcode, GetLexicalSymbolOpcode, + GetLocalSymbolOpcode, GetStrictKeywordOpcode, - GetSymbolOpcode, HasBlockOpcode, HasBlockParamsOpcode, IfInlineOpcode, IfOpcode, InElementOpcode, + InvokeComponentKeywordOpcode, InvokeDynamicComponentOpcode, InvokeLexicalComponentOpcode, InvokeResolvedComponentOpcode, LetOpcode, - LexicalBlockComponentOpcode, LexicalModifierOpcode, LogOpcode, NotOpcode, OpenElementOpcode, OpenElementWithSplatOpcode, + ResolveAsComponentHeadOpcode, + ResolveAsComponentOrHelperHeadOpcode, + ResolveAsHelperHeadOpcode, + ResolveAsModifierHeadOpcode, ResolvedBlockOpcode, ResolvedComponentOpcode, ResolvedModifierOpcode, @@ -69,21 +68,21 @@ import type { export type * from './opcodes.js'; export type * from './resolution.js'; -export type TupleSyntax = Statement | TupleExpression; +export type TupleSyntax = Content | TupleExpression; export type TemplateReference = Nullable; export type YieldTo = number; -export type StatementSexpOpcode = Statement[0]; -export type StatementSexpOpcodeMap = { - [TSexpOpcode in Statement[0]]: Extract; +export type ContentSexpOpcode = Content[0]; +export type ContentSexpOpcodeMap = { + [TSexpOpcode in Content[0]]: Extract; }; export type ExpressionSexpOpcode = TupleExpression[0]; export type ExpressionSexpOpcodeMap = { [TSexpOpcode in TupleExpression[0]]: Extract; }; -export interface SexpOpcodeMap extends ExpressionSexpOpcodeMap, StatementSexpOpcodeMap {} +export interface SexpOpcodeMap extends ExpressionSexpOpcodeMap, ContentSexpOpcodeMap {} export type SexpOpcode = keyof SexpOpcodeMap; export namespace Core { @@ -125,34 +124,34 @@ export namespace Expressions { export type Params = Core.Params; export type Hash = Core.Hash; - export type GetSymbol = [GetSymbolOpcode, number]; + export type GetLocalSymbol = [GetLocalSymbolOpcode, number]; export type GetLexicalSymbol = [GetLexicalSymbolOpcode, number]; export type GetStrictKeyword = [GetStrictKeywordOpcode, number]; - export type GetFreeAsComponentOrHelperHead = [GetFreeAsComponentOrHelperHeadOpcode, number]; - export type GetFreeAsHelperHead = [GetFreeAsHelperHeadOpcode, number]; - export type GetFreeAsModifierHead = [GetFreeAsModifierHeadOpcode, number]; - export type GetFreeAsComponentHead = [GetFreeAsComponentHeadOpcode, number]; + export type GetFreeAsComponentOrHelperHead = [ResolveAsComponentOrHelperHeadOpcode, number]; + export type GetFreeAsHelperHead = [ResolveAsHelperHeadOpcode, number]; + export type GetFreeAsModifierHead = [ResolveAsModifierHeadOpcode, number]; + export type GetFreeAsComponentHead = [ResolveAsComponentHeadOpcode, number]; export type GetUnknownAppend = GetFreeAsComponentOrHelperHead | GetFreeAsHelperHead; - export type GetContextualFree = + export type GetResolved = | GetFreeAsComponentOrHelperHead | GetFreeAsHelperHead | GetFreeAsModifierHead | GetFreeAsComponentHead; - export type GetFree = GetStrictKeyword | GetContextualFree; - export type GetVar = GetSymbol | GetLexicalSymbol | GetFree; + export type GetResolvedOrKeyword = GetStrictKeyword | GetResolved; + export type GetVar = GetLocalSymbol | GetLexicalSymbol | GetResolvedOrKeyword; - export type GetPathSymbol = [GetSymbolOpcode, number, Path]; + export type GetPathSymbol = [GetLocalSymbolOpcode, number, Path]; export type GetPathLexicalSymbol = [GetLexicalSymbolOpcode, number, Path]; export type GetPathFreeAsComponentOrHelperHead = [ - GetFreeAsComponentOrHelperHeadOpcode, + ResolveAsComponentOrHelperHeadOpcode, number, Path, ]; - export type GetPathFreeAsHelperHead = [GetFreeAsHelperHeadOpcode, number, Path]; - export type GetPathFreeAsModifierHead = [GetFreeAsModifierHeadOpcode, number, Path]; - export type GetPathFreeAsComponentHead = [GetFreeAsComponentHeadOpcode, number, Path]; + export type GetPathFreeAsHelperHead = [ResolveAsHelperHeadOpcode, number, Path]; + export type GetPathFreeAsModifierHead = [ResolveAsModifierHeadOpcode, number, Path]; + export type GetPathFreeAsComponentHead = [ResolveAsComponentHeadOpcode, number, Path]; export type GetPathContextualFree = | GetPathFreeAsComponentOrHelperHead @@ -240,7 +239,7 @@ export type ATag = 3; export type WellKnownTagName = DivTag | SpanTag | PTag | ATag; -export namespace Statements { +export namespace Contents { export type Expression = Expressions.Expression | undefined; export type Params = Core.Params; export type Hash = Core.Hash; @@ -258,11 +257,8 @@ export namespace Statements { | UnknownAppend | UnknownTrustingAppend; export type SomeModifier = LexicalModifier | ResolvedModifier; - export type SomeInvokeComponent = - | InvokeDynamicComponent - | InvokeResolvedComponent - | InvokeLexicalComponent; - export type SomeBlock = LexicalBlockComponent | ResolvedBlock | DynamicBlock; + export type SomeInvokeComponent = InvokeDynamicComponent | InvokeLexicalComponent; + export type SomeBlock = ResolvedBlock | DynamicBlock | InvokeLexicalComponent; export type UnknownAppend = [UnknownAppendOpcode, Expressions.GetUnknownAppend]; export type UnknownTrustingAppend = [UnknownTrustingAppendOpcode, Expressions.GetUnknownAppend]; @@ -285,13 +281,6 @@ export namespace Statements { path: Expressions.GetVar, args?: Optional, ]; - export type LexicalBlockComponent = [ - LexicalBlockComponentOpcode, - path: Expressions.GetVar, - // Blocks don't have splattributes, so this is normal block args, - // not ComponentArgs. - args?: Optional, - ]; export type DynamicBlock = [ DynamicBlockOpcode, path: Expressions.Get, @@ -306,7 +295,7 @@ export namespace Statements { ]; export type DynamicComponent = [ - op: DynamicComponentOpcode, + op: InvokeDynamicComponentOpcode, tag: Expression, args?: Optional, ]; @@ -386,7 +375,7 @@ export namespace Statements { ]; export type InvokeDynamicComponent = [ - op: InvokeDynamicComponentOpcode, + op: InvokeComponentKeywordOpcode, definition: Expression, args?: Optional, ]; @@ -399,14 +388,14 @@ export namespace Statements { export type InvokeResolvedComponent = [ op: InvokeResolvedComponentOpcode, - definition: Expressions.GetVar, + definition: string, args?: Optional, ]; /** * A Handlebars statement */ - export type Statement = + export type Content = | SomeAppend | SomeModifier | SomeInvokeComponent @@ -445,12 +434,12 @@ export namespace Statements { } /** A Handlebars statement */ -export type Statement = Statements.Statement; -export type Attribute = Statements.Attribute; -export type Argument = Statements.Argument; -export type ElementParameter = Statements.ElementParameter; +export type Content = Contents.Content; +export type Attribute = Contents.Attribute; +export type Argument = Contents.Argument; +export type ElementParameter = Contents.ElementParameter; -export type SexpSyntax = Statement | TupleExpression; +export type SexpSyntax = Content | TupleExpression; // TODO this undefined is related to the other TODO in this file export type Syntax = SexpSyntax | Expressions.Value | undefined; @@ -465,15 +454,15 @@ export type SyntaxWithInternal = /** * A JSON object that the Block was serialized into. */ -export type SerializedBlock = [statements: Statements.Statement[]]; +export type SerializedBlock = [statements: Contents.Content[]]; -export type SerializedInlineBlock = [statements: Statements.Statement[], parameters: number[]]; +export type SerializedInlineBlock = [statements: Contents.Content[], parameters: number[]]; /** * A JSON object that the compiled TemplateBlock was serialized into. */ export type SerializedTemplateBlock = [ - statements: Statements.Statement[], + statements: Contents.Content[], locals: string[], upvars: string[], lexicalSymbols?: string[], diff --git a/packages/@glimmer/interfaces/lib/compile/wire-format/opcodes.d.ts b/packages/@glimmer/interfaces/lib/compile/wire-format/opcodes.d.ts index 1cac50c7c0..79b0efc5be 100644 --- a/packages/@glimmer/interfaces/lib/compile/wire-format/opcodes.d.ts +++ b/packages/@glimmer/interfaces/lib/compile/wire-format/opcodes.d.ts @@ -16,7 +16,7 @@ export type ResolvedBlockOpcode = 8; export type LexicalBlockComponentOpcode = 9; export type DynamicBlockOpcode = 57; export type ResolvedComponentOpcode = 10; -export type DynamicComponentOpcode = 58; +export type InvokeDynamicComponentOpcode = 58; export type OpenElementOpcode = 11; export type OpenElementWithSplatOpcode = 12; @@ -45,7 +45,7 @@ export type UnknownInvokeOpcode = 30; export type ConcatOpcode = 31; // Get a local value via symbol -export type GetSymbolOpcode = 32; // GetPath + 0-2, +export type GetLocalSymbolOpcode = 32; // GetPath + 0-2, // Lexical symbols are values that are in scope in the template in strict mode export type GetLexicalSymbolOpcode = 33; // If a free variable is not a lexical symbol in strict mode, it must be a keyword. @@ -53,11 +53,11 @@ export type GetLexicalSymbolOpcode = 33; export type GetStrictKeywordOpcode = 34; // a component or helper (`{{ x}}` in append position) -export type GetFreeAsComponentOrHelperHeadOpcode = 35; +export type ResolveAsComponentOrHelperHeadOpcode = 35; // a call head `(x)` -export type GetFreeAsHelperHeadOpcode = 37; -export type GetFreeAsModifierHeadOpcode = 38; -export type GetFreeAsComponentHeadOpcode = 39; +export type ResolveAsHelperHeadOpcode = 37; +export type ResolveAsModifierHeadOpcode = 38; +export type ResolveAsComponentHeadOpcode = 39; // Keyword Statements export type InElementOpcode = 40; @@ -65,7 +65,7 @@ export type IfOpcode = 41; export type EachOpcode = 42; export type LetOpcode = 44; export type WithDynamicVarsOpcode = 45; -export type InvokeDynamicComponentOpcode = 46; +export type InvokeComponentKeywordOpcode = 46; export type InvokeResolvedComponentOpcode = 47; export type InvokeLexicalComponentOpcode = 55; @@ -78,15 +78,21 @@ export type IfInlineOpcode = 52; export type GetDynamicVarOpcode = 53; export type LogOpcode = 54; -export type GetStartOpcode = GetSymbolOpcode; -export type GetEndOpcode = GetFreeAsComponentHeadOpcode; -export type GetLooseFreeEndOpcode = GetFreeAsComponentHeadOpcode; +export type GetStartOpcode = GetLocalSymbolOpcode; +export type GetEndOpcode = ResolveAsComponentHeadOpcode; +export type GetLooseFreeEndOpcode = ResolveAsComponentHeadOpcode; -export type GetContextualFreeOpcode = - | GetFreeAsComponentOrHelperHeadOpcode - | GetFreeAsHelperHeadOpcode - | GetFreeAsModifierHeadOpcode - | GetFreeAsComponentHeadOpcode +export type GetResolvedOpcode = + | ResolveAsComponentOrHelperHeadOpcode + | ResolveAsHelperHeadOpcode + | ResolveAsModifierHeadOpcode + | ResolveAsComponentHeadOpcode; + +export type GetResolvedOrKeywordOpcode = + | ResolveAsComponentOrHelperHeadOpcode + | ResolveAsHelperHeadOpcode + | ResolveAsModifierHeadOpcode + | ResolveAsComponentHeadOpcode | GetStrictKeywordOpcode; export type AttrOpcode = diff --git a/packages/@glimmer/opcode-compiler/lib/compilable-template.ts b/packages/@glimmer/opcode-compiler/lib/compilable-template.ts index 083a0088d4..ba0bd9a486 100644 --- a/packages/@glimmer/opcode-compiler/lib/compilable-template.ts +++ b/packages/@glimmer/opcode-compiler/lib/compilable-template.ts @@ -10,7 +10,7 @@ import type { Nullable, SerializedBlock, SerializedInlineBlock, - Statement, + Content, SymbolTable, WireFormat, } from '@glimmer/interfaces'; @@ -36,7 +36,7 @@ class CompilableTemplateImpl implements CompilableTemplat compiled: Nullable = null; constructor( - readonly statements: WireFormat.Statement[], + readonly statements: WireFormat.Content[], readonly meta: BlockMetadata, // Part of CompilableTemplate readonly symbolTable: S, @@ -79,7 +79,7 @@ function maybeCompile( } export function compileStatements( - statements: Statement[], + statements: Content[], meta: BlockMetadata, syntaxContext: EvaluationContext ): HandleResult { diff --git a/packages/@glimmer/opcode-compiler/lib/opcode-builder/helpers/resolution.ts b/packages/@glimmer/opcode-compiler/lib/opcode-builder/helpers/resolution.ts index d6d8473234..306fa5ca48 100644 --- a/packages/@glimmer/opcode-compiler/lib/opcode-builder/helpers/resolution.ts +++ b/packages/@glimmer/opcode-compiler/lib/opcode-builder/helpers/resolution.ts @@ -20,7 +20,7 @@ function isGetLikeTuple(opcode: Expressions.Expression): opcode is Expressions.T function makeResolutionTypeVerifier(typeToVerify: SexpOpcode) { return ( opcode: Expressions.Expression - ): opcode is Expressions.GetFree | Expressions.GetLexicalSymbol => { + ): opcode is Expressions.GetResolvedOrKeyword | Expressions.GetLexicalSymbol => { if (!isGetLikeTuple(opcode)) return false; let type = opcode[0]; diff --git a/packages/@glimmer/opcode-compiler/lib/syntax/expressions.ts b/packages/@glimmer/opcode-compiler/lib/syntax/expressions.ts index c1bed809bc..c91ff4e300 100644 --- a/packages/@glimmer/opcode-compiler/lib/syntax/expressions.ts +++ b/packages/@glimmer/opcode-compiler/lib/syntax/expressions.ts @@ -65,14 +65,14 @@ EXPRESSIONS.add(SexpOpcodes.Curry, (op, [, expr, type, args]) => { Curry(op, type, expr, args); }); -export const GetSymbol = (encode: EncodeOp, sym: number): void => { +export const GetLocalSymbol = (encode: EncodeOp, sym: number): void => { encode.op(VM_GET_VARIABLE_OP, sym); }; export const GetPath = (encode: EncodeOp, path: string[]): void => withPath(encode, path); -EXPRESSIONS.add(SexpOpcodes.GetSymbol, (encode, [, sym, path]) => { - GetSymbol(encode, sym); +EXPRESSIONS.add(SexpOpcodes.GetLocalSymbol, (encode, [, sym, path]) => { + GetLocalSymbol(encode, sym); if (path) GetPath(encode, path); }); diff --git a/packages/@glimmer/opcode-compiler/lib/syntax/statements.ts b/packages/@glimmer/opcode-compiler/lib/syntax/statements.ts index af2bea0da5..2a1b142b50 100644 --- a/packages/@glimmer/opcode-compiler/lib/syntax/statements.ts +++ b/packages/@glimmer/opcode-compiler/lib/syntax/statements.ts @@ -1,7 +1,7 @@ import type { CompileTimeComponent, + ContentSexpOpcode, Optional, - StatementSexpOpcode, WellKnownAttrName, WellKnownTagName, WireFormat, @@ -60,7 +60,7 @@ import { namedBlocks } from '../utils'; import { CloseElement, Comment, FlushElement, LexicalModifier } from './api'; import { Compilers } from './compilers'; -export const STATEMENTS = new Compilers(); +export const STATEMENTS = new Compilers(); const INFLATE_ATTR_TABLE: { [I in WellKnownAttrName]: string; @@ -269,12 +269,6 @@ STATEMENTS.add(Op.TrustingAppend, (encode, [, value]) => { } }); -STATEMENTS.add(Op.LexicalBlockComponent, (encode, [, expr, args]) => { - const component = encode.getLexicalComponent(expr); - - InvokeComponent(encode, component, args, args?.params); -}); - STATEMENTS.add(Op.ResolvedBlock, (encode, [, expr, args]) => { const component = encode.resolveComponent(expr); @@ -407,31 +401,21 @@ STATEMENTS.add(Op.ResolvedComponent, (encode, [, expr, args]) => { InvokeComponent(encode, component, args); }); -STATEMENTS.add(Op.DynamicComponent, (encode, [, expr, args]) => { +STATEMENTS.add(Op.InvokeDynamicComponent, (encode, [, expr, args]) => { // otherwise, the component name was an expression, so resolve the expression // and invoke it as a dynamic component InvokeDynamicComponent(encode, expr, args, { atNames: true, curried: true }); }); -STATEMENTS.add(Op.InvokeLexicalComponent, (encode, [, expr, args]) => { - const component = encode.getLexicalComponent(expr); - InvokeComponent(encode, component, args); -}); - -STATEMENTS.add(Op.InvokeDynamicComponent, (encode, [, expr, args]) => { +STATEMENTS.add(Op.InvokeComponentKeyword, (encode, [, expr, args]) => { InvokeDynamicComponent(encode, expr, compact({ hash: args?.hash, blocks: args?.blocks }), { positional: args?.params, }); }); -STATEMENTS.add(Op.InvokeResolvedComponent, (encode, [, expr, args]) => { - const component = encode.resolveComponent(expr); - InvokeComponent( - encode, - component, - compact({ hash: args?.hash, blocks: args?.blocks }), - args?.params - ); +STATEMENTS.add(Op.InvokeLexicalComponent, (encode, [, expr, args]) => { + const component = encode.getLexicalComponent(expr); + InvokeComponent(encode, component, args); }); function hashToArgs(hash: Optional): Optional { diff --git a/packages/@glimmer/syntax/lib/v2/builders.ts b/packages/@glimmer/syntax/lib/v2/builders.ts index 918f2f4d0f..9e800f79e8 100644 --- a/packages/@glimmer/syntax/lib/v2/builders.ts +++ b/packages/@glimmer/syntax/lib/v2/builders.ts @@ -184,7 +184,7 @@ export class Builder { context: ASTv2.FreeVarResolution; symbol: number; loc: SourceSpan; - }): ASTv2.FreeVarReference { + }): ASTv2.ResolvedVarReference { localAssert( name !== 'this', `You called builders.freeVar() with 'this'. Call builders.this instead` @@ -194,7 +194,7 @@ export class Builder { `You called builders.freeVar() with '${name}'. Call builders.at('${name}') instead` ); - return new ASTv2.FreeVarReference({ + return new ASTv2.ResolvedVarReference({ name, resolution: context, symbol, @@ -205,7 +205,7 @@ export class Builder { localVar( name: string, symbol: number, - isTemplateLocal: boolean, + isLexical: boolean, loc: SourceSpan ): ASTv2.VariableReference { localAssert( @@ -216,7 +216,7 @@ export class Builder { return new ASTv2.LocalVarReference({ loc, name, - isTemplateLocal, + referenceType: isLexical ? 'lexical' : 'dynamic', symbol, }); } diff --git a/packages/@glimmer/syntax/lib/v2/objects/refs.ts b/packages/@glimmer/syntax/lib/v2/objects/refs.ts index d9ce57e747..8b181a3f7b 100644 --- a/packages/@glimmer/syntax/lib/v2/objects/refs.ts +++ b/packages/@glimmer/syntax/lib/v2/objects/refs.ts @@ -1,7 +1,10 @@ import type { SourceSlice } from '../../source/slice'; +import type { AbstractNode } from './node'; import type { FreeVarResolution } from './resolution'; -import { type AbstractNode, node } from './node'; +import { node } from './node'; + +export type ReferenceType = 'dynamic' | 'resolved' | 'lexical'; /** * Corresponds to `this` at the head of an expression. @@ -19,7 +22,7 @@ export class ArgReference extends node('Arg').fields<{ name: SourceSlice; symbol */ export class LocalVarReference extends node('Local').fields<{ name: string; - isTemplateLocal: boolean; + referenceType: 'dynamic' | 'lexical'; symbol: number; }>() {} @@ -32,16 +35,20 @@ export class LocalVarReference extends node('Local').fields<{ * Note: In strict mode, it must always be a variable that is in a concrete JavaScript scope that * the template will be installed into. */ -export class FreeVarReference extends node('Free').fields<{ +export class ResolvedVarReference extends node('Resolved').fields<{ name: string; resolution: FreeVarResolution; symbol: number; }>() {} -export type VariableReference = ThisReference | ArgReference | LocalVarReference | FreeVarReference; +export type VariableReference = + | ThisReference + | ArgReference + | LocalVarReference + | ResolvedVarReference; export function isVariableReference(node: AbstractNode): node is VariableReference { return ( - node.type === 'This' || node.type === 'Arg' || node.type === 'Local' || node.type === 'Free' + node.type === 'This' || node.type === 'Arg' || node.type === 'Local' || node.type === 'Resolved' ); } diff --git a/packages/@glimmer/syntax/lib/v2/objects/resolution.ts b/packages/@glimmer/syntax/lib/v2/objects/resolution.ts index 6be6345ddd..628f92127f 100644 --- a/packages/@glimmer/syntax/lib/v2/objects/resolution.ts +++ b/packages/@glimmer/syntax/lib/v2/objects/resolution.ts @@ -5,8 +5,8 @@ * 2. Namespaced resolution */ -import type { GetContextualFreeOpcode } from '@glimmer/interfaces'; -import { SexpOpcodes } from '@glimmer/wire-format'; +import type { GetResolvedOrKeywordOpcode } from '@glimmer/interfaces'; +import { SexpOpcodes as Op } from '@glimmer/wire-format'; import type { FreeVarNamespace } from './constants'; @@ -19,7 +19,7 @@ import { COMPONENT_VAR_NS, HELPER_VAR_NS, MODIFIER_VAR_NS } from './constants'; * 2. in an local variable invocation with dot paths */ export const STRICT_RESOLUTION = { - resolution: (): GetContextualFreeOpcode => SexpOpcodes.GetStrictKeyword, + resolution: (): GetResolvedOrKeywordOpcode => Op.GetStrictKeyword, serialize: (): SerializedResolution => 'Strict', isAngleBracket: false as const, }; @@ -97,18 +97,18 @@ export class LooseModeResolution { readonly isAngleBracket = false ) {} - resolution(): GetContextualFreeOpcode { + resolution(): GetResolvedOrKeywordOpcode { if (this.namespaces.length === 1) { switch (this.namespaces[0]) { case HELPER_VAR_NS: - return SexpOpcodes.GetFreeAsHelperHead; + return Op.GetFreeAsHelperHead; case MODIFIER_VAR_NS: - return SexpOpcodes.GetFreeAsModifierHead; + return Op.GetFreeAsModifierHead; case COMPONENT_VAR_NS: - return SexpOpcodes.GetFreeAsComponentHead; + return Op.GetFreeAsComponentHead; } } else { - return SexpOpcodes.GetFreeAsComponentOrHelperHead; + return Op.GetFreeAsComponentOrHelperHead; } } diff --git a/packages/@glimmer/syntax/lib/v2/serialize/serialize.ts b/packages/@glimmer/syntax/lib/v2/serialize/serialize.ts index 068993697d..615b0288ae 100644 --- a/packages/@glimmer/syntax/lib/v2/serialize/serialize.ts +++ b/packages/@glimmer/syntax/lib/v2/serialize/serialize.ts @@ -11,7 +11,7 @@ import type { SerializedContentNode, SerializedElementModifier, SerializedExpressionNode, - SerializedFreeVarReference, + SerializedResolvedVarReference, SerializedGlimmerComment, SerializedHtmlComment, SerializedHtmlOrSplatAttr, @@ -35,9 +35,9 @@ import type { import { SourceSlice } from '../../source/slice'; export class RefSerializer { - keyword(keyword: ASTv2.KeywordExpression): SerializedFreeVarReference { + keyword(keyword: ASTv2.KeywordExpression): SerializedResolvedVarReference { return { - type: 'Free', + type: 'Resolved', loc: keyword.loc.serialize(), resolution: 'Strict', name: keyword.name, @@ -52,9 +52,9 @@ export class RefSerializer { }; } - free(ref: ASTv2.FreeVarReference): SerializedFreeVarReference { + resolved(ref: ASTv2.ResolvedVarReference): SerializedResolvedVarReference { return { - type: 'Free', + type: 'Resolved', loc: ref.loc.serialize(), resolution: ref.resolution.serialize(), name: ref.name, @@ -205,7 +205,7 @@ export class ContentSerializer { invokeComponent(node: ASTv2.InvokeComponent): SerializedInvokeComponent { return { - type: 'InvokeComponent', + type: 'ComponentKeywordExpr', loc: node.loc.serialize(), callee: visit.expr(node.callee), blocks: INTERNAL.namedBlocks(node.blocks), @@ -309,8 +309,8 @@ const visit = { switch (ref.type) { case 'Arg': return REF.arg(ref); - case 'Free': - return REF.free(ref); + case 'Resolved': + return REF.resolved(ref); case 'Local': return REF.local(ref); case 'This': diff --git a/packages/@glimmer/syntax/lib/v2/serialize/types.ts b/packages/@glimmer/syntax/lib/v2/serialize/types.ts index aab234a4ce..ffed267929 100644 --- a/packages/@glimmer/syntax/lib/v2/serialize/types.ts +++ b/packages/@glimmer/syntax/lib/v2/serialize/types.ts @@ -38,8 +38,8 @@ export interface SerializedLocalVarReference extends SerializedBaseNode { name: string; } -export interface SerializedFreeVarReference extends SerializedBaseNode { - type: 'Free'; +export interface SerializedResolvedVarReference extends SerializedBaseNode { + type: 'Resolved'; name: string; resolution: ASTv2.SerializedResolution; } @@ -48,7 +48,7 @@ export type SerializedVariableReference = | SerializedThisReference | SerializedArgReference | SerializedLocalVarReference - | SerializedFreeVarReference; + | SerializedResolvedVarReference; export interface SerializedCallNode extends SerializedBaseNode { callee: SerializedExpressionNode; @@ -61,7 +61,7 @@ export interface SerializedCallExpression extends SerializedCallNode { export interface SerializedDeprecatedCallExpression extends SerializedBaseNode { type: 'DeprecatedCall'; - callee: SerializedFreeVarReference; + callee: SerializedResolvedVarReference; } export type SerializedExpressionNode = @@ -113,7 +113,7 @@ export interface SerializedInvokeBlock extends SerializedCallNode { } export interface SerializedInvokeComponent extends SerializedBaseNode { - type: 'InvokeComponent'; + type: 'ComponentKeywordExpr'; callee: SerializedExpressionNode; blocks: SerializedNamedBlocks; attrs: SerializedAttrNode[]; diff --git a/packages/@glimmer/wire-format/index.ts b/packages/@glimmer/wire-format/index.ts index 6d2e5b423e..9a8e64b829 100644 --- a/packages/@glimmer/wire-format/index.ts +++ b/packages/@glimmer/wire-format/index.ts @@ -1,4 +1,4 @@ -import type { Expressions, Statement, Statements } from '@glimmer/interfaces'; +import type { Expressions, Content, Contents } from '@glimmer/interfaces'; import { opcodes as Op } from './lib/opcodes'; @@ -14,9 +14,9 @@ export function is(variant: number): (value: unknown) => value is T { } // Statements -export const isFlushElement = is(Op.FlushElement); +export const isFlushElement = is(Op.FlushElement); -export function isAttribute(val: Statement): val is Statements.Attribute { +export function isAttribute(val: Content): val is Contents.Attribute { return ( val[0] === Op.StaticAttr || val[0] === Op.DynamicAttr || @@ -43,10 +43,10 @@ export function isGetPath(expr: Expressions.TupleExpression): expr is Expression export function isGet(expr: Expressions.TupleExpression): expr is Expressions.Get { const [opcode] = expr; - return opcode === Op.GetSymbol || opcode === Op.GetLexicalSymbol || isGetFree(expr); + return opcode === Op.GetLocalSymbol || opcode === Op.GetLexicalSymbol || isGetFree(expr); } -export function isGetFree(expr: Expressions.TupleExpression): expr is Expressions.GetFree { +export function isGetFree(expr: Expressions.TupleExpression): expr is Expressions.GetResolvedOrKeyword { const [opcode] = expr; return opcode === Op.GetStrictKeyword || isGetContextualFree(expr); } @@ -59,7 +59,7 @@ export function isGetLexical( export function isGetContextualFree( expr: Expressions.TupleExpression -): expr is Expressions.GetContextualFree | Expressions.GetPathContextualFree { +): expr is Expressions.GetResolved | Expressions.GetPathContextualFree { const [opcode] = expr; switch (opcode) { case Op.GetFreeAsComponentHead: diff --git a/packages/@glimmer/wire-format/lib/opcodes.ts b/packages/@glimmer/wire-format/lib/opcodes.ts index a3b1df5da6..0769691344 100644 --- a/packages/@glimmer/wire-format/lib/opcodes.ts +++ b/packages/@glimmer/wire-format/lib/opcodes.ts @@ -17,22 +17,18 @@ import type { DynamicArgOpcode, DynamicAttrOpcode, DynamicBlockOpcode, - DynamicComponentOpcode, EachOpcode, FlushElementOpcode, GetDynamicVarOpcode, - GetFreeAsComponentHeadOpcode, - GetFreeAsComponentOrHelperHeadOpcode, - GetFreeAsHelperHeadOpcode, - GetFreeAsModifierHeadOpcode, GetLexicalSymbolOpcode, + GetLocalSymbolOpcode, GetStrictKeywordOpcode, - GetSymbolOpcode, HasBlockOpcode, HasBlockParamsOpcode, IfInlineOpcode, IfOpcode, InElementOpcode, + InvokeComponentKeywordOpcode, InvokeDynamicComponentOpcode, InvokeLexicalComponentOpcode, InvokeResolvedComponentOpcode, @@ -43,6 +39,10 @@ import type { NotOpcode, OpenElementOpcode, OpenElementWithSplatOpcode, + ResolveAsComponentHeadOpcode, + ResolveAsComponentOrHelperHeadOpcode, + ResolveAsHelperHeadOpcode, + ResolveAsModifierHeadOpcode, ResolvedBlockOpcode, ResolvedComponentOpcode, ResolvedModifierOpcode, @@ -70,6 +70,14 @@ export const opcodes = { AppendBuiltinHelper: 102 satisfies AppendBuiltinHelperOpcode, UnknownAppend: 2 satisfies UnknownAppendOpcode, UnknownTrustingAppend: 3 satisfies UnknownTrustingAppendOpcode, + + LexicalBlockComponent: 9 satisfies LexicalBlockComponentOpcode, + ResolvedComponent: 10 satisfies ResolvedComponentOpcode, + InvokeDynamicComponent: 58 satisfies InvokeDynamicComponentOpcode, + InvokeComponentKeyword: 46 satisfies InvokeComponentKeywordOpcode, + InvokeResolvedComponent: 47 satisfies InvokeResolvedComponentOpcode, + InvokeLexicalComponent: 55 satisfies InvokeLexicalComponentOpcode, + TrustingAppend: 4 satisfies TrustingAppendOpcode, Comment: 5 satisfies CommentOpcode, LexicalModifier: 6 satisfies LexicalModifierOpcode, @@ -77,9 +85,6 @@ export const opcodes = { StrictModifier: 7 satisfies StrictModifierOpcode, DynamicBlock: 57 satisfies DynamicBlockOpcode, ResolvedBlock: 8 satisfies ResolvedBlockOpcode, - LexicalBlockComponent: 9 satisfies LexicalBlockComponentOpcode, - ResolvedComponent: 10 satisfies ResolvedComponentOpcode, - DynamicComponent: 58 satisfies DynamicComponentOpcode, OpenElement: 11 satisfies OpenElementOpcode, OpenElementWithSplat: 12 satisfies OpenElementWithSplatOpcode, FlushElement: 13 satisfies FlushElementOpcode, @@ -100,21 +105,18 @@ export const opcodes = { CallLexical: 29 satisfies CallLexicalOpcode, UnknownInvoke: 30 satisfies UnknownInvokeOpcode, Concat: 31 satisfies ConcatOpcode, - GetSymbol: 32 satisfies GetSymbolOpcode, + GetLocalSymbol: 32 satisfies GetLocalSymbolOpcode, GetLexicalSymbol: 33 satisfies GetLexicalSymbolOpcode, GetStrictKeyword: 34 satisfies GetStrictKeywordOpcode, - GetFreeAsComponentOrHelperHead: 35 satisfies GetFreeAsComponentOrHelperHeadOpcode, - GetFreeAsHelperHead: 37 satisfies GetFreeAsHelperHeadOpcode, - GetFreeAsModifierHead: 38 satisfies GetFreeAsModifierHeadOpcode, - GetFreeAsComponentHead: 39 satisfies GetFreeAsComponentHeadOpcode, + GetFreeAsComponentOrHelperHead: 35 satisfies ResolveAsComponentOrHelperHeadOpcode, + GetFreeAsHelperHead: 37 satisfies ResolveAsHelperHeadOpcode, + GetFreeAsModifierHead: 38 satisfies ResolveAsModifierHeadOpcode, + GetFreeAsComponentHead: 39 satisfies ResolveAsComponentHeadOpcode, InElement: 40 satisfies InElementOpcode, If: 41 satisfies IfOpcode, Each: 42 satisfies EachOpcode, Let: 44 satisfies LetOpcode, WithDynamicVars: 45 satisfies WithDynamicVarsOpcode, - InvokeDynamicComponent: 46 satisfies InvokeDynamicComponentOpcode, - InvokeResolvedComponent: 47 satisfies InvokeResolvedComponentOpcode, - InvokeLexicalComponent: 55 satisfies InvokeLexicalComponentOpcode, HasBlock: 48 satisfies HasBlockOpcode, HasBlockParams: 49 satisfies HasBlockParamsOpcode, Curry: 50 satisfies CurryOpcode,