ember-source
Version:
A JavaScript framework for creating ambitious web applications
1,143 lines (1,125 loc) • 62.8 kB
JavaScript
import { isSmallInt, encodeImmediate, assert as debugAssert, EMPTY_STRING_ARRAY, unwrap, reverse, expect, debugToString as debugToString$1, Stack as StackImpl, isPresentArray, encodeHandle, EMPTY_ARRAY, assign, enumerate, dict } from '../util/index.js';
import { Op, MachineOp, $v0, $fp, InternalComponentCapabilities, $s0, $sp, ContentType, $s1, TYPE_SIZE, isMachineOp, MACHINE_MASK, ARG_SHIFT } from '../vm/index.js';
import { InstructionEncoderImpl } from '../encoder/index.js';
import { SexpOpcodes as opcodes } from '../wire-format/index.js';
import { hasCapability } from '../manager/index.js';
import { isDevelopingApp } from '@embroider/macros';
let debugCompiler;
function makeResolutionTypeVerifier(typeToVerify) {
return opcode => {
if (!function (opcode) {
return Array.isArray(opcode) && 2 === opcode.length;
}(opcode)) return !1;
let type = opcode[0];
return type === opcodes.GetStrictKeyword || type === opcodes.GetLexicalSymbol || type === typeToVerify;
};
}
new Array(Op.Size).fill(null), new Array(Op.Size).fill(null);
const isGetFreeComponent = makeResolutionTypeVerifier(opcodes.GetFreeAsComponentHead),
isGetFreeModifier = makeResolutionTypeVerifier(opcodes.GetFreeAsModifierHead),
isGetFreeHelper = makeResolutionTypeVerifier(opcodes.GetFreeAsHelperHead),
isGetFreeComponentOrHelper = makeResolutionTypeVerifier(opcodes.GetFreeAsComponentOrHelperHead);
function assertResolverInvariants(meta) {
if (isDevelopingApp()) {
if (!meta.upvars) throw new Error("Attempted to resolve a component, helper, or modifier, but no free vars were found");
if (!meta.owner) throw new Error("Attempted to resolve a component, helper, or modifier, but no owner was associated with the template it was being resolved from");
}
return meta;
}
/**
* <Foo/>
* <Foo></Foo>
* <Foo @arg={{true}} />
*/
function lookupBuiltInHelper(expr, resolver, meta, constants, type) {
let {
upvars: upvars
} = assertResolverInvariants(meta),
name = unwrap(upvars[expr[1]]),
helper = resolver.lookupBuiltInHelper(name);
if (isDevelopingApp() && null === helper)
// Keyword helper did not exist, which means that we're attempting to use a
// value of some kind that is not in scope
throw debugAssert(!meta.isStrictMode, "Strict mode errors should already be handled at compile time"), new Error(`Attempted to resolve a ${type} in a strict mode template, but that value was not in scope: ${meta.upvars[expr[1]] ?? "{unknown variable}"}`);
return constants.helper(helper, name);
}
const HighLevelResolutionOpcodes = {
Modifier: 1003,
Component: 1004,
Helper: 1005,
ComponentOrHelper: 1007,
OptionalComponentOrHelper: 1008,
Local: 1010,
TemplateLocal: 1011
},
HighLevelBuilderOpcodes = {
Label: 1e3,
StartLabels: 1001,
StopLabels: 1002,
Start: 1e3,
End: 1002
},
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: value
};
}
function isStrictMode() {
return {
type: HighLevelOperands.IsStrictMode,
value: void 0
};
}
function stdlibOperand(value) {
return {
type: HighLevelOperands.StdLib,
value: value
};
}
function symbolTableOperand(value) {
return {
type: HighLevelOperands.SymbolTable,
value: value
};
}
function layoutOperand(value) {
return {
type: HighLevelOperands.Layout,
value: value
};
}
class Labels {
labels = dict();
targets = [];
label(name, index) {
this.labels[name] = index;
}
target(at, target) {
this.targets.push({
at: at,
target: target
});
}
patch(heap) {
let {
targets: targets,
labels: labels
} = this;
for (const {
at: at,
target: target
} of targets) {
let address = labels[target] - at;
debugAssert(-1 === heap.getbyaddr(at), "Expected heap to contain a placeholder, but it did not"), heap.setbyaddr(at, address);
}
}
}
function encodeOp(encoder, constants, resolver, meta, op) {
if (function (op) {
return op < HighLevelBuilderOpcodes.Start;
}(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 function (resolver, constants, meta, [, expr, then]) {
debugAssert(isGetFreeComponent(expr), "Attempted to resolve a component with incorrect opcode");
let type = expr[0];
if (isDevelopingApp() && expr[0] === opcodes.GetStrictKeyword) throw debugAssert(!meta.isStrictMode, "Strict mode errors should already be handled at compile time"), new Error(`Attempted to resolve a component in a strict mode template, but that value was not in scope: ${meta.upvars[expr[1]] ?? "{unknown variable}"}`);
if (type === opcodes.GetLexicalSymbol) {
let {
scopeValues: scopeValues,
owner: owner
} = meta,
definition = expect(scopeValues, "BUG: scopeValues must exist if template symbol is used")[expr[1]];
then(constants.component(definition, expect(owner, "BUG: expected owner when resolving component definition")));
} else {
let {
upvars: upvars,
owner: owner
} = assertResolverInvariants(meta),
name = unwrap(upvars[expr[1]]),
definition = resolver.lookupComponent(name, owner);
if (isDevelopingApp() && ("object" != typeof definition || null === definition)) throw debugAssert(!meta.isStrictMode, "Strict mode errors should already be handled at compile time"), new Error(`Attempted to resolve \`${name}\`, which was expected to be a component, but nothing was found.`);
then(constants.resolvedComponent(definition, name));
}
}
/**
* (helper)
* (helper arg)
*/(resolver, constants, meta, op);
case HighLevelResolutionOpcodes.Modifier:
/**
* <div {{modifier}}/>
* <div {{modifier arg}}/>
* <Foo {{modifier}}/>
*/
return function (resolver, constants, meta, [, expr, then]) {
debugAssert(isGetFreeModifier(expr), "Attempted to resolve a modifier with incorrect opcode");
let type = expr[0];
if (type === opcodes.GetLexicalSymbol) {
let {
scopeValues: scopeValues
} = meta,
definition = expect(scopeValues, "BUG: scopeValues must exist if template symbol is used")[expr[1]];
then(constants.modifier(definition));
} else if (type === opcodes.GetStrictKeyword) {
let {
upvars: upvars
} = assertResolverInvariants(meta),
name = unwrap(upvars[expr[1]]),
modifier = resolver.lookupBuiltInModifier(name);
if (isDevelopingApp() && null === modifier) throw debugAssert(!meta.isStrictMode, "Strict mode errors should already be handled at compile time"), new Error(`Attempted to resolve a modifier in a strict mode template, but it was not in scope: ${name}`);
then(constants.modifier(modifier, name));
} else {
let {
upvars: upvars,
owner: owner
} = assertResolverInvariants(meta),
name = unwrap(upvars[expr[1]]),
modifier = resolver.lookupModifier(name, owner);
if (isDevelopingApp() && null === modifier) throw debugAssert(!meta.isStrictMode, "Strict mode errors should already be handled at compile time"), new Error(`Attempted to resolve \`${name}\`, which was expected to be a modifier, but nothing was found.`);
then(constants.modifier(modifier, name));
}
}
/**
* {{component-or-helper arg}}
*/(resolver, constants, meta, op);
case HighLevelResolutionOpcodes.Helper:
return function (resolver, constants, meta, [, expr, then]) {
debugAssert(isGetFreeHelper(expr), "Attempted to resolve a helper with incorrect opcode");
let type = expr[0];
if (type === opcodes.GetLexicalSymbol) {
let {
scopeValues: scopeValues
} = meta,
definition = expect(scopeValues, "BUG: scopeValues must exist if template symbol is used")[expr[1]];
then(constants.helper(definition));
} else if (type === opcodes.GetStrictKeyword) then(lookupBuiltInHelper(expr, resolver, meta, constants, "helper"));else {
let {
upvars: upvars,
owner: owner
} = assertResolverInvariants(meta),
name = unwrap(upvars[expr[1]]),
helper = resolver.lookupHelper(name, owner);
if (isDevelopingApp() && null === helper) throw debugAssert(!meta.isStrictMode, "Strict mode errors should already be handled at compile time"), new Error(`Attempted to resolve \`${name}\`, which was expected to be a helper, but nothing was found.`);
then(constants.helper(helper, name));
}
}(resolver, constants, meta, op);
case HighLevelResolutionOpcodes.ComponentOrHelper:
return function (resolver, constants, meta, [, expr, {
ifComponent: ifComponent,
ifHelper: ifHelper
}]) {
debugAssert(isGetFreeComponentOrHelper(expr), "Attempted to resolve a component or helper with incorrect opcode");
let type = expr[0];
if (type === opcodes.GetLexicalSymbol) {
let {
scopeValues: scopeValues,
owner: owner
} = meta,
definition = expect(scopeValues, "BUG: scopeValues must exist if template symbol is used")[expr[1]],
component = constants.component(definition, expect(owner, "BUG: expected owner when resolving component definition"), !0);
if (null !== component) return void ifComponent(component);
let helper = constants.helper(definition, null, !0);
if (isDevelopingApp() && null === helper) throw debugAssert(!meta.isStrictMode, "Strict mode errors should already be handled at compile time"), new Error(`Attempted to use a value as either a component or helper, but it did not have a component manager or helper manager associated with it. The value was: ${debugToString$1(definition)}`);
ifHelper(expect(helper, "BUG: helper must exist"));
} else if (type === opcodes.GetStrictKeyword) ifHelper(lookupBuiltInHelper(expr, resolver, meta, constants, "component or helper"));else {
let {
upvars: upvars,
owner: owner
} = assertResolverInvariants(meta),
name = unwrap(upvars[expr[1]]),
definition = resolver.lookupComponent(name, owner);
if (null !== definition) ifComponent(constants.resolvedComponent(definition, name));else {
let helper = resolver.lookupHelper(name, owner);
if (isDevelopingApp() && null === helper) throw debugAssert(!meta.isStrictMode, "Strict mode errors should already be handled at compile time"), new Error(`Attempted to resolve \`${name}\`, which was expected to be a component or helper, but nothing was found.`);
ifHelper(constants.helper(helper, name));
}
}
}
/**
* {{maybeHelperOrComponent}}
*/(resolver, constants, meta, op);
case HighLevelResolutionOpcodes.OptionalComponentOrHelper:
return function (resolver, constants, meta, [, expr, {
ifComponent: ifComponent,
ifHelper: ifHelper,
ifValue: ifValue
}]) {
debugAssert(isGetFreeComponentOrHelper(expr), "Attempted to resolve an optional component or helper with incorrect opcode");
let type = expr[0];
if (type === opcodes.GetLexicalSymbol) {
let {
scopeValues: scopeValues,
owner: owner
} = meta,
definition = expect(scopeValues, "BUG: scopeValues must exist if template symbol is used")[expr[1]];
if ("function" != typeof definition && ("object" != typeof definition || null === definition))
// The value is not an object, so it can't be a component or helper.
return void ifValue(constants.value(definition));
let component = constants.component(definition, expect(owner, "BUG: expected owner when resolving component definition"), !0);
if (null !== component) return void ifComponent(component);
let helper = constants.helper(definition, null, !0);
if (null !== helper) return void ifHelper(helper);
ifValue(constants.value(definition));
} else if (type === opcodes.GetStrictKeyword) ifHelper(lookupBuiltInHelper(expr, resolver, meta, constants, "value"));else {
let {
upvars: upvars,
owner: owner
} = assertResolverInvariants(meta),
name = unwrap(upvars[expr[1]]),
definition = resolver.lookupComponent(name, owner);
if (null !== definition) return void ifComponent(constants.resolvedComponent(definition, name));
let helper = resolver.lookupHelper(name, owner);
null !== helper && ifHelper(constants.helper(helper, name));
}
}(resolver, constants, meta, op);
case HighLevelResolutionOpcodes.Local:
{
let freeVar = op[1],
name = expect(meta.upvars, "BUG: attempted to resolve value but no upvars found")[freeVar];
(0, op[2])(name, meta.moduleName);
break;
}
case HighLevelResolutionOpcodes.TemplateLocal:
{
let [, valueIndex, then] = op,
value = expect(meta.scopeValues, "BUG: Attempted to get a template local, but template does not have any")[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(Op.Primitive, 0), this.errors.push(error);
}
commit(size) {
let handle = this.handle;
return this.heap.pushMachine(MachineOp.Return), this.heap.finishMalloc(handle, size), isPresentArray(this.errors) ? {
errors: this.errors,
handle: handle
} : handle;
}
push(constants, type, ...args) {
let {
heap: heap
} = this;
if (isDevelopingApp() && type > TYPE_SIZE) throw new Error(`Opcode type over 8-bits. Got ${type}.`);
let first = type | (isMachineOp(type) ? MACHINE_MASK : 0) | 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 ("number" == typeof operand) return operand;
if ("object" == typeof operand && null !== operand) {
if (Array.isArray(operand)) return encodeHandle(constants.array(operand));
switch (operand.type) {
case HighLevelOperands.Label:
return this.currentLabels.target(this.heap.offset, operand.value), -1;
case HighLevelOperands.IsStrictMode:
return encodeHandle(constants.value(this.meta.isStrictMode));
case HighLevelOperands.DebugSymbols:
return encodeHandle(constants.array(this.meta.evalSymbols || EMPTY_STRING_ARRAY));
case HighLevelOperands.Block:
return encodeHandle(constants.value((block = operand.value, containing = this.meta, new CompilableTemplateImpl(block[0], containing, {
parameters: block[1] || EMPTY_ARRAY
}))));
case HighLevelOperands.StdLib:
return expect(this.stdlib, "attempted to encode a stdlib operand, but the encoder did not have a stdlib. Are you currently building the stdlib?")[operand.value];
case HighLevelOperands.NonSmallInt:
case HighLevelOperands.SymbolTable:
case HighLevelOperands.Layout:
return constants.value(operand.value);
}
}
var block, containing;
return encodeHandle(constants.value(operand));
}
get currentLabels() {
return expect(this.labelsStack.current, "bug: not in a label stack");
}
label(name) {
this.currentLabels.label(name, this.heap.offset + 1);
}
startLabels() {
this.labelsStack.push(new Labels());
}
stopLabels() {
expect(this.labelsStack.pop(), "unbalanced push and pop labels").patch(this.heap);
}
}
class StdLib {
constructor(main, trustingGuardedAppend, cautiousGuardedAppend, trustingNonDynamicAppend, cautiousNonDynamicAppend) {
this.main = main, this.trustingGuardedAppend = trustingGuardedAppend, this.cautiousGuardedAppend = cautiousGuardedAppend, this.trustingNonDynamicAppend = trustingNonDynamicAppend, this.cautiousNonDynamicAppend = cautiousNonDynamicAppend;
}
get "trusting-append"() {
return this.trustingGuardedAppend;
}
get "cautious-append"() {
return this.cautiousGuardedAppend;
}
get "trusting-non-dynamic-append"() {
return this.trustingNonDynamicAppend;
}
get "cautious-non-dynamic-append"() {
return this.cautiousNonDynamicAppend;
}
getAppend(trusting) {
return trusting ? this.trustingGuardedAppend : this.cautiousGuardedAppend;
}
}
class NamedBlocksImpl {
names;
constructor(blocks) {
this.blocks = blocks, this.names = blocks ? Object.keys(blocks) : [];
}
get(name) {
return this.blocks && this.blocks[name] || null;
}
has(name) {
let {
blocks: blocks
} = this;
return null !== blocks && name in blocks;
}
with(name, block) {
let {
blocks: blocks
} = this;
return new NamedBlocksImpl(blocks ? assign({}, blocks, {
[name]: block
}) : {
[name]: block
});
}
get hasAny() {
return null !== this.blocks;
}
}
const EMPTY_BLOCKS = new NamedBlocksImpl(null);
function namedBlocks(blocks) {
if (null === blocks) return EMPTY_BLOCKS;
let out = dict(),
[keys, values] = blocks;
for (const [i, key] of enumerate(keys)) out[key] = unwrap(values[i]);
return new NamedBlocksImpl(out);
}
/**
* 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(Op.PrimitiveReference);
}
/**
* 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;
var value;
"number" == typeof p && (p = isSmallInt(p) ? encodeImmediate(p) : (debugAssert(!isSmallInt(value = p), "Attempted to make a operand for an int that was not a small int, you should encode this as an immediate"), {
type: HighLevelOperands.NonSmallInt,
value: value
})), op(Op.Primitive, 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(MachineOp.PushFrame), SimpleArgs(op, positional, named, !1), op(Op.Helper, handle), op(MachineOp.PopFrame), op(Op.Fetch, $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(MachineOp.PushFrame), SimpleArgs(op, positional, named, !1), op(Op.Dup, $fp, 1), op(Op.DynamicHelper), append ? (op(Op.Fetch, $v0), append(), op(MachineOp.PopFrame), op(Op.Pop, 1)) : (op(MachineOp.PopFrame), op(Op.Pop, 1), op(Op.Fetch, $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 Curry(op, type, definition, positional, named) {
op(MachineOp.PushFrame), SimpleArgs(op, positional, named, !1), op(Op.CaptureArgs), expr(op, definition), op(Op.Curry, type, isStrictMode()), op(MachineOp.PopFrame), op(Op.Fetch, $v0);
}
class Compilers {
names = {};
funcs = [];
add(name, func) {
this.names[name] = this.funcs.push(func) - 1;
}
compile(op, sexp) {
let name = sexp[0],
index = unwrap(this.names[name]),
func = this.funcs[index];
debugAssert(!!func, `expected an implementation for ${sexp[0]}`), func(op, sexp);
}
}
const EXPRESSIONS = new Compilers();
function withPath(op, path) {
if (void 0 !== path && 0 !== path.length) for (let i = 0; i < path.length; i++) op(Op.GetProperty, path[i]);
}
function expr(op, expression) {
Array.isArray(expression) ? EXPRESSIONS.compile(op, expression) : (PushPrimitive(op, expression), op(Op.PrimitiveReference));
}
/**
* Compile arguments, pushing an Arguments object onto the stack.
*
* @param args.params
* @param args.hash
* @param args.blocks
* @param args.atNames
*/
function SimpleArgs(op, positional, named, atNames) {
if (null === positional && null === named) return void op(Op.PushEmptyArgs);
let flags = CompilePositional(op, positional) << 4;
atNames && (flags |= 8);
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(Op.PushArgs, 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 (null === positional) return 0;
for (let i = 0; i < positional.length; i++) expr(op, positional[i]);
return positional.length;
}
function meta(layout) {
let [, symbols,, upvars] = layout.block;
return {
evalSymbols: evalSymbols(layout),
upvars: upvars,
scopeValues: layout.scope?.() ?? null,
isStrictMode: layout.isStrictMode,
moduleName: layout.moduleName,
owner: layout.owner,
size: symbols.length
};
}
function evalSymbols(layout) {
let {
block: block
} = layout,
[, symbols, hasEval] = block;
return hasEval ? symbols : null;
}
/**
* 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, !0), op(Op.GetBlock, to), op(Op.SpreadBlock), op(Op.CompileBlock), op(Op.InvokeYield), op(Op.PopScope), op(MachineOp.PopFrame);
}
/**
* 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) {
!function (op, parameters) {
null !== parameters ? op(Op.PushSymbolTable, symbolTableOperand({
parameters: parameters
})) : PushPrimitive(op, null);
}(op, block && block[1]), op(Op.PushBlockScope), PushCompilable(op, block);
}
/**
* Invoke a block that is known statically at compile time.
*
* @param block a Compilable block
*/
function InvokeStaticBlock(op, block) {
op(MachineOp.PushFrame), PushCompilable(op, block), op(Op.CompileBlock), op(MachineOp.InvokeVirtual), op(MachineOp.PopFrame);
}
/**
* 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],
calleeCount = parameters.length,
count = Math.min(callerCount, calleeCount);
if (0 !== count) {
if (op(MachineOp.PushFrame), count) {
op(Op.ChildScope);
for (let i = 0; i < count; i++) op(Op.Dup, $fp, callerCount - i), op(Op.SetVariable, parameters[i]);
}
PushCompilable(op, block), op(Op.CompileBlock), op(MachineOp.InvokeVirtual), count && op(Op.PopScope), op(MachineOp.PopFrame);
} else InvokeStaticBlock(op, block);
}
function PushCompilable(op, _block) {
var value;
null === _block ? PushPrimitive(op, null) : op(Op.Constant, (value = _block, {
type: HighLevelOperands.Block,
value: value
}));
}
function SwitchCases(op, bootstrap, matcher) {
// Setup the switch DSL
let clauses = [],
count = 0;
// Call the callback
matcher(function (match, callback) {
clauses.push({
match: match,
callback: callback,
label: "CLAUSE" + count++
});
}),
// Emit the opcodes for the switch
op(Op.Enter, 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(Op.JumpEq, 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(Op.Pop, 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.
0 !== i && op(MachineOp.Jump, labelOperand("END"));
}
op(HighLevelBuilderOpcodes.Label, "END"), op(HighLevelBuilderOpcodes.StopLabels), op(Op.Exit);
}
/**
* 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(MachineOp.PushFrame),
// If the body invokes a block, its return will return to
// END. Otherwise, the return in RETURN will return to END.
op(MachineOp.ReturnTo, 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(Op.Enter, 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(Op.Exit),
// In initial execution, this is a noop: it returns to the
// immediately following opcode. In updating execution, this
// exits the updating routine.
op(MachineOp.Return),
// Cleanup code for the block. Runs on initial execution
// but not on updating.
op(HighLevelBuilderOpcodes.Label, "ENDINITIAL"), op(MachineOp.PopFrame), 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(Op.JumpUnless, 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(MachineOp.Jump, 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.
void 0 !== ifFalse && ifFalse();
});
}
// {{component}}
// <Component>
// chokepoint
function InvokeComponent(op, component, _elementBlock, positional, named, _blocks) {
let {
compilable: compilable,
capabilities: capabilities,
handle: handle
} = component,
elementBlock = _elementBlock ? [_elementBlock, []] : null,
blocks = Array.isArray(_blocks) || null === _blocks ? namedBlocks(_blocks) : _blocks;
compilable ? (op(Op.PushComponentDefinition, handle), function (op, {
capabilities: capabilities,
layout: layout,
elementBlock: elementBlock,
positional: positional,
named: named,
blocks: blocks
}) {
let {
symbolTable: symbolTable
} = layout;
if (symbolTable.hasEval || hasCapability(capabilities, InternalComponentCapabilities.prepareArgs)) return void InvokeNonStaticComponent(op, {
capabilities: capabilities,
elementBlock: elementBlock,
positional: positional,
named: named,
atNames: !0,
blocks: blocks,
layout: layout
});
op(Op.Fetch, $s0), op(Op.Dup, $sp, 1), op(Op.Load, $s0), op(MachineOp.PushFrame);
// Setup arguments
let {
symbols: symbols
} = symbolTable,
blockSymbols = [],
argSymbols = [],
argNames = [],
blockNames = blocks.names;
// 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
// Starting with the attrs block, if it exists and is referenced in the component
if (null !== elementBlock) {
let symbol = symbols.indexOf("&attrs");
-1 !== symbol && (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}`);
-1 !== symbol && (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 flags = CompilePositional(op, positional) << 4;
// setup the flags with the count of positionals, and to indicate that atNames
// are used
flags |= 8;
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 (null !== named) {
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(Op.PushArgs, 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 (null !== named) {
// 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],
val = named[1];
for (let i = 0; i < val.length; i++) {
let name = unwrap(names[i]),
symbol = symbols.indexOf(name);
-1 !== symbol && (expr(op, val[i]), argSymbols.push(symbol), argNames.push(name));
}
}
op(Op.BeginComponentTransaction, $s0), hasCapability(capabilities, InternalComponentCapabilities.dynamicScope) && op(Op.PushDynamicScope), hasCapability(capabilities, InternalComponentCapabilities.createInstance) && op(Op.CreateComponent, 0 | blocks.has("default"), $s0), op(Op.RegisterComponentDestructor, $s0), hasCapability(capabilities, InternalComponentCapabilities.createArgs) ? op(Op.GetComponentSelf, $s0) : op(Op.GetComponentSelf, $s0, argNames),
// Setup the new root scope for the component
op(Op.RootScope, 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(Op.SetVariable, 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];
-1 === symbol ?
// The expression was not bound to a local symbol, it was only pushed to be
// used with VM args in the javascript side
op(Op.Pop, 1) : op(Op.SetVariable, symbol + 1);
// if any positional params exist, pop them off the stack as well
null !== positional && op(Op.Pop, positional.length);
// Finish up by popping off and assigning blocks
for (const symbol of reverse(blockSymbols)) op(Op.SetBlock, symbol + 1);
op(Op.Constant, layoutOperand(layout)), op(Op.CompileBlock), op(MachineOp.InvokeVirtual), op(Op.DidRenderLayout, $s0), op(MachineOp.PopFrame), op(Op.PopScope), hasCapability(capabilities, InternalComponentCapabilities.dynamicScope) && op(Op.PopDynamicScope), op(Op.CommitComponentTransaction), op(Op.Load, $s0);
}(op, {
capabilities: capabilities,
layout: compilable,
elementBlock: elementBlock,
positional: positional,
named: named,
blocks: blocks
})) : (op(Op.PushComponentDefinition, handle), InvokeNonStaticComponent(op, {
capabilities: capabilities,
elementBlock: elementBlock,
positional: positional,
named: named,
atNames: !0,
blocks: blocks
}));
}
function InvokeDynamicComponent(op, definition, _elementBlock, positional, named, _blocks, atNames, curried) {
let elementBlock = _elementBlock ? [_elementBlock, []] : null,
blocks = Array.isArray(_blocks) || null === _blocks ? namedBlocks(_blocks) : _blocks;
Replayable(op, () => (expr(op, definition), op(Op.Dup, $sp, 0), 2), () => {
op(Op.JumpUnless, labelOperand("ELSE")), curried ? op(Op.ResolveCurriedComponent) : op(Op.ResolveDynamicComponent, isStrictMode()), op(Op.PushDynamicComponentInstance), InvokeNonStaticComponent(op, {
capabilities: !0,
elementBlock: elementBlock,
positional: positional,
named: named,
atNames: atNames,
blocks: blocks
}), op(HighLevelBuilderOpcodes.Label, "ELSE");
});
}
function InvokeNonStaticComponent(op, {
capabilities: capabilities,
elementBlock: elementBlock,
positional: positional,
named: named,
atNames: atNames,
blocks: namedBlocks,
layout: layout
}) {
let bindableBlocks = !!namedBlocks,
bindableAtNames = !0 === capabilities || hasCapability(capabilities, InternalComponentCapabilities.prepareArgs) || !(!named || 0 === named[0].length),
blocks = namedBlocks.with("attrs", elementBlock);
op(Op.Fetch, $s0), op(Op.Dup, $sp, 1), op(Op.Load, $s0), op(MachineOp.PushFrame), function (op, positional, named, blocks, atNames) {
let blockNames = blocks.names;
for (const name of blockNames) PushYieldableBlock(op, blocks.get(name));
let flags = CompilePositional(op, positional) << 4;
atNames && (flags |= 8), blocks && (flags |= 7);
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(Op.PushArgs, names, blockNames, flags);
}(op, positional, named, blocks, atNames), op(Op.PrepareArgs, $s0), invokePreparedComponent(op, blocks.has("default"), bindableBlocks, bindableAtNames, () => {
layout ? (op(Op.PushSymbolTable, symbolTableOperand(layout.symbolTable)), op(Op.Constant, layoutOperand(layout)), op(Op.CompileBlock)) : op(Op.GetComponentLayout, $s0), op(Op.PopulateLayout, $s0);
}), op(Op.Load, $s0);
}
function invokePreparedComponent(op, hasBlock, bindableBlocks, bindableAtNames, populateLayout = null) {
op(Op.BeginComponentTransaction, $s0), op(Op.PushDynamicScope), op(Op.CreateComponent, 0 | hasBlock, $s0),
// 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.
populateLayout && populateLayout(), op(Op.RegisterComponentDestructor, $s0), op(Op.GetComponentSelf, $s0), op(Op.VirtualRootScope, $s0), op(Op.SetVariable, 0), op(Op.SetupForEval, $s0), bindableAtNames && op(Op.SetNamedVariables, $s0), bindableBlocks && op(Op.SetBlocks, $s0), op(Op.Pop, 1), op(Op.InvokeComponentLayout, $s0), op(Op.DidRenderLayout, $s0), op(MachineOp.PopFrame), op(Op.PopScope), op(Op.PopDynamicScope), op(Op.CommitComponentTransaction);
}
/**
* Append content to the DOM. This standard function triages content and does the
* right thing based upon whether it's a string, safe string, component, fragment
* or node.
*
* @param trusting whether to interpolate a string as raw HTML (corresponds to
* triple curlies)
*/
function StdAppend(op, trusting, nonDynamicAppend) {
SwitchCases(op, () => op(Op.ContentType), when => {
when(ContentType.String, () => {
trusting ? (op(Op.AssertSame), op(Op.AppendHTML)) : op(Op.AppendText);
}), "number" == typeof nonDynamicAppend ? (when(ContentType.Component, () => {
op(Op.ResolveCurriedComponent), op(Op.PushDynamicComponentInstance), function (op) {
op(Op.Fetch, $s0), op(Op.Dup, $sp, 1), op(Op.Load, $s0), op(MachineOp.PushFrame), op(Op.PushEmptyArgs), op(Op.PrepareArgs, $s0), invokePreparedComponent(op, !1, !1, !0, () => {
op(Op.GetComponentLayout, $s0), op(Op.PopulateLayout, $s0);
}), op(Op.Load, $s0);
}(op);
}), when(ContentType.Helper, () => {
CallDynamic(op, null, null, () => {
op(MachineOp.InvokeStatic, nonDynamicAppend);
});
})) : (
// when non-dynamic, we can no longer call the value (potentially because we've already called it)
// this prevents infinite loops. We instead coerce the value, whatever it is, into the DOM.
when(ContentType.Component, () => {
op(Op.AppendText);
}), when(ContentType.Helper, () => {
op(Op.AppendText);
})), when(ContentType.SafeString, () => {
op(Op.AssertSame), op(Op.AppendSafeHTML);
}), when(ContentType.Fragment, () => {
op(Op.AssertSame), op(Op.AppendDocumentFragment);
}), when(ContentType.Node, () => {
op(Op.AssertSame), op(Op.AppendNode);
});
});
}
function compileStd(context) {
let mainHandle = build(context, op => function (op) {
op(Op.Main, $s0), invokePreparedComponent(op, !1, !1, !0);
}(op)),
trustingGuardedNonDynamicAppend = build(context, op => StdAppend(op, !0, null)),
cautiousGuardedNonDynamicAppend = build(context, op => StdAppend(op, !1, null)),
trustingGuardedDynamicAppend = build(context, op => StdAppend(op, !0, trustingGuardedNonDynamicAppend)),
cautiousGuardedDynamicAppend = build(context, op => StdAppend(op, !1, cautiousGuardedNonDynamicAppend));
return new StdLib(mainHandle, trustingGuardedDynamicAppend, cautiousGuardedDynamicAppend, trustingGuardedNonDynamicAppend, cautiousGuardedNonDynamicAppend);
}
EXPRESSIONS.add(opcodes.Concat, (op, [, parts]) => {
for (let part of parts) expr(op, part);
op(Op.Concat, parts.length);
}), EXPRESSIONS.add(opcodes.Call, (op, [, expression, positional, named]) => {
isGetFreeHelper(expression) ? op(HighLevelResolutionOpcodes.Helper, expression, handle => {
Call(op, handle, positional, named);
}) : (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(Op.GetVariable, sym), withPath(op, path);
}), EXPRESSIONS.add(opcodes.GetLexicalSymbol, (op, [, sym, path]) => {
op(HighLevelResolutionOpcodes.TemplateLocal, sym, handle => {
op(Op.ConstantReference, 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);
});
});
}), EXPRESSIONS.add(opcodes.Undefined, op => PushPrimitiveReference(op, void 0)), EXPRESSIONS.add(opcodes.HasBlock, (op, [, block]) => {
expr(op, block), op(Op.HasBlock);
}), EXPRESSIONS.add(opcodes.HasBlockParams, (op, [, block]) => {
expr(op, block), op(Op.SpreadBlock), op(Op.CompileBlock), op(Op.HasBlockParams);
}), EXPRESSIONS.add(opcodes.IfInline, (op, [, condition, truthy, falsy]) => {
// Push in reverse order
expr(op, falsy), expr(op, truthy), expr(op, condition), op(Op.IfInline);
}), EXPRESSIONS.add(opcodes.Not, (op, [, value]) => {
expr(op, value), op(Op.Not);
}), EXPRESSIONS.add(opcodes.GetDynamicVar, (op, [, expression]) => {
expr(op, expression), op(Op.GetDynamicVar);
}), EXPRESSIONS.add(opcodes.Log, (op, [, positional]) => {
op(MachineOp.PushFrame), SimpleArgs(op, positional, null, !1), op(Op.Log), op(MachineOp.PopFrame), op(Op.Fetch, $v0);
});
const STDLIB_META = {
evalSymbols: null,
upvars: null,
moduleName: "stdlib",
// TODO: ??
scopeValues: null,
isStrictMode: !0,
owner: null,
size: 0
};
function build(program, builder) {
let {
constants: constants,
heap: heap,
resolver: resolver
} = program,
encoder = new EncoderImpl(heap, STDLIB_META);
builder(function (...op) {
encodeOp(encoder, constants, resolver, STDLIB_META, op);
});
let result = encoder.commit(0);
if ("number" != typeof result)
// This shouldn't be possible
throw new Error("Unexpected errors compiling std");
return result;
}
class CompileTimeCompilationContextImpl {
constants;
heap;
stdlib;
constructor({
constants: constants,
heap: heap
}, resolver, createOp) {
this.resolver = resolver, this.createOp = createOp, this.constants = constants, this.heap = heap, this.stdlib = compileStd(this);
}
}
function programCompilationContext(artifacts, resolver, createOp) {
return new CompileTimeCompilationContextImpl(artifacts, resolver, createOp);
}
function templateCompilationContext(program, meta) {
return {
program: program,
encoder: new EncoderImpl(program.heap, meta, program.stdlib),
meta: meta
};
}
const STATEMENTS = new Compilers(),
INFLATE_ATTR_TABLE = ["class", "id", "value", "name", "type", "style", "href"],
INFLATE_TAG_TABLE = ["div", "span", "p", "a"];
function inflateTagName(tagName) {
return "string" == typeof tagName ? tagName : INFLATE_TAG_TABLE[tagName];
}
function inflateAttrName(attrName) {
return "string" == typeof attrName ? attrName : INFLATE_ATTR_TABLE[attrName];
}
function hashToArgs(hash) {
return null === hash ? null : [hash[0].map(key => `@${key}`), hash[1]];
}
STATEMENTS.add(opcodes.Comment, (op, sexp) => op(Op.Comment, sexp[1])), STATEMENTS.add(opcodes.CloseElement, op => op(Op.CloseElement)), STATEMENTS.add(opcodes.FlushElement, op => op(Op.FlushElement)), STATEMENTS.add(opcodes.Modifier, (op, [, expression, positional, named]) => {
isGetFreeModifier(expression) ? op(HighLevelResolutionOpcodes.Modifier, expression, handle => {
op(MachineOp.PushFrame), SimpleArgs(op, positional, named, !1), op(Op.Modifier, handle), op(MachineOp.PopFrame);
}) : (expr(op, expression), op(MachineOp.PushFrame), SimpleArgs(op, positional, named, !1), op(Op.Dup, $fp, 1), op(Op.DynamicModifier), op(MachineOp.PopFrame));
}), STATEMENTS.add(opcodes.StaticAttr, (op, [, name, value, namespace]) => {
op(Op.StaticAttr, inflateAttrName(name), value, namespace ?? null);
}), STATEMENTS.add(opcodes.StaticComponentAttr, (op, [, name, value, namespace]) => {
op(Op.StaticComponentAttr, inflateAttrName(name), value, namespace ?? null);
}), STATEMENTS.add(opcodes.DynamicAttr, (op, [, name, value, namespace]) => {
expr(op, value), op(Op.DynamicAttr, inflateAttrName(name), !1, namespace ?? null);
}), STATEMENTS.add(opcodes.TrustingDynamicAttr, (op, [, name, value, namespace]) => {
expr(op, value), op(Op.DynamicAttr, inflateAttrName(name), !0, namespace ?? null);
}), STATEMENTS.add(opcodes.ComponentAttr, (op, [, name, value, namespace]) => {
expr(op, value), op(Op.ComponentAttr, inflateAttrName(name), !1, namespace ?? null);
}), STATEMENTS.add(opcodes.TrustingComponentAttr, (op, [, name, value, namespace]) => {
expr(op, value), op(Op.ComponentAttr, inflateAttrName(name), !0, namespace ?? null);
}), STATEMENTS.add(opcodes.OpenElement, (op, [, tag]) => {
op(Op.OpenElement, inflateTagName(tag));
}), STATEMENTS.add(opcodes.OpenElementWithSplat, (op, [, tag]) => {
op(Op.PutComponentOperations), op(Op.OpenElement, inflateTagName(tag));
}), STATEMENTS.add(opcodes.Component, (op, [, expr, elementBlock, named, blocks]) => {
isGetFreeComp