ember-source
Version:
A JavaScript framework for creating ambitious web applications
1,563 lines (1,489 loc) • 59.9 kB
JavaScript
import { z as VM_PRIMITIVE_OP, m as VM_RETURN_OP, aP as encodeHandle, aQ as isMachineOp, aF as VM_CONCAT_OP, aw as VM_GET_VARIABLE_OP, y as VM_CONSTANT_REFERENCE_OP, aA as VM_GET_PROPERTY_OP, aD as VM_HAS_BLOCK_OP, aC as VM_SPREAD_BLOCK_OP, N as VM_COMPILE_BLOCK_OP, aE as VM_HAS_BLOCK_PARAMS_OP, aG as VM_IF_INLINE_OP, aH as VM_NOT_OP, aI as VM_GET_DYNAMIC_VAR_OP, q as VM_PUSH_FRAME_OP, aJ as VM_LOG_OP, p as VM_POP_FRAME_OP, H as VM_FETCH_OP, D as VM_PRIMITIVE_REFERENCE_OP, av as VM_HELPER_OP, E as VM_DUP_OP, at as VM_DYNAMIC_HELPER_OP, F as VM_POP_OP, a9 as VM_CAPTURE_ARGS_OP, as as VM_CURRY_OP, aR as isSmallInt, aS as encodeImmediate, u as VM_PUSH_DYNAMIC_SCOPE_OP, I as VM_BIND_DYNAMIC_SCOPE_OP, v as VM_POP_DYNAMIC_SCOPE_OP, aB as VM_GET_BLOCK_OP, O as VM_INVOKE_YIELD_OP, t as VM_POP_SCOPE_OP, s as VM_CHILD_SCOPE_OP, ax as VM_SET_VARIABLE_OP, o as VM_INVOKE_VIRTUAL_OP, w as VM_CONSTANT_OP, M as VM_PUSH_BLOCK_SCOPE_OP, L as VM_PUSH_SYMBOL_TABLE_OP, a8 as VM_PUSH_EMPTY_ARGS_OP, a7 as VM_PUSH_ARGS_OP, J as VM_ENTER_OP, R as VM_JUMP_EQ_OP, n as VM_JUMP_OP, K as VM_EXIT_OP, l as VM_RETURN_TO_OP, Q as VM_JUMP_UNLESS_OP, a5 as VM_PUSH_COMPONENT_DEFINITION_OP, G as VM_LOAD_OP, ad as VM_BEGIN_COMPONENT_TRANSACTION_OP, ab as VM_CREATE_COMPONENT_OP, ac as VM_REGISTER_COMPONENT_DESTRUCTOR_OP, ai as VM_GET_COMPONENT_SELF_OP, az as VM_ROOT_SCOPE_OP, ay as VM_SET_BLOCK_OP, aq as VM_DID_RENDER_LAYOUT_OP, ar as VM_COMMIT_COMPONENT_TRANSACTION_OP, aa as VM_PREPARE_ARGS_OP, am as VM_VIRTUAL_ROOT_SCOPE_OP, an as VM_SET_NAMED_VARIABLES_OP, ao as VM_SET_BLOCKS_OP, ap as VM_INVOKE_COMPONENT_LAYOUT_OP, ae as VM_PUT_COMPONENT_OPERATIONS_OP, X as VM_OPEN_DYNAMIC_ELEMENT_OP, ah as VM_DID_CREATE_ELEMENT_OP, _ as VM_FLUSH_ELEMENT_OP, $ as VM_CLOSE_ELEMENT_OP, f as VM_RESOLVE_CURRIED_COMPONENT_OP, a6 as VM_RESOLVE_DYNAMIC_COMPONENT_OP, g as VM_PUSH_DYNAMIC_COMPONENT_INSTANCE_OP, ak as VM_GET_COMPONENT_LAYOUT_OP, al as VM_POPULATE_LAYOUT_OP, aj as VM_GET_COMPONENT_TAG_NAME_OP, U as VM_COMMENT_OP, a0 as VM_MODIFIER_OP, a1 as VM_DYNAMIC_MODIFIER_OP, a2 as VM_STATIC_ATTR_OP, ag as VM_STATIC_COMPONENT_ATTR_OP, a3 as VM_DYNAMIC_ATTR_OP, af as VM_COMPONENT_ATTR_OP, W as VM_OPEN_ELEMENT_OP, aL as VM_DEBUGGER_OP, T as VM_TEXT_OP, k as VM_INVOKE_STATIC_OP, aK as VM_DYNAMIC_CONTENT_TYPE_OP, Y as VM_PUSH_REMOTE_ELEMENT_OP, Z as VM_POP_REMOTE_ELEMENT_OP, S as VM_TO_BOOLEAN_OP, aM as VM_ENTER_LIST_OP, aO as VM_ITERATE_OP, aN as VM_EXIT_LIST_OP } from './fragment-EpVz5Xuc.js';
import '../@glimmer/validator/index.js';
import './reference-BNqcwZWH.js';
import { a as assert } from './assert-CUCJBR2C.js';
import { e as expect, u as unwrap, S as StackImpl, a as isPresentArray, d as dict } from './collections-GpG8lT2g.js';
import { SexpOpcodes as opcodes } from '../@glimmer/wire-format/index.js';
import { e as $v0, c as $fp, $ as $s0, d as $sp, h as $s1 } from './registers-ylirb0dq.js';
import { b as EMPTY_STRING_ARRAY, E as EMPTY_ARRAY, e as enumerate, r as reverse } from './array-utils-CZQxrdD3.js';
import { ContentType } from '../@glimmer/vm/index.js';
import { h as hasCapability } from './capabilities-DGmQ_mz4.js';
import { A as ARG_SHIFT, M as MACHINE_MASK, I as InternalComponentCapabilities } from './flags-B9qxc-pB.js';
import { a as assign } from './object-utils-AijlD-JH.js';
import { InstructionEncoderImpl } from '../@glimmer/encoder/index.js';
function isGetLikeTuple(opcode) {
return Array.isArray(opcode) && opcode.length === 2;
}
function makeResolutionTypeVerifier(typeToVerify) {
return opcode => {
if (!isGetLikeTuple(opcode)) return false;
let type = opcode[0];
return type === opcodes.GetStrictKeyword || type === opcodes.GetLexicalSymbol || type === typeToVerify;
};
}
const isGetFreeComponent = makeResolutionTypeVerifier(opcodes.GetFreeAsComponentHead);
const isGetFreeModifier = makeResolutionTypeVerifier(opcodes.GetFreeAsModifierHead);
const isGetFreeHelper = makeResolutionTypeVerifier(opcodes.GetFreeAsHelperHead);
const isGetFreeComponentOrHelper = makeResolutionTypeVerifier(opcodes.GetFreeAsComponentOrHelperHead);
function assertResolverInvariants(meta) {
return meta;
}
/**
* <Foo/>
* <Foo></Foo>
* <Foo @arg={{true}} />
*/
function resolveComponent(resolver, constants, meta, [, expr, then]) {
assert(isGetFreeComponent(expr));
let type = expr[0];
if (type === opcodes.GetLexicalSymbol) {
let {
scopeValues,
owner,
symbols: {
lexical
}
} = meta;
let definition = expect(scopeValues)[expr[1]];
then(constants.component(definition, expect(owner), false, lexical?.at(expr[1])));
} else {
let {
symbols: {
upvars
},
owner
} = assertResolverInvariants(meta);
let name = unwrap(upvars[expr[1]]);
let definition = resolver?.lookupComponent?.(name, owner) ?? null;
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion -- @fixme
then(constants.resolvedComponent(definition, name));
}
}
/**
* (helper)
* (helper arg)
*/
function resolveHelper(resolver, constants, meta, [, expr, then]) {
assert(isGetFreeHelper(expr));
let type = expr[0];
if (type === opcodes.GetLexicalSymbol) {
let {
scopeValues
} = meta;
let definition = expect(scopeValues)[expr[1]];
then(constants.helper(definition));
} else if (type === opcodes.GetStrictKeyword) {
then(lookupBuiltInHelper(expr, resolver, meta, constants));
} else {
let {
symbols: {
upvars
},
owner
} = assertResolverInvariants(meta);
let name = unwrap(upvars[expr[1]]);
let helper = resolver?.lookupHelper?.(name, owner) ?? null;
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion -- @fixme
then(constants.helper(helper, name));
}
}
/**
* <div {{modifier}}/>
* <div {{modifier arg}}/>
* <Foo {{modifier}}/>
*/
function resolveModifier(resolver, constants, meta, [, expr, then]) {
assert(isGetFreeModifier(expr));
let type = expr[0];
if (type === opcodes.GetLexicalSymbol) {
let {
scopeValues,
symbols: {
lexical
}
} = meta;
let definition = expect(scopeValues)[expr[1]];
then(constants.modifier(definition, lexical?.at(expr[1]) ?? undefined));
} else if (type === opcodes.GetStrictKeyword) {
let {
symbols: {
upvars
}
} = assertResolverInvariants(meta);
let name = unwrap(upvars[expr[1]]);
let modifier = resolver?.lookupBuiltInModifier?.(name) ?? null;
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion -- @fixme
then(constants.modifier(modifier, name));
} else {
let {
symbols: {
upvars
},
owner
} = assertResolverInvariants(meta);
let name = unwrap(upvars[expr[1]]);
let modifier = resolver?.lookupModifier?.(name, owner) ?? null;
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion -- @fixme
then(constants.modifier(modifier));
}
}
/**
* {{component-or-helper arg}}
*/
function resolveComponentOrHelper(resolver, constants, meta, [, expr, {
ifComponent,
ifHelper
}]) {
assert(isGetFreeComponentOrHelper(expr));
let type = expr[0];
if (type === opcodes.GetLexicalSymbol) {
let {
scopeValues,
owner,
symbols: {
lexical
}
} = meta;
let definition = expect(scopeValues)[expr[1]];
let component = constants.component(definition, expect(owner), true, lexical?.at(expr[1]));
if (component !== null) {
ifComponent(component);
return;
}
let helper = constants.helper(definition, null, true);
ifHelper(expect(helper));
} else if (type === opcodes.GetStrictKeyword) {
ifHelper(lookupBuiltInHelper(expr, resolver, meta, constants));
} else {
let {
symbols: {
upvars
},
owner
} = assertResolverInvariants(meta);
let name = unwrap(upvars[expr[1]]);
let definition = resolver?.lookupComponent?.(name, owner) ?? null;
if (definition !== null) {
ifComponent(constants.resolvedComponent(definition, name));
} else {
let helper = resolver?.lookupHelper?.(name, owner) ?? null;
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion -- @fixme
ifHelper(constants.helper(helper, name));
}
}
}
/**
* {{maybeHelperOrComponent}}
*/
function resolveOptionalComponentOrHelper(resolver, constants, meta, [, expr, {
ifComponent,
ifHelper,
ifValue
}]) {
assert(isGetFreeComponentOrHelper(expr));
let type = expr[0];
if (type === opcodes.GetLexicalSymbol) {
let {
scopeValues,
owner,
symbols: {
lexical
}
} = meta;
let definition = expect(scopeValues)[expr[1]];
if (typeof definition !== 'function' && (typeof definition !== 'object' || definition === null)) {
// The value is not an object, so it can't be a component or helper.
ifValue(constants.value(definition));
return;
}
let component = constants.component(definition, expect(owner), true, lexical?.at(expr[1]));
if (component !== null) {
ifComponent(component);
return;
}
let helper = constants.helper(definition, null, true);
if (helper !== null) {
ifHelper(helper);
return;
}
ifValue(constants.value(definition));
} else if (type === opcodes.GetStrictKeyword) {
ifHelper(lookupBuiltInHelper(expr, resolver, meta, constants));
} else {
let {
symbols: {
upvars
},
owner
} = assertResolverInvariants(meta);
let name = unwrap(upvars[expr[1]]);
let definition = resolver?.lookupComponent?.(name, owner) ?? null;
if (definition !== null) {
ifComponent(constants.resolvedComponent(definition, name));
return;
}
let helper = resolver?.lookupHelper?.(name, owner) ?? null;
if (helper !== null) {
ifHelper(constants.helper(helper, name));
}
}
}
function lookupBuiltInHelper(expr, resolver, meta, constants, type) {
let {
symbols: {
upvars
}
} = assertResolverInvariants(meta);
let name = unwrap(upvars[expr[1]]);
let helper = resolver?.lookupBuiltInHelper?.(name) ?? null;
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion -- @fixme
return constants.helper(helper, name);
}
const HighLevelResolutionOpcodes = {
Modifier: 1003,
Component: 1004,
Helper: 1005,
ComponentOrHelper: 1007,
OptionalComponentOrHelper: 1008,
Local: 1010,
TemplateLocal: 1011
};
const HighLevelBuilderOpcodes = {
Label: 1000,
StartLabels: 1001,
StopLabels: 1002,
Start: 1000};
const HighLevelOperands = {
Label: 1,
IsStrictMode: 2,
DebugSymbols: 3,
Block: 4,
StdLib: 5,
NonSmallInt: 6,
SymbolTable: 7,
Layout: 8
};
function labelOperand(value) {
return {
type: HighLevelOperands.Label,
value
};
}
function debugSymbolsOperand(locals, upvars, lexical) {
return {
type: HighLevelOperands.DebugSymbols,
value: {
locals,
upvars,
lexical
}
};
}
function isStrictMode() {
return {
type: HighLevelOperands.IsStrictMode,
value: undefined
};
}
function blockOperand(value) {
return {
type: HighLevelOperands.Block,
value
};
}
function stdlibOperand(value) {
return {
type: HighLevelOperands.StdLib,
value
};
}
function nonSmallIntOperand(value) {
return {
type: HighLevelOperands.NonSmallInt,
value
};
}
function symbolTableOperand(value) {
return {
type: HighLevelOperands.SymbolTable,
value
};
}
function layoutOperand(value) {
return {
type: HighLevelOperands.Layout,
value
};
}
class Labels {
labels = dict();
targets = [];
label(name, index) {
this.labels[name] = index;
}
target(at, target) {
this.targets.push({
at,
target
});
}
patch(heap) {
let {
targets,
labels
} = this;
for (const {
at,
target
} of targets) {
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion -- @fixme
let address = labels[target] - at;
assert(heap.getbyaddr(at) === -1);
heap.setbyaddr(at, address);
}
}
}
function encodeOp(encoder, context, meta, op) {
let {
program: {
constants
},
resolver
} = context;
if (isBuilderOpcode(op[0])) {
let [type, ...operands] = op;
encoder.push(constants, type, ...operands);
} else {
switch (op[0]) {
case HighLevelBuilderOpcodes.Label:
return encoder.label(op[1]);
case HighLevelBuilderOpcodes.StartLabels:
return encoder.startLabels();
case HighLevelBuilderOpcodes.StopLabels:
return encoder.stopLabels();
case HighLevelResolutionOpcodes.Component:
return resolveComponent(resolver, constants, meta, op);
case HighLevelResolutionOpcodes.Modifier:
return resolveModifier(resolver, constants, meta, op);
case HighLevelResolutionOpcodes.Helper:
return resolveHelper(resolver, constants, meta, op);
case HighLevelResolutionOpcodes.ComponentOrHelper:
return resolveComponentOrHelper(resolver, constants, meta, op);
case HighLevelResolutionOpcodes.OptionalComponentOrHelper:
return resolveOptionalComponentOrHelper(resolver, constants, meta, op);
case HighLevelResolutionOpcodes.Local:
{
let [, freeVar, andThen] = op;
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion -- @fixme
let name = expect(meta.symbols.upvars)[freeVar];
andThen(name, meta.moduleName);
break;
}
case HighLevelResolutionOpcodes.TemplateLocal:
{
let [, valueIndex, then] = op;
let value = expect(meta.scopeValues)[valueIndex];
then(constants.value(value));
break;
}
default:
throw new Error(`Unexpected high level opcode ${op[0]}`);
}
}
}
class EncoderImpl {
labelsStack = new StackImpl();
encoder = new InstructionEncoderImpl([]);
errors = [];
handle;
constructor(heap, meta, stdlib) {
this.heap = heap;
this.meta = meta;
this.stdlib = stdlib;
this.handle = heap.malloc();
}
error(error) {
this.encoder.encode(VM_PRIMITIVE_OP, 0);
this.errors.push(error);
}
commit(size) {
let handle = this.handle;
this.heap.pushMachine(VM_RETURN_OP);
this.heap.finishMalloc(handle, size);
if (isPresentArray(this.errors)) {
return {
errors: this.errors,
handle
};
} else {
return handle;
}
}
push(constants, type, ...args) {
let {
heap
} = this;
let machine = isMachineOp(type) ? MACHINE_MASK : 0;
let first = type | machine | args.length << ARG_SHIFT;
heap.pushRaw(first);
for (let i = 0; i < args.length; i++) {
let op = args[i];
heap.pushRaw(this.operand(constants, op));
}
}
operand(constants, operand) {
if (typeof operand === 'number') {
return operand;
}
if (typeof operand === 'object' && operand !== null) {
if (Array.isArray(operand)) {
return encodeHandle(constants.array(operand));
} else {
switch (operand.type) {
case HighLevelOperands.Label:
this.currentLabels.target(this.heap.offset, operand.value);
return -1;
case HighLevelOperands.IsStrictMode:
return encodeHandle(constants.value(this.meta.isStrictMode));
case HighLevelOperands.DebugSymbols:
return encodeHandle(constants.value(operand.value));
case HighLevelOperands.Block:
return encodeHandle(constants.value(compilableBlock(operand.value, this.meta)));
case HighLevelOperands.StdLib:
return expect(this.stdlib)[operand.value];
case HighLevelOperands.NonSmallInt:
case HighLevelOperands.SymbolTable:
case HighLevelOperands.Layout:
return constants.value(operand.value);
}
}
}
return encodeHandle(constants.value(operand));
}
get currentLabels() {
return expect(this.labelsStack.current);
}
label(name) {
this.currentLabels.label(name, this.heap.offset + 1);
}
startLabels() {
this.labelsStack.push(new Labels());
}
stopLabels() {
let label = expect(this.labelsStack.pop());
label.patch(this.heap);
}
}
function isBuilderOpcode(op) {
return op < HighLevelBuilderOpcodes.Start;
}
function templateCompilationContext(evaluation, meta) {
let encoder = new EncoderImpl(evaluation.program.heap, meta, evaluation.stdlib);
return {
evaluation,
encoder,
meta
};
}
class Compilers {
names = {};
// eslint-disable-next-line @typescript-eslint/no-explicit-any
funcs = [];
add(name, func) {
this.names[name] = this.funcs.push(func) - 1;
}
compile(op, sexp) {
let name = sexp[0];
let index = unwrap(this.names[name]);
let func = this.funcs[index];
assert(func, `expected an implementation for ${sexp[0]}`);
func(op, sexp);
}
}
const EXPRESSIONS = new Compilers();
EXPRESSIONS.add(opcodes.Concat, (op, [, parts]) => {
for (let part of parts) {
expr(op, part);
}
op(VM_CONCAT_OP, parts.length);
});
EXPRESSIONS.add(opcodes.Call, (op, [, expression, positional, named]) => {
if (isGetFreeHelper(expression)) {
op(HighLevelResolutionOpcodes.Helper, expression, handle => {
Call(op, handle, positional, named);
});
} else {
expr(op, expression);
CallDynamic(op, positional, named);
}
});
EXPRESSIONS.add(opcodes.Curry, (op, [, expr, type, positional, named]) => {
Curry(op, type, expr, positional, named);
});
EXPRESSIONS.add(opcodes.GetSymbol, (op, [, sym, path]) => {
op(VM_GET_VARIABLE_OP, sym);
withPath(op, path);
});
EXPRESSIONS.add(opcodes.GetLexicalSymbol, (op, [, sym, path]) => {
op(HighLevelResolutionOpcodes.TemplateLocal, sym, handle => {
op(VM_CONSTANT_REFERENCE_OP, handle);
withPath(op, path);
});
});
EXPRESSIONS.add(opcodes.GetStrictKeyword, (op, expr) => {
op(HighLevelResolutionOpcodes.Local, expr[1], _name => {
op(HighLevelResolutionOpcodes.Helper, expr, handle => {
Call(op, handle, null, null);
});
});
});
EXPRESSIONS.add(opcodes.GetFreeAsHelperHead, (op, expr) => {
op(HighLevelResolutionOpcodes.Local, expr[1], _name => {
op(HighLevelResolutionOpcodes.Helper, expr, handle => {
Call(op, handle, null, null);
});
});
});
function withPath(op, path) {
if (path === undefined || path.length === 0) return;
for (let i = 0; i < path.length; i++) {
op(VM_GET_PROPERTY_OP, path[i]);
}
}
EXPRESSIONS.add(opcodes.Undefined, op => PushPrimitiveReference(op, undefined));
EXPRESSIONS.add(opcodes.HasBlock, (op, [, block]) => {
expr(op, block);
op(VM_HAS_BLOCK_OP);
});
EXPRESSIONS.add(opcodes.HasBlockParams, (op, [, block]) => {
expr(op, block);
op(VM_SPREAD_BLOCK_OP);
op(VM_COMPILE_BLOCK_OP);
op(VM_HAS_BLOCK_PARAMS_OP);
});
EXPRESSIONS.add(opcodes.IfInline, (op, [, condition, truthy, falsy]) => {
// Push in reverse order
expr(op, falsy);
expr(op, truthy);
expr(op, condition);
op(VM_IF_INLINE_OP);
});
EXPRESSIONS.add(opcodes.Not, (op, [, value]) => {
expr(op, value);
op(VM_NOT_OP);
});
EXPRESSIONS.add(opcodes.GetDynamicVar, (op, [, expression]) => {
expr(op, expression);
op(VM_GET_DYNAMIC_VAR_OP);
});
EXPRESSIONS.add(opcodes.Log, (op, [, positional]) => {
op(VM_PUSH_FRAME_OP);
SimpleArgs(op, positional, null, false);
op(VM_LOG_OP);
op(VM_POP_FRAME_OP);
op(VM_FETCH_OP, $v0);
});
function expr(op, expression) {
if (Array.isArray(expression)) {
EXPRESSIONS.compile(op, expression);
} else {
PushPrimitive(op, expression);
op(VM_PRIMITIVE_REFERENCE_OP);
}
}
/**
* Push a reference onto the stack corresponding to a statically known primitive
* @param value A JavaScript primitive (undefined, null, boolean, number or string)
*/
function PushPrimitiveReference(op, value) {
PushPrimitive(op, value);
op(VM_PRIMITIVE_REFERENCE_OP);
}
/**
* Push an encoded representation of a JavaScript primitive on the stack
*
* @param value A JavaScript primitive (undefined, null, boolean, number or string)
*/
function PushPrimitive(op, primitive) {
let p = primitive;
if (typeof p === 'number') {
p = isSmallInt(p) ? encodeImmediate(p) : nonSmallIntOperand(p);
}
op(VM_PRIMITIVE_OP, p);
}
/**
* Invoke a foreign function (a "helper") based on a statically known handle
*
* @param op The op creation function
* @param handle A handle
* @param positional An optional list of expressions to compile
* @param named An optional list of named arguments (name + expression) to compile
*/
function Call(op, handle, positional, named) {
op(VM_PUSH_FRAME_OP);
SimpleArgs(op, positional, named, false);
op(VM_HELPER_OP, handle);
op(VM_POP_FRAME_OP);
op(VM_FETCH_OP, $v0);
}
/**
* Invoke a foreign function (a "helper") based on a dynamically loaded definition
*
* @param op The op creation function
* @param positional An optional list of expressions to compile
* @param named An optional list of named arguments (name + expression) to compile
*/
function CallDynamic(op, positional, named, append) {
op(VM_PUSH_FRAME_OP);
SimpleArgs(op, positional, named, false);
op(VM_DUP_OP, $fp, 1);
op(VM_DYNAMIC_HELPER_OP);
if (append) {
op(VM_FETCH_OP, $v0);
append();
op(VM_POP_FRAME_OP);
op(VM_POP_OP, 1);
} else {
op(VM_POP_FRAME_OP);
op(VM_POP_OP, 1);
op(VM_FETCH_OP, $v0);
}
}
/**
* Evaluate statements in the context of new dynamic scope entries. Move entries from the
* stack into named entries in the dynamic scope, then evaluate the statements, then pop
* the dynamic scope
*
* @param names a list of dynamic scope names
* @param block a function that returns a list of statements to evaluate
*/
function DynamicScope(op, names, block) {
op(VM_PUSH_DYNAMIC_SCOPE_OP);
op(VM_BIND_DYNAMIC_SCOPE_OP, names);
block();
op(VM_POP_DYNAMIC_SCOPE_OP);
}
function Curry(op, type, definition, positional, named) {
op(VM_PUSH_FRAME_OP);
SimpleArgs(op, positional, named, false);
op(VM_CAPTURE_ARGS_OP);
expr(op, definition);
op(VM_CURRY_OP, type, isStrictMode());
op(VM_POP_FRAME_OP);
op(VM_FETCH_OP, $v0);
}
/**
* Yield to a block located at a particular symbol location.
*
* @param to the symbol containing the block to yield to
* @param params optional block parameters to yield to the block
*/
function YieldBlock(op, to, positional) {
SimpleArgs(op, positional, null, true);
op(VM_GET_BLOCK_OP, to);
op(VM_SPREAD_BLOCK_OP);
op(VM_COMPILE_BLOCK_OP);
op(VM_INVOKE_YIELD_OP);
op(VM_POP_SCOPE_OP);
op(VM_POP_FRAME_OP);
}
/**
* Push an (optional) yieldable block onto the stack. The yieldable block must be known
* statically at compile time.
*
* @param block An optional Compilable block
*/
function PushYieldableBlock(op, block) {
PushSymbolTable(op, block && block[1]);
op(VM_PUSH_BLOCK_SCOPE_OP);
PushCompilable(op, block);
}
/**
* Invoke a block that is known statically at compile time.
*
* @param block a Compilable block
*/
function InvokeStaticBlock(op, block) {
op(VM_PUSH_FRAME_OP);
PushCompilable(op, block);
op(VM_COMPILE_BLOCK_OP);
op(VM_INVOKE_VIRTUAL_OP);
op(VM_POP_FRAME_OP);
}
/**
* Invoke a static block, preserving some number of stack entries for use in
* updating.
*
* @param block A compilable block
* @param callerCount A number of stack entries to preserve
*/
function InvokeStaticBlockWithStack(op, block, callerCount) {
let parameters = block[1];
let calleeCount = parameters.length;
let count = Math.min(callerCount, calleeCount);
if (count === 0) {
InvokeStaticBlock(op, block);
return;
}
op(VM_PUSH_FRAME_OP);
if (count) {
op(VM_CHILD_SCOPE_OP);
for (let i = 0; i < count; i++) {
op(VM_DUP_OP, $fp, callerCount - i);
op(VM_SET_VARIABLE_OP, parameters[i]);
}
}
PushCompilable(op, block);
op(VM_COMPILE_BLOCK_OP);
op(VM_INVOKE_VIRTUAL_OP);
if (count) {
op(VM_POP_SCOPE_OP);
}
op(VM_POP_FRAME_OP);
}
function PushSymbolTable(op, parameters) {
if (parameters !== null) {
op(VM_PUSH_SYMBOL_TABLE_OP, symbolTableOperand({
parameters
}));
} else {
PushPrimitive(op, null);
}
}
function PushCompilable(op, _block) {
if (_block === null) {
PushPrimitive(op, null);
} else {
op(VM_CONSTANT_OP, blockOperand(_block));
}
}
/**
* Compile arguments, pushing an Arguments object onto the stack.
*
* @param args.params
* @param args.hash
* @param args.blocks
* @param args.atNames
*/
function CompileArgs(op, positional, named, blocks, atNames) {
let blockNames = blocks.names;
for (const name of blockNames) {
PushYieldableBlock(op, blocks.get(name));
}
let count = CompilePositional(op, positional);
let flags = count << 4;
if (atNames) flags |= 0b1000;
if (blocks.hasAny) {
flags |= 0b111;
}
let names = EMPTY_ARRAY;
if (named) {
names = named[0];
let val = named[1];
for (let i = 0; i < val.length; i++) {
expr(op, val[i]);
}
}
op(VM_PUSH_ARGS_OP, names, blockNames, flags);
}
function SimpleArgs(op, positional, named, atNames) {
if (positional === null && named === null) {
op(VM_PUSH_EMPTY_ARGS_OP);
return;
}
let count = CompilePositional(op, positional);
let flags = count << 4;
if (atNames) flags |= 0b1000;
let names = EMPTY_STRING_ARRAY;
if (named) {
names = named[0];
let val = named[1];
for (let i = 0; i < val.length; i++) {
expr(op, val[i]);
}
}
op(VM_PUSH_ARGS_OP, names, EMPTY_STRING_ARRAY, flags);
}
/**
* Compile an optional list of positional arguments, which pushes each argument
* onto the stack and returns the number of parameters compiled
*
* @param positional an optional list of positional arguments
*/
function CompilePositional(op, positional) {
if (positional === null) return 0;
for (let i = 0; i < positional.length; i++) {
expr(op, positional[i]);
}
return positional.length;
}
function meta(layout) {
let [, locals, upvars, lexicalSymbols] = layout.block;
let scopeRecord = layout.scope?.() ?? null;
return {
symbols: {
locals,
upvars,
lexical: scopeRecord ? Object.keys(scopeRecord) : lexicalSymbols
},
scopeValues: scopeRecord ? Object.values(scopeRecord) : null,
isStrictMode: layout.isStrictMode,
moduleName: layout.moduleName,
owner: layout.owner,
size: locals.length
};
}
class NamedBlocksImpl {
names;
constructor(blocks) {
this.blocks = blocks;
this.names = blocks ? Object.keys(blocks) : [];
}
get(name) {
if (!this.blocks) return null;
return this.blocks[name] || null;
}
has(name) {
let {
blocks
} = this;
return blocks !== null && name in blocks;
}
with(name, block) {
let {
blocks
} = this;
if (blocks) {
return new NamedBlocksImpl(assign({}, blocks, {
[name]: block
}));
} else {
return new NamedBlocksImpl({
[name]: block
});
}
}
get hasAny() {
return this.blocks !== null;
}
}
const EMPTY_BLOCKS = new NamedBlocksImpl(null);
function namedBlocks(blocks) {
if (blocks === null) {
return EMPTY_BLOCKS;
}
let out = dict();
let [keys, values] = blocks;
for (const [i, key] of enumerate(keys)) {
out[key] = unwrap(values[i]);
}
return new NamedBlocksImpl(out);
}
function SwitchCases(op, bootstrap, matcher) {
// Setup the switch DSL
let clauses = [];
let count = 0;
function when(match, callback) {
clauses.push({
match,
callback,
label: `CLAUSE${count++}`
});
}
// Call the callback
matcher(when);
// Emit the opcodes for the switch
op(VM_ENTER_OP, 1);
bootstrap();
op(HighLevelBuilderOpcodes.StartLabels);
// First, emit the jump opcodes. We don't need a jump for the last
// opcode, since it bleeds directly into its clause.
for (let clause of clauses.slice(0, -1)) {
op(VM_JUMP_EQ_OP, labelOperand(clause.label), clause.match);
}
// Enumerate the clauses in reverse order. Earlier matches will
// require fewer checks.
for (let i = clauses.length - 1; i >= 0; i--) {
let clause = unwrap(clauses[i]);
op(HighLevelBuilderOpcodes.Label, clause.label);
op(VM_POP_OP, 1);
clause.callback();
// The first match is special: it is placed directly before the END
// label, so no additional jump is needed at the end of it.
if (i !== 0) {
op(VM_JUMP_OP, labelOperand('END'));
}
}
op(HighLevelBuilderOpcodes.Label, 'END');
op(HighLevelBuilderOpcodes.StopLabels);
op(VM_EXIT_OP);
}
/**
* A convenience for pushing some arguments on the stack and
* running some code if the code needs to be re-executed during
* updating execution if some of the arguments have changed.
*
* # Initial Execution
*
* The `args` function should push zero or more arguments onto
* the stack and return the number of arguments pushed.
*
* The `body` function provides the instructions to execute both
* during initial execution and during updating execution.
*
* Internally, this function starts by pushing a new frame, so
* that the body can return and sets the return point ($ra) to
* the ENDINITIAL label.
*
* It then executes the `args` function, which adds instructions
* responsible for pushing the arguments for the block to the
* stack. These arguments will be restored to the stack before
* updating execution.
*
* Next, it adds the Enter opcode, which marks the current position
* in the DOM, and remembers the current $pc (the next instruction)
* as the first instruction to execute during updating execution.
*
* Next, it runs `body`, which adds the opcodes that should
* execute both during initial execution and during updating execution.
* If the `body` wishes to finish early, it should Jump to the
* `FINALLY` label.
*
* Next, it adds the FINALLY label, followed by:
*
* - the Exit opcode, which finalizes the marked DOM started by the
* Enter opcode.
* - the Return opcode, which returns to the current return point
* ($ra).
*
* Finally, it adds the ENDINITIAL label followed by the PopFrame
* instruction, which restores $fp, $sp and $ra.
*
* # Updating Execution
*
* Updating execution for this `replayable` occurs if the `body` added an
* assertion, via one of the `JumpIf`, `JumpUnless` or `AssertSame` opcodes.
*
* If, during updating executon, the assertion fails, the initial VM is
* restored, and the stored arguments are pushed onto the stack. The DOM
* between the starting and ending markers is cleared, and the VM's cursor
* is set to the area just cleared.
*
* The return point ($ra) is set to -1, the exit instruction.
*
* Finally, the $pc is set to to the instruction saved off by the
* Enter opcode during initial execution, and execution proceeds as
* usual.
*
* The only difference is that when a `Return` instruction is
* encountered, the program jumps to -1 rather than the END label,
* and the PopFrame opcode is not needed.
*/
function Replayable(op, args, body) {
// Start a new label frame, to give END and RETURN
// a unique meaning.
op(HighLevelBuilderOpcodes.StartLabels);
op(VM_PUSH_FRAME_OP);
// If the body invokes a block, its return will return to
// END. Otherwise, the return in RETURN will return to END.
op(VM_RETURN_TO_OP, labelOperand('ENDINITIAL'));
// Push the arguments onto the stack. The args() function
// tells us how many stack elements to retain for re-execution
// when updating.
let count = args();
// Start a new updating closure, remembering `count` elements
// from the stack. Everything after this point, and before END,
// will execute both initially and to update the block.
//
// The enter and exit opcodes also track the area of the DOM
// associated with this block. If an assertion inside the block
// fails (for example, the test value changes from true to false
// in an #if), the DOM is cleared and the program is re-executed,
// restoring `count` elements to the stack and executing the
// instructions between the enter and exit.
op(VM_ENTER_OP, count);
// Evaluate the body of the block. The body of the block may
// return, which will jump execution to END during initial
// execution, and exit the updating routine.
body();
// All execution paths in the body should run the FINALLY once
// they are done. It is executed both during initial execution
// and during updating execution.
op(HighLevelBuilderOpcodes.Label, 'FINALLY');
// Finalize the DOM.
op(VM_EXIT_OP);
// In initial execution, this is a noop: it returns to the
// immediately following opcode. In updating execution, this
// exits the updating routine.
op(VM_RETURN_OP);
// Cleanup code for the block. Runs on initial execution
// but not on updating.
op(HighLevelBuilderOpcodes.Label, 'ENDINITIAL');
op(VM_POP_FRAME_OP);
op(HighLevelBuilderOpcodes.StopLabels);
}
/**
* A specialized version of the `replayable` convenience that allows the
* caller to provide different code based upon whether the item at
* the top of the stack is true or false.
*
* As in `replayable`, the `ifTrue` and `ifFalse` code can invoke `return`.
*
* During the initial execution, a `return` will continue execution
* in the cleanup code, which finalizes the current DOM block and pops
* the current frame.
*
* During the updating execution, a `return` will exit the updating
* routine, as it can reuse the DOM block and is always only a single
* frame deep.
*/
function ReplayableIf(op, args, ifTrue, ifFalse) {
return Replayable(op, args, () => {
// If the conditional is false, jump to the ELSE label.
op(VM_JUMP_UNLESS_OP, labelOperand('ELSE'));
// Otherwise, execute the code associated with the true branch.
ifTrue();
// We're done, so return. In the initial execution, this runs
// the cleanup code. In the updating VM, it exits the updating
// routine.
op(VM_JUMP_OP, labelOperand('FINALLY'));
op(HighLevelBuilderOpcodes.Label, 'ELSE');
// If the conditional is false, and code associatied ith the
// false branch was provided, execute it. If there was no code
// associated with the false branch, jumping to the else statement
// has no other behavior.
if (ifFalse !== undefined) {
ifFalse();
}
});
}
const ATTRS_BLOCK = '&attrs';
// {{component}}
// <Component>
// chokepoint
function InvokeComponent(op, component, _elementBlock, positional, named, _blocks) {
let {
compilable,
capabilities,
handle
} = component;
let elementBlock = _elementBlock ? [_elementBlock, []] : null;
let blocks = namedBlocks(_blocks);
if (compilable) {
op(VM_PUSH_COMPONENT_DEFINITION_OP, handle);
InvokeStaticComponent(op, {
capabilities: capabilities,
layout: compilable,
elementBlock,
positional,
named,
blocks
});
} else {
op(VM_PUSH_COMPONENT_DEFINITION_OP, handle);
InvokeNonStaticComponent(op, {
capabilities: capabilities,
elementBlock,
positional,
named,
atNames: true,
blocks
});
}
}
function InvokeDynamicComponent(op, definition, _elementBlock, positional, named, _blocks, atNames, curried) {
let elementBlock = _elementBlock ? [_elementBlock, []] : null;
let blocks = namedBlocks(_blocks);
Replayable(op, () => {
expr(op, definition);
op(VM_DUP_OP, $sp, 0);
return 2;
}, () => {
op(VM_JUMP_UNLESS_OP, labelOperand('ELSE'));
if (curried) {
op(VM_RESOLVE_CURRIED_COMPONENT_OP);
} else {
op(VM_RESOLVE_DYNAMIC_COMPONENT_OP, isStrictMode());
}
op(VM_PUSH_DYNAMIC_COMPONENT_INSTANCE_OP);
InvokeNonStaticComponent(op, {
capabilities: true,
elementBlock,
positional,
named,
atNames,
blocks
});
op(HighLevelBuilderOpcodes.Label, 'ELSE');
});
}
function InvokeStaticComponent(op, {
capabilities,
layout,
elementBlock,
positional,
named,
blocks
}) {
let {
symbolTable
} = layout;
let bailOut = hasCapability(capabilities, InternalComponentCapabilities.prepareArgs);
if (bailOut) {
InvokeNonStaticComponent(op, {
capabilities,
elementBlock,
positional,
named,
atNames: true,
blocks,
layout
});
return;
}
op(VM_FETCH_OP, $s0);
op(VM_DUP_OP, $sp, 1);
op(VM_LOAD_OP, $s0);
op(VM_PUSH_FRAME_OP);
// Setup arguments
let {
symbols
} = symbolTable;
// As we push values onto the stack, we store the symbols associated with them
// so that we can set them on the scope later on with SetVariable and SetBlock
let blockSymbols = [];
let argSymbols = [];
let argNames = [];
// First we push the blocks onto the stack
let blockNames = blocks.names;
// Starting with the attrs block, if it exists and is referenced in the component
if (elementBlock !== null) {
let symbol = symbols.indexOf(ATTRS_BLOCK);
if (symbol !== -1) {
PushYieldableBlock(op, elementBlock);
blockSymbols.push(symbol);
}
}
// Followed by the other blocks, if they exist and are referenced in the component.
// Also store the index of the associated symbol.
for (const name of blockNames) {
let symbol = symbols.indexOf(`&${name}`);
if (symbol !== -1) {
PushYieldableBlock(op, blocks.get(name));
blockSymbols.push(symbol);
}
}
// Next up we have arguments. If the component has the `createArgs` capability,
// then it wants access to the arguments in JavaScript. We can't know whether
// or not an argument is used, so we have to give access to all of them.
if (hasCapability(capabilities, InternalComponentCapabilities.createArgs)) {
// First we push positional arguments
let count = CompilePositional(op, positional);
// setup the flags with the count of positionals, and to indicate that atNames
// are used
let flags = count << 4;
flags |= 0b1000;
let names = EMPTY_STRING_ARRAY;
// Next, if named args exist, push them all. If they have an associated symbol
// in the invoked component (e.g. they are used within its template), we push
// that symbol. If not, we still push the expression as it may be used, and
// we store the symbol as -1 (this is used later).
if (named !== null) {
names = named[0];
let val = named[1];
for (let i = 0; i < val.length; i++) {
let symbol = symbols.indexOf(unwrap(names[i]));
expr(op, val[i]);
argSymbols.push(symbol);
}
}
// Finally, push the VM arguments themselves. These args won't need access
// to blocks (they aren't accessible from userland anyways), so we push an
// empty array instead of the actual block names.
op(VM_PUSH_ARGS_OP, names, EMPTY_STRING_ARRAY, flags);
// And push an extra pop operation to remove the args before we begin setting
// variables on the local context
argSymbols.push(-1);
} else if (named !== null) {
// If the component does not have the `createArgs` capability, then the only
// expressions we need to push onto the stack are those that are actually
// referenced in the template of the invoked component (e.g. have symbols).
let names = named[0];
let val = named[1];
for (let i = 0; i < val.length; i++) {
let name = unwrap(names[i]);
let symbol = symbols.indexOf(name);
if (symbol !== -1) {
expr(op, val[i]);
argSymbols.push(symbol);
argNames.push(name);
}
}
}
op(VM_BEGIN_COMPONENT_TRANSACTION_OP, $s0);
if (hasCapability(capabilities, InternalComponentCapabilities.dynamicScope)) {
op(VM_PUSH_DYNAMIC_SCOPE_OP);
}
if (hasCapability(capabilities, InternalComponentCapabilities.createInstance)) {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
op(VM_CREATE_COMPONENT_OP, blocks.has('default') | 0);
}
op(VM_REGISTER_COMPONENT_DESTRUCTOR_OP, $s0);
if (hasCapability(capabilities, InternalComponentCapabilities.createArgs)) {
op(VM_GET_COMPONENT_SELF_OP, $s0);
} else {
op(VM_GET_COMPONENT_SELF_OP, $s0, argNames);
}
// Setup the new root scope for the component
op(VM_ROOT_SCOPE_OP, symbols.length + 1, Object.keys(blocks).length > 0 ? 1 : 0);
// Pop the self reference off the stack and set it to the symbol for `this`
// in the new scope. This is why all subsequent symbols are increased by one.
op(VM_SET_VARIABLE_OP, 0);
// Going in reverse, now we pop the args/blocks off the stack, starting with
// arguments, and assign them to their symbols in the new scope.
for (const symbol of reverse(argSymbols)) {
// for (let i = argSymbols.length - 1; i >= 0; i--) {
// let symbol = argSymbols[i];
if (symbol === -1) {
// The expression was not bound to a local symbol, it was only pushed to be
// used with VM args in the javascript side
op(VM_POP_OP, 1);
} else {
op(VM_SET_VARIABLE_OP, symbol + 1);
}
}
// if any positional params exist, pop them off the stack as well
if (positional !== null) {
op(VM_POP_OP, positional.length);
}
// Finish up by popping off and assigning blocks
for (const symbol of reverse(blockSymbols)) {
op(VM_SET_BLOCK_OP, symbol + 1);
}
op(VM_CONSTANT_OP, layoutOperand(layout));
op(VM_COMPILE_BLOCK_OP);
op(VM_INVOKE_VIRTUAL_OP);
op(VM_DID_RENDER_LAYOUT_OP, $s0);
op(VM_POP_FRAME_OP);
op(VM_POP_SCOPE_OP);
if (hasCapability(capabilities, InternalComponentCapabilities.dynamicScope)) {
op(VM_POP_DYNAMIC_SCOPE_OP);
}
op(VM_COMMIT_COMPONENT_TRANSACTION_OP);
op(VM_LOAD_OP, $s0);
}
function InvokeNonStaticComponent(op, {
capabilities,
elementBlock,
positional,
named,
atNames,
blocks: namedBlocks,
layout
}) {
let bindableBlocks = Boolean(namedBlocks);
let bindableAtNames = capabilities === true || hasCapability(capabilities, InternalComponentCapabilities.prepareArgs) || named?.[0].length !== 0;
let blocks = namedBlocks.with('attrs', elementBlock);
op(VM_FETCH_OP, $s0);
op(VM_DUP_OP, $sp, 1);
op(VM_LOAD_OP, $s0);
op(VM_PUSH_FRAME_OP);
CompileArgs(op, positional, named, blocks, atNames);
op(VM_PREPARE_ARGS_OP, $s0);
invokePreparedComponent(op, blocks.has('default'), bindableBlocks, bindableAtNames, () => {
if (layout) {
op(VM_PUSH_SYMBOL_TABLE_OP, symbolTableOperand(layout.symbolTable));
op(VM_CONSTANT_OP, layoutOperand(layout));
op(VM_COMPILE_BLOCK_OP);
} else {
op(VM_GET_COMPONENT_LAYOUT_OP, $s0);
}
op(VM_POPULATE_LAYOUT_OP, $s0);
});
op(VM_LOAD_OP, $s0);
}
function WrappedComponent(op, layout, attrsBlockNumber) {
op(HighLevelBuilderOpcodes.StartLabels);
WithSavedRegister(op, $s1, () => {
op(VM_GET_COMPONENT_TAG_NAME_OP, $s0);
op(VM_PRIMITIVE_REFERENCE_OP);
op(VM_DUP_OP, $sp, 0);
});
op(VM_JUMP_UNLESS_OP, labelOperand('BODY'));
op(VM_FETCH_OP, $s1);
op(VM_PUT_COMPONENT_OPERATIONS_OP);
op(VM_OPEN_DYNAMIC_ELEMENT_OP);
op(VM_DID_CREATE_ELEMENT_OP, $s0);
YieldBlock(op, attrsBlockNumber, null);
op(VM_FLUSH_ELEMENT_OP);
op(HighLevelBuilderOpcodes.Label, 'BODY');
InvokeStaticBlock(op, [layout.block[0], []]);
op(VM_FETCH_OP, $s1);
op(VM_JUMP_UNLESS_OP, labelOperand('END'));
op(VM_CLOSE_ELEMENT_OP);
op(HighLevelBuilderOpcodes.Label, 'END');
op(VM_LOAD_OP, $s1);
op(HighLevelBuilderOpcodes.StopLabels);
}
function invokePreparedComponent(op, hasBlock, bindableBlocks, bindableAtNames, populateLayout = null) {
op(VM_BEGIN_COMPONENT_TRANSACTION_OP, $s0);
op(VM_PUSH_DYNAMIC_SCOPE_OP);
// eslint-disable-next-line @typescript-eslint/no-explicit-any
op(VM_CREATE_COMPONENT_OP, hasBlock | 0);
// this has to run after createComponent to allow
// for late-bound layouts, but a caller is free
// to populate the layout earlier if it wants to
// and do nothing here.
if (populateLayout) {
populateLayout();
}
op(VM_REGISTER_COMPONENT_DESTRUCTOR_OP, $s0);
op(VM_GET_COMPONENT_SELF_OP, $s0);
op(VM_VIRTUAL_ROOT_SCOPE_OP, $s0);
op(VM_SET_VARIABLE_OP, 0);
if (bindableAtNames) op(VM_SET_NAMED_VARIABLES_OP, $s0);
if (bindableBlocks) op(VM_SET_BLOCKS_OP, $s0);
op(VM_POP_OP, 1);
op(VM_INVOKE_COMPONENT_LAYOUT_OP, $s0);
op(VM_DID_RENDER_LAYOUT_OP, $s0);
op(VM_POP_FRAME_OP);
op(VM_POP_SCOPE_OP);
op(VM_POP_DYNAMIC_SCOPE_OP);
op(VM_COMMIT_COMPONENT_TRANSACTION_OP);
}
function InvokeBareComponent(op) {
op(VM_FETCH_OP, $s0);
op(VM_DUP_OP, $sp, 1);
op(VM_LOAD_OP, $s0);
op(VM_PUSH_FRAME_OP);
op(VM_PUSH_EMPTY_ARGS_OP);
op(VM_PREPARE_ARGS_OP, $s0);
invokePreparedComponent(op, false, false, true, () => {
op(VM_GET_COMPONENT_LAYOUT_OP, $s0);
op(VM_POPULATE_LAYOUT_OP, $s0);
});
op(VM_LOAD_OP, $s0);
}
function WithSavedRegister(op, register, block) {
op(VM_FETCH_OP, register);
block();
op(VM_LOAD_OP, register);
}
const STATEMENTS = new Compilers();
const INFLATE_ATTR_TABLE = ['class', 'id', 'value', 'name', 'type', 'style', 'href'];
const INFLATE_TAG_TABLE = ['div', 'span', 'p', 'a'];
function inflateTagName(tagName) {
return typeof tagName === 'string' ? tagName : INFLATE_TAG_TABLE[tagName];
}
function inflateAttrName(attrName) {
return typeof attrName === 'string' ? attrName : INFLATE_ATTR_TABLE[attrName];
}
STATEMENTS.add(opcodes.Comment, (op, sexp) => op(VM_COMMENT_OP, sexp[1]));
STATEMENTS.add(opcodes.CloseElement, op => op(VM_CLOSE_ELEMENT_OP));
STATEMENTS.add(opcodes.FlushElement, op => op(VM_FLUSH_ELEMENT_OP));
STATEMENTS.add(opcodes.Modifier, (op, [, expression, positional, named]) => {
if (isGetFreeModifier(expression)) {
op(HighLevelResolutionOpcodes.Modifier, expression, handle => {
op(VM_PUSH_FRAME_OP);
SimpleArgs(op, positional, named, false);
op(VM_MODIFIER_OP, handle);
op(VM_POP_FRAME_OP);
});
} else {
expr(op, expression);
op(VM_PUSH_FRAME_OP);
SimpleArgs(op, positional, named, false);
op(VM_DUP_OP, $fp, 1);
op(VM_DYNAMIC_MODIFIER_OP);
op(VM_POP_FRAME_OP);
}
});
STATEMENTS.add(opcodes.StaticAttr, (op, [, name, value, namespace]) => {
op(VM_STATIC_ATTR_OP, inflateAttrName(name), value, namespace ?? null);
});
STATEMENTS.add(opcodes.StaticComponentAttr, (op, [, name, value, namespace]) => {
op(VM_STATIC_COMPONENT_ATTR_OP, inflateAttrName(name), value, namespace ?? null);
});
STATEMENTS.add(opcodes.DynamicAttr, (op, [, name, value, namespace]) => {
expr(op, value);
op(VM_DYNAMIC_ATTR_OP, inflateAttrName(name), false, namespace ?? null);
});
STATEMENTS.add(opcodes.TrustingDynamicAttr, (op, [, name, value, namespace]) => {
expr(op, value);
op(VM_DYNAMIC_ATTR_OP, inflateAttrName(name), true, namespace ?? null);
});
STATEMENTS.add(opcodes.ComponentAttr, (op, [, name, value, namespace]) => {
expr(op, value);
op(VM_COMPONENT_ATTR_OP, inflateAttrName(name), false, namespace ?? null);
});
STATEMENTS.add(opcodes.TrustingComponentAttr, (op, [, name, value, namespace]) => {
expr(op, value);
op(VM_COMPONENT_ATTR_OP, inflateAttrName(name), true, namespace ?? null);
});
STATEMENTS.add(opcodes.OpenElement, (op, [, tag]) => {
op(VM_OPEN_ELEMENT_OP, inflateTagName(tag));
});
STATEMENTS.add(opcodes.OpenElementWithSplat, (op, [, tag]) => {
op(VM_PUT_COMPONENT_OPERATIONS_OP);
op(VM_OPEN_ELEMENT_OP, inflateTagName(tag));
});
STATEMENTS.add(opcodes.Component, (op, [, expr, elementBlock, named, blocks]) => {
if (isGetFreeComponent(expr)) {
op(HighLevelResolutionOpcodes.Component, expr, component => {
InvokeComponent(op, component, elementBlock, null, named, blocks);
});
} else {
// otherwise, the component name was an expression, so resolve the expression
// and invoke it as a dynamic component
InvokeDynamicComponent(op, expr, elementBlock, null, named, blocks, true, true);
}
});
STATEMENTS.add(opcodes.Yield, (op, [, to, params]) => YieldBlock(op, to, params));
STATEMENTS.add(opcodes.AttrSplat, (op, [, to]) => YieldBlock(op, to, null));
STATEMENTS.add(opcodes.Debugger, (op, [, locals, upvars, lexical]) => {
op(VM_DEBUGGER_OP, debugSymbolsOperand(locals, upvars, lexical));
});
STATEMENTS.add(opcodes.Append, (op, [, value]) => {
// Special case for static values
if (!Array.isArray(value)) {
op(VM_TEXT_OP, value === null || value === undefined ? '' : String(value));
} else if (isGetFreeComponentOrHelper(value)) {
op(HighLevelResolutionOpcodes.OptionalComponentOrHelper, value, {
ifComponent(component) {
InvokeComponent(op, component, null, null, null, null);
},
ifHelper(handle) {
op(VM_PUSH_FRAME_OP);
Call(op, handle, null, null);
op(VM_INVOKE_STATIC_OP, stdlibOperand('cautious-non-dynamic-append'));
op(VM_POP_FRAME_OP);
},
ifValue(handle) {
op(VM_PUSH_FRAME_OP);
op(VM_CONSTANT_REFERENCE_OP, handle);
op(VM_INVOKE_STATIC_OP, stdlibOperand('cautious-non-dynamic-append'));
op(VM_POP_FRAME_OP);
}
});
} else if (value[0] === opcodes.Call) {
let [, expression, positional, named] = value;
if (isGetFreeComponentOrHelper(expression)) {
op(HighLevelResolutionOpcodes.ComponentOrHelper, expression, {
ifComponent(component) {
InvokeComponent(op, component, null, positional, hashToArgs(named), null);
},
ifHelper(handle) {
op(VM_PUSH_FRAME_OP);
Call(op, handle, positional, named);
op(VM_INVOKE_STATIC_OP, stdlibOperand('cautious-non-dynamic-append'));
op(VM_POP_FRAME_OP);
}
});
} else {
SwitchCases(op, () => {
expr(op, expression);
op(VM_DYNAMIC_CONTENT_TYPE_OP);
}, when => {
when(ContentType.Component, () => {
op(VM_RESOLVE_CURRIED_COMPONENT_OP);
op(VM_PUSH_DYNAMIC_COMPONENT_IN