microvium
Version:
A compact, embeddable scripting engine for microcontrollers for executing small scripts written in a subset of JavaScript.
843 lines (841 loc) • 41 kB
JavaScript
;
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.pass1_findScopesAndBindings = void 0;
const utils_1 = require("../../utils");
const common_1 = require("../common");
const traverse_ast_1 = require("../traverse-ast");
const B = __importStar(require("../supported-babel-types"));
function pass1_findScopesAndBindings({ file, cur, importBindings, model, }) {
/*
(See analyzeScopes for a description of this pass)
This function is implemented as a single tree-traversal pass using
`traverseAST`. It maintains a `scopeStack` to keep track of what lexical scope
the cursor is in. When it encounters a new scope AST node (e.g. function or
block scope), it will push the scope onto the stack and enumerate the local
bindings. When it encounters a reference node (e.g. a variable or parameter
reference), it iterates up the stack to find the binding or falls back to
creating a free variable (`freeVariableNames`).
A `this` reference can either resolve to a local argument (if using the
caller-passed this) or to the `this` value in the parent (if using lexical
this, as in arrow functions). The `this` value in the parent may again resolve
to a parameter or to _its_ parent's `this` value, etc.
*/
const { references, bindings, scopes } = model;
const scopeStack = [];
const currentScope = () => (0, utils_1.notUndefined)(scopeStack[scopeStack.length - 1]);
const ilFunctionNames = new Set();
traverse(file.program);
function traverse(node_, context) {
const node = node_;
(0, common_1.visitingNode)(cur, node);
switch (node.type) {
// Scope nodes
case 'Program': return traverseModuleScope(node);
case 'FunctionDeclaration': return traverseFunctionDeclarationScope(node);
case 'ClassDeclaration': return traverseClassDeclaration(node);
case 'ClassMethod': (0, utils_1.unexpected)(); // These are iterated inside traverseClassDeclaration
case 'ArrowFunctionExpression': return traverseFunctionExpressionScope(cur, node);
case 'FunctionExpression': return traverseFunctionExpressionScope(cur, node);
case 'BlockStatement': return traverseBlockScope(node);
case 'ForStatement': return traverseForStatement(node);
case 'TryStatement': return traverseTryStatement(node);
// Reference nodes
case 'Identifier': return createVariableReference(node);
case 'ThisExpression': return createVariableReference(node);
// Mutating nodes
case 'AssignmentExpression': return handleAssignmentExpression(node);
case 'UpdateExpression': return handleUpdateExpression(node);
case 'AwaitExpression': return handleAwaitExpression(node);
default:
(0, traverse_ast_1.traverseChildren)(cur, node, traverse);
}
function traverseModuleScope(node) {
const scope = pushModuleScope(node);
model.moduleScope = scope;
const body = node.body;
findImportsAndExports(node);
// Find variables in the root scope and nested blocks
findVarDeclarations(body);
// Lexical variables are also found upfront because nested functions can
// reference variables that are declared further down than the nested
// function (TDZ). (But `findLexicalVariables` isn't recursive)
findBlockScopeDeclarations(body);
// Iterate through the function/program body to find variable usage
(0, traverse_ast_1.traverseChildren)(cur, node, traverse);
popScope(scope);
}
function traverseFunctionDeclarationScope(node, className) {
const isAsync = node.async === true;
const scope = pushFunctionScope(node, true, isAsync, className);
registerWithEmbeddingLocation(cur, scope);
createParameterBindings(scope, node.params);
const body = node.body.body;
findVarDeclarations(body);
// Note: we don't do `findBlockScopeDeclarations` because the traversal
// will find these declarations (let and const) in the function body which
// is a a "block"
// Iterate through the body to find variable usage
(0, traverse_ast_1.traverseChildren)(cur, node, traverse);
popScope(scope);
return scope;
}
function traverseClassDeclaration(node) {
// Note: this function runs multiple passes over the class
!node.superClass || (0, common_1.featureNotSupported)(cur, 'extends', node);
const className = node.id.name;
const classScope = pushClassScope(node);
node.body.body.forEach(n => B.isClassField(n) || (0, common_1.featureNotSupported)(cur, n.type, n));
const fields = node.body.body.filter(B.isClassField);
// Pass 1: methods and computed member names. These are evaluated in the
// parent scope because `this` refers to the same thing as the outer
// scope, whatever that is.
for (const decl of fields) {
if (decl.computed) {
(0, common_1.featureNotSupported)(cur, 'Computed names for class members', decl.key);
// traverse(decl.key)
}
if (decl.type === 'ClassMethod' && !B.isConstructor(decl)) {
traverseFunctionDeclarationScope(decl, className);
}
}
// Pass 2: static property initializers. These are evaluated in a scope
// where `this` refers to the class itself
classScope.staticConstructorScope = createBlockScope(undefined, true);
pushScope(classScope.staticConstructorScope);
classScope.staticConstructorScope.thisBinding = createBinding('#this', 'this', undefined, false, classScope.staticConstructorScope);
for (const decl of fields) {
if (decl.static && decl.type === 'ClassProperty' && decl.value) {
// For efficiency reasons, the static constructor of a class is inline
// rather than in a separate function. Normally `this` refers to
// arg[0], but nested inside a class static property initializer,
// `this` actually refers to the class itself. But it will be a pain
// to implement that and it gives almost now value, so I'm just
// disallowing it for the moment. A user can always just refer to the
// class name instead.
checkNoThis(cur, decl.value, 'static property initializer');
traverse(decl.value);
}
}
popScope(classScope.staticConstructorScope);
// Pass 3: non-static property initializers. These are evaluated in a
// scope where `this` refers to the class instance.
classScope.physicalConstructorScope = createFunctionScope(undefined, true, false, className);
pushScope(classScope.physicalConstructorScope);
for (const decl of fields) {
if (!decl.static && decl.type === 'ClassProperty' && decl.value) {
traverse(decl.value);
}
}
// Pass 4: the user-provided constructor itself is evaluated in a scope
// that contains both the `this` of the instance and the parameters of the
// constructor. Note: The virtual constructor is created as a child to the
// physical constructor of pass 3.
const userProvidedConstructor = fields.find(B.isConstructor);
if (userProvidedConstructor) {
classScope.virtualConstructorScope = createBlockScope(userProvidedConstructor, true);
scopes.set(userProvidedConstructor, classScope.virtualConstructorScope);
pushScope(classScope.virtualConstructorScope);
createParameterBindings(classScope.virtualConstructorScope, userProvidedConstructor.params);
const body = userProvidedConstructor.body;
findVarDeclarations(body.body);
(0, traverse_ast_1.traverseChildren)(cur, userProvidedConstructor, traverse);
popScope(classScope.virtualConstructorScope);
}
popScope(classScope.physicalConstructorScope);
popScope(classScope);
}
function traverseFunctionExpressionScope(cur, node) {
const hasThisBinding = node.type === 'FunctionExpression';
const isAsync = node.async === true;
const scope = pushFunctionScope(node, hasThisBinding, isAsync);
registerWithEmbeddingLocation(cur, scope);
createParameterBindings(scope, node.params);
const body = node.body;
if (node.type === 'FunctionExpression' && node.id) {
// Named function expressions are not supported yet, since they would
// introduce recursion possibilities that are not as simple to solve.
// E.g.
//
// const foo = function bar() { bar() };
// const bar = 42; // A different `bar`
//
return (0, common_1.featureNotSupported)(cur, 'Named function expressions');
}
if (body.type === 'BlockStatement') {
const statements = body.body;
findVarDeclarations(statements);
// Note: we don't do `findBlockScopeDeclarations` because the traversal
// will find these declarations (let and const) in the function body which
// is a "block"
}
else {
/* Note: Arrow functions with expression bodies do not have any hoisted variables */
}
traverse(body);
popScope(scope);
}
function traverseBlockScope(node, sameInstanceCountAsParent) {
// Creates a lexical scope
const scope = pushBlockScope(node, sameInstanceCountAsParent ?? true);
// Here we don't need to populate the hoisted variables because they're
// already populated by the containing function/program
findBlockScopeDeclarations(node.body);
for (const statement of node.body) {
traverse(statement);
}
popScope(scope);
return scope;
}
function traverseTryStatement(node) {
if (node.finalizer) {
(0, common_1.visitingNode)(cur, node.finalizer);
return (0, common_1.compileError)(cur, 'Not supported: finally');
}
if (!node.handler) {
// If we supported `finally` then the catch is optional, but a try on its
// own doesn't make sense.
return (0, common_1.compileError)(cur, 'Missing catch clause in try..catch');
}
const tryScope = traverseBlockScope(node.block);
tryScope.isTryScope = true;
traverseCatchBlock(node.handler);
}
function traverseCatchBlock(node) {
const scope = pushBlockScope(node.body, true);
scope.isCatchScope = true;
if (node.param) {
if (node.param.type !== 'Identifier') {
(0, common_1.visitingNode)(cur, node.param);
return (0, common_1.compileError)(cur, 'Only simple binding supported in catch statement');
}
const paramName = node.param.name;
const binding = createBindingAndSelfReference(paramName, 'catch-param', node.param, false);
scope.catchExceptionBinding = binding;
}
// A catch clause seems to define its own scope for `var` declarations
findVarDeclarations(node.body.body);
findBlockScopeDeclarations(node.body.body);
// Iterate through the body to find variable usage
(0, traverse_ast_1.traverseChildren)(cur, node.body, traverse);
popScope(scope);
}
function traverseForStatement(node) {
// The outer block is for the loop variables (e.g. `i`). If these are part
// of a closure scope, this scope is created during the loop
// initialization and given the initial values of the loop variables, and
// then cloned between each loop iteration so that each loop iteration
// "sees" the value of the variables from its iteration.
const sameInstanceCountAsParent = false;
// Create a lexical scope for any variables introduced by the `for`
const scope = pushBlockScope(node, sameInstanceCountAsParent);
if (node.init && node.init.type === 'VariableDeclaration') {
bindLexicalDeclaration(node.init);
}
// Note: this also needs to traverse the `node.init` and `node.update`
(0, traverse_ast_1.traverseChildren)(cur, node, (node, context) => {
if (node.type === 'BlockStatement') {
// The loop body also exists once per loop iteration, so in some sense
// it has the same lifetime as its parent (the loop outer block) but the
// loop outer block is cloned on each iteration while the inner block is
// not, which is why we mark it as different lifetimes. This means that
// the variables declared in the loop body get a fresh TDZ value at the
// beginning of each iteration rather than inheriting the cloned value
// from the previous iteration.
const bodyHasSameInstanceCountAsParent = false;
traverseBlockScope(node, bodyHasSameInstanceCountAsParent);
}
else {
traverse(node, context);
}
});
popScope(scope);
}
function handleAssignmentExpression(node) {
(0, traverse_ast_1.traverseChildren)(cur, node, traverse);
handleMutationToVariable(node.left);
}
function handleUpdateExpression(node) {
(0, traverse_ast_1.traverseChildren)(cur, node, traverse);
handleMutationToVariable(node.argument);
}
function handleAwaitExpression(node) {
(0, traverse_ast_1.traverseChildren)(cur, node, traverse);
const currentFunction = containingFunction(currentScope()) ?? (0, utils_1.unexpected)();
if (currentFunction.type === 'ModuleScope') {
return (0, common_1.compileError)(cur, 'Await expressions are not supported at the top level');
}
currentFunction.awaitExpressions.push(node);
}
function handleMutationToVariable(expr) {
// This is basically to determine which slots need to be mutable. The main
// reason for this is to decide which parameters need to be copied into
// local slots.
if (expr.type === 'Identifier') {
const reference = references.get(expr) ?? (0, utils_1.unexpected)();
const resolvesTo = reference.resolvesTo;
if (resolvesTo.type === 'Binding') {
if (resolvesTo.binding.isDeclaredReadonly) {
(0, common_1.compileError)(cur, `Cannot assign to variable "${reference.name}" because it is declared readonly`);
}
resolvesTo.binding.isWrittenTo = true;
}
}
}
function createVariableReference(node) {
const name = node.type === 'Identifier' ? node.name : '#this';
const binding = node.type === 'Identifier'
? findBinding(name)
: findThisBinding();
if (binding) {
const currentFunction = containingFunction(currentScope());
const bindingFunction = containingFunction(binding.scope);
const isInLocalFunction = bindingFunction === currentFunction;
binding.isUsed = true;
// Note that this includes block-scoped variables for blocks at the root level
const mustBeClosureAllocated = !isInLocalFunction;
if (mustBeClosureAllocated) {
if (!currentFunction)
(0, utils_1.unexpected)();
binding.isAccessedByNestedFunction = true;
const isGlobal = binding.scope.type === 'ModuleScope';
// Note: Global variables can be accessed without a closure scope
if (!isGlobal) {
markClosureChain(currentScope(), binding.scope);
}
}
const reference = {
name: name,
resolvesTo: { type: 'Binding', binding },
isInLocalFunction,
nearestScope: currentScope(),
access: undefined // Will be populated in a later phase
};
references.set(node, reference);
currentScope().references.push(reference);
}
else { // Binding not found
if (node.type === 'ThisExpression') {
// The `this` expression must evaluate to undefined
const reference = {
name: name,
resolvesTo: { type: 'RootLevelThis' },
isInLocalFunction: false,
nearestScope: currentScope(),
access: undefined, // Populated in phase 2
};
references.set(node, reference);
currentScope().references.push(reference);
}
else {
// Free variable reference
const reference = {
name,
isInLocalFunction: false,
nearestScope: currentScope(),
resolvesTo: { type: 'FreeVariable', name },
access: undefined, // Populated in phase 2
};
model.freeVariables.add(name);
references.set(node, reference);
currentScope().references.push(reference);
}
}
}
function findBinding(name) {
// Loop through the scope stack starting from the inner-most and working
// out until we find it
for (let i = scopeStack.length - 1; i >= 0; i--) {
const scope = scopeStack[i];
const binding = scope.bindings[name];
if (binding) {
return binding;
}
}
// If a binding is not found, it's a free variable (a reference to a global)
return undefined;
}
function findThisBinding() {
// Loop through the scope stack starting from the inner-most and working
// out until we find it
for (let i = scopeStack.length - 1; i >= 0; i--) {
const scope = scopeStack[i];
if (scope.thisBinding) {
return scope.thisBinding;
}
}
// If a binding is not found, it's a free variable (a reference to a global)
return undefined;
}
// Mark all the scopes from referencingScope (inclusive) to bindingScope
// (exclusive) as needing to have a reference to their parent (because they
// access their outer scope). Note that "undefined" here refers to the
// module scope. For functions, it also marks them as closures because they
// will need to capture their parent scope at runtime.
function markClosureChain(referencingScope, bindingScope) {
let cursor = referencingScope;
// While we're not at the scope we want to be at
while (cursor !== bindingScope) {
if (!cursor)
(0, utils_1.unexpected)();
cursor.accessesParentScope = true;
if (cursor.type === 'FunctionScope') {
cursor.functionIsClosure = true;
}
cursor = cursor.parent;
}
}
// Returns the innermost function containing or equal to the given scope,
// or undefined if the given scope is not within a function (e.g. it's at
// the model level)
function containingFunction(scope) {
let current = scope;
while (current !== undefined && current.type !== 'FunctionScope' && current.type !== 'ModuleScope')
current = current.parent;
return current;
}
}
/**
* This function looks for var declarations for a variable scope (program- or
* function-level) and creates bindings for them in the current scope.
*
* Note: this function does NOT find exported var declarations (e.g. `export
* var x;`).
*/
function findVarDeclarations(body) {
for (const statement of body) {
traverse(statement);
}
function traverse(node_) {
const node = node_;
switch (node.type) {
case 'ExportNamedDeclaration':
case 'ImportDeclaration':
break; // Handled separately
case 'VariableDeclaration': {
// This function is only looking for hoisted variables
if (node.kind === 'var') {
bindVarDeclaration(node, false);
}
break;
}
case 'FunctionDeclaration':
case 'FunctionExpression':
case 'ClassMethod':
case 'CatchClause': // `var` declarations in catch clauses seem not to be hoisted to the function level
case 'ArrowFunctionExpression':
case 'ClassDeclaration':
case 'ClassExpression':
break;
default:
// We don't want to recurse into nested functions accidentally
if (B.isFunctionNode(node))
(0, utils_1.assertUnreachable)(node);
(0, traverse_ast_1.traverseChildren)(cur, node, traverse);
break;
}
}
}
// This function looks for block-scoped declarations (let, const, and function
// declarations). It does not look recursively because these kinds of
// declarations are not hoisted out of nested blocks.
function findBlockScopeDeclarations(statements) {
for (const statement of statements) {
if (statement.type === 'ExportNamedDeclaration' || statement.type === 'ImportDeclaration')
continue; // Handled separately
(0, common_1.visitingNode)(cur, statement);
if (statement.type === 'VariableDeclaration') {
bindLexicalDeclaration(statement);
}
else if (statement.type === 'FunctionDeclaration') {
// Function declarations are "hoisted" but not to the function scope but
// rather to the top of the block
if (statement.id) {
bindFunctionDeclaration(statement, false);
}
}
else if (statement.type === 'ClassDeclaration') {
bindLexicalDeclaration(statement);
}
}
}
function bindLexicalDeclaration(statement) {
if (statement.type === 'VariableDeclaration' && statement.kind !== 'var') {
(0, utils_1.hardAssert)(statement.kind === 'const' || statement.kind === 'let');
for (const declaration of statement.declarations) {
const id = declaration.id;
if (id.type !== 'Identifier')
return (0, common_1.compileError)(cur, 'Syntax not supported', id);
const name = id.name;
const binding = createBindingAndSelfReference(name, statement.kind, declaration, false);
currentScope().lexicalDeclarations.push(binding);
}
}
else if (statement.type === 'ClassDeclaration') {
const id = statement.id;
const name = id.name;
const binding = createBindingAndSelfReference(name, 'class', statement, false);
currentScope().lexicalDeclarations.push(binding);
}
}
function bindFunctionDeclaration(node, isExported) {
const id = node.id ?? (0, utils_1.unexpected)();
const name = id.name;
const binding = createBindingAndSelfReference(name, 'function', node, isExported);
currentScope().nestedFunctionDeclarations.push({
func: node,
binding,
});
return binding;
}
function bindClassDeclaration(node, isExported) {
const id = node.id ?? (0, utils_1.unexpected)();
const name = id.name;
const binding = createBindingAndSelfReference(name, 'class', node, isExported);
return binding;
}
function findImportsAndExports(program) {
for (const statement of program.body) {
(0, common_1.visitingNode)(cur, statement);
switch (statement.type) {
case 'ExportNamedDeclaration':
bindNamedExports(statement);
break;
case 'ImportDeclaration':
createImportBindings(statement);
break;
}
}
}
function createImportBindings(statement) {
const source = statement.source.value;
const isExported = false;
for (const specifier of statement.specifiers) {
(0, common_1.visitingNode)(cur, specifier);
const localName = specifier.local.name;
const binding = createBindingAndSelfReference(localName, 'import', specifier, isExported);
importBindings.set(binding, { source, specifier });
}
}
function bindNamedExports(statement) {
if (statement.source || statement.specifiers.length) {
return (0, common_1.compileError)(cur, 'Only simple export syntax is supported');
}
const declaration = statement.declaration;
if (!declaration) {
// Older versions of babel didn't seem to allow for a null declaration,
// so I'm thinking maybe it's to support a new language feature. I
// haven't looked into it. (Note: this might be to support `export { x
// as y }` syntax)
return (0, common_1.featureNotSupported)(cur, 'Expected a declaration');
}
const isExported = true;
if (declaration.type === 'VariableDeclaration') {
bindVarDeclaration(declaration, isExported);
}
else if (declaration.type === 'FunctionDeclaration') {
bindFunctionDeclaration(declaration, isExported);
}
else if (declaration.type === 'ClassDeclaration') {
bindClassDeclaration(declaration, isExported);
}
else {
return (0, common_1.compileError)(cur, `Not supported: export of ${declaration.type}`);
}
}
function bindVarDeclaration(decl, isExported) {
for (const node of decl.declarations) {
if (node.id.type !== 'Identifier') {
return (0, common_1.compileError)(cur, 'Only simple variable declarations are supported.');
}
const name = node.id.name;
if (!(0, utils_1.isNameString)(name)) {
return (0, common_1.compileError)(cur, `Invalid variable identifier: "${name}"`);
}
const scope = currentScope();
const existingBinding = scope.varDeclarations.find(v => v.name === name);
if (existingBinding) {
// Duplicate var declarations of the same name are allowed but they
// point to the same variable.
const selfReferenceNode = getDeclarationSelfReference(node);
if (selfReferenceNode) {
const ref = {
name: name,
isInLocalFunction: true,
nearestScope: currentScope(),
resolvesTo: { type: 'Binding', binding: existingBinding },
access: undefined // Will be populated in a later phase
};
references.set(selfReferenceNode, ref);
}
}
else {
const binding = createBindingAndSelfReference(name, 'var', node, isExported);
scope.varDeclarations.push(binding);
}
}
}
function pushModuleScope(node) {
// Top-level-await (not really supported yet)
const isAsyncFunction = node.body.some(n => containsAwait(cur, n));
const scope = {
type: 'ModuleScope',
node,
bindings: Object.create(null),
children: [],
references: [],
parent: undefined,
ilFunctionId: (0, utils_1.uniqueNameInSet)('moduleEntry', ilFunctionNames),
prologue: [],
epilogue: [],
lexicalDeclarations: [],
nestedFunctionDeclarations: [],
varDeclarations: [],
parameterBindings: [],
embeddingCandidates: [],
functionIsClosure: false,
sameInstanceCountAsParent: false,
isAsyncFunction,
awaitExpressions: [],
};
scopes.set(node, scope);
pushScope(scope);
return scope;
}
function pushFunctionScope(node, hasThisBinding, isAsync, className) {
const scope = createFunctionScope(node, hasThisBinding, isAsync, className);
model.functions.push(scope);
scopes.set(node, scope);
pushScope(scope);
return scope;
}
function createFunctionScope(node, hasThisBinding, isAsyncFunction, className) {
const name = node ?
node.type === 'FunctionDeclaration' ? node.id?.name :
node.type === 'ClassMethod' ?
!node.computed && node.key.type === 'Identifier' ? `${className}_${node.key.name}` :
`${className}_method` :
undefined :
className ? className :
undefined;
if (name && !(0, utils_1.isNameString)(name)) {
return (0, common_1.compileError)(cur, `Invalid function identifier: "${name}`);
}
const ilFunctionId = (0, utils_1.uniqueNameInSet)(name ?? 'anonymous', ilFunctionNames);
const scope = {
type: 'FunctionScope',
...createBaseScope(node, false, isAsyncFunction),
node,
ilFunctionId,
funcName: name,
// Assume the function is not a closure until we find a free variable
// that references the outer scope
functionIsClosure: false
};
if (hasThisBinding) {
scope.thisBinding = createBinding('#this', 'this', undefined, false, scope);
}
return scope;
}
function pushClassScope(node) {
const name = node.type === 'ClassDeclaration' ? node.id?.name : undefined;
if (name && !(0, utils_1.isNameString)(name)) {
return (0, common_1.compileError)(cur, `Invalid class identifier: "${name}`);
}
const scope = {
type: 'ClassScope',
...createBaseScope(node, true, false),
className: name,
// These will be populated later
physicalConstructorScope: undefined,
staticConstructorScope: undefined,
virtualConstructorScope: undefined,
};
scopes.set(node, scope);
pushScope(scope);
return scope;
}
function pushBlockScope(node, sameInstanceCountAsParent) {
const scope = createBlockScope(node, sameInstanceCountAsParent);
scopes.set(node, scope);
pushScope(scope);
return scope;
}
function createBlockScope(node, sameInstanceCountAsParent) {
return {
type: 'BlockScope',
...createBaseScope(node, sameInstanceCountAsParent, false)
};
}
function createBaseScope(node, sameInstanceCountAsParent, isAsyncFunction) {
return {
node,
bindings: Object.create(null),
children: [],
references: [],
parent: currentScope(),
prologue: [],
epilogue: [],
sameInstanceCountAsParent,
lexicalDeclarations: [],
varDeclarations: [],
embeddingCandidates: [],
// Note: parameter bindings at the block level are used by
parameterBindings: [],
nestedFunctionDeclarations: [],
closureSlots: undefined,
isAsyncFunction,
awaitExpressions: [],
};
}
function pushScope(scope) {
const parent = scopeStack[scopeStack.length - 1]; // Can be undefined
parent && parent.children.push(scope);
scopeStack.push(scope);
}
function popScope(scope) {
(0, utils_1.hardAssert)(scopeStack[scopeStack.length - 1] === scope);
scopeStack.pop();
}
function createParameterBindings(scope, params) {
for (const param of params) {
if (param.type !== 'Identifier') {
return (0, common_1.featureNotSupported)(cur, 'Only simple parameters supported');
}
const binding = createBindingAndSelfReference(param.name, 'param', param, false);
scope.parameterBindings.push(binding);
}
}
function createBindingAndSelfReference(name, kind, node, isExported) {
const binding = createBinding(name, kind, node, isExported, currentScope());
const selfReferenceNode = getDeclarationSelfReference(node);
if (selfReferenceNode) {
const ref = {
name: name,
isInLocalFunction: true,
nearestScope: currentScope(),
resolvesTo: { type: 'Binding', binding },
access: undefined // Will be populated in a later phase
};
references.set(selfReferenceNode, ref);
binding.selfReference = ref;
}
return binding;
}
function getDeclarationSelfReference(node) {
switch (node.type) {
case 'FunctionDeclaration': return node.id ?? undefined;
case 'ClassDeclaration': return node.id ?? undefined;
case 'Identifier': return node;
case 'VariableDeclarator':
return node.id.type === 'Identifier'
? node.id
: undefined;
case 'ImportDefaultSpecifier': return node.local;
case 'ImportSpecifier': return node.local;
case 'ImportNamespaceSpecifier': return node.local;
default:
return (0, utils_1.assertUnreachable)(node);
}
}
function createBinding(name, kind, node, isExported, scope) {
const readonly = kind === 'const';
const scopeBindings = scope.bindings;
const isLexical = kind === 'let' || kind === 'const';
if (isLexical && name in scopeBindings) {
return (0, common_1.compileError)(cur, `Variable "${name}" already declared in scope`);
}
const binding = {
kind,
name,
// We do slot assignment in a separate pass
slot: undefined,
scope,
node,
isExported,
selfReference: undefined,
isDeclaredReadonly: readonly,
// Assume by default that the variable is not written to,
isWrittenTo: false,
// Assuming not closure allocated until we detect otherwise
isAccessedByNestedFunction: false,
isUsed: false,
};
scopeBindings[name] = binding;
binding.node && bindings.set(binding.node, binding);
isExported && model.exportedBindings.push(binding);
return binding;
}
function registerWithEmbeddingLocation(cur, func) {
// See [Closure Embedding](../../../doc/internals/closure-embedding.md)
// Move to the declaring scope of the function, not the function scope itself
let embeddingScope = func.parent;
// Find the outer-most scope that is still the same "lifetime" as the
// function declaration.
while (embeddingScope && embeddingScope.sameInstanceCountAsParent) {
embeddingScope = embeddingScope.parent;
}
// Note: `undefined` means we've reached the top-level module scope.
if (embeddingScope) {
// In this pass, we don't yet know if the func is a closure, so we don't
// know if it should be embedded or not, but we add it to a list of
// possible functions that could be embedded, and then later find the
// first closure in this list to embed.
embeddingScope.embeddingCandidates.push(func);
}
}
}
exports.pass1_findScopesAndBindings = pass1_findScopesAndBindings;
function checkNoThis(cur, node, context) {
inner(node);
function inner(node) {
if (node.type === 'ThisExpression') {
(0, common_1.featureNotSupported)(cur, `Using \`this\` inside ${context}`);
}
(0, traverse_ast_1.traverseChildren)(cur, node, inner);
}
}
// Checks for `await` expressions in a given statement or expression, ignoring
// the body of nested functions.
function containsAwait(cur, node) {
let containsAwait = false;
inner(node);
return containsAwait;
function inner(node) {
if (node.type === 'FunctionDeclaration' ||
node.type === 'FunctionExpression' ||
node.type === 'ArrowFunctionExpression' ||
node.type === 'ClassMethod') {
/* Do not traverse into nested functions */
}
else if (node.type === 'AwaitExpression') {
containsAwait = true;
}
else {
(0, traverse_ast_1.traverseChildren)(cur, node, inner);
}
}
}
//# sourceMappingURL=pass-1-find-scopes-and-bindings.js.map