js-slang
Version:
Javascript-based implementations of Source, written in Typescript
896 lines • 34.1 kB
JavaScript
Object.defineProperty(exports, "__esModule", { value: true });
exports.isEnvDependent = exports.hasContinueStatement = exports.hasContinueStatementIf = exports.hasBreakStatement = exports.hasBreakStatementIf = exports.hasReturnStatement = exports.hasReturnStatementIf = exports.checkStackOverFlow = exports.checkNumberOfArguments = exports.handleRuntimeError = exports.setVariable = exports.getVariable = exports.defineVariable = exports.hasImportDeclarations = exports.hasDeclarations = exports.declareFunctionsAndVariables = exports.declareIdentifier = exports.createProgramEnvironment = exports.createBlockEnvironment = exports.pushEnvironment = exports.popEnvironment = exports.createEnvironment = exports.currentEnvironment = exports.setTransformers = exports.currentTransformers = exports.isSimpleFunction = exports.envChanging = exports.valueProducing = exports.reduceConditional = exports.handleSequence = exports.handleArrayCreation = exports.isStreamFn = exports.isEnvArray = exports.uniqueId = exports.isRestElement = exports.isStatementSequence = exports.isBlockStatement = exports.isIfStatement = exports.isReturnStatement = exports.isIdentifier = exports.isNode = exports.isInstr = exports.isSchemeValue = void 0;
const lodash_1 = require("lodash");
const errors = require("../errors/errors");
const types_1 = require("../types");
const ast = require("../utils/ast/astCreator");
const base_1 = require("../alt-langs/scheme/scm-slang/src/stdlib/base");
const core_math_1 = require("../alt-langs/scheme/scm-slang/src/stdlib/core-math");
const heap_1 = require("./heap");
const instr = require("./instrCreator");
const types_2 = require("./types");
const closure_1 = require("./closure");
const continuations_1 = require("./continuations");
const scheme_macros_1 = require("./scheme-macros");
/**
* Typeguard for commands to check if they are scheme values.
*
* @param command A ControlItem
* @returns true if the ControlItem is a scheme value, false otherwise.
*/
const isSchemeValue = (command) => {
return (command === null ||
typeof command === 'string' ||
typeof command === 'boolean' ||
(0, lodash_1.isArray)(command) ||
command instanceof base_1._Symbol ||
(0, core_math_1.is_number)(command));
};
exports.isSchemeValue = isSchemeValue;
/**
* Typeguard for Instr to distinguish between program statements and instructions.
*
* @param command A ControlItem
* @returns true if the ControlItem is an instruction and false otherwise.
*/
const isInstr = (command) => {
// this prevents us from reading properties of null
if ((0, exports.isSchemeValue)(command)) {
return false;
}
return command.instrType !== undefined;
};
exports.isInstr = isInstr;
/**
* Typeguard for Node to distinguish between program statements and instructions.
*
* @param command A ControlItem
* @returns true if the ControlItem is a Node or StatementSequence, false if it is an instruction.
*/
const isNode = (command) => {
// this prevents us from reading properties of null
if ((0, exports.isSchemeValue)(command)) {
return false;
}
return command.type !== undefined;
};
exports.isNode = isNode;
/**
* Typeguard for esIdentifier. To verify if a Node is an esIdentifier.
*
* @param node a Node
* @returns true if node is an esIdentifier, false otherwise.
*/
const isIdentifier = (node) => {
return node.name !== undefined;
};
exports.isIdentifier = isIdentifier;
/**
* Typeguard for esReturnStatement. To verify if a Node is an esReturnStatement.
*
* @param node a Node
* @returns true if node is an esReturnStatement, false otherwise.
*/
const isReturnStatement = (node) => {
return node.type == 'ReturnStatement';
};
exports.isReturnStatement = isReturnStatement;
/**
* Typeguard for esIfStatement. To verify if a Node is an esIfStatement.
*
* @param node a Node
* @returns true if node is an esIfStatement, false otherwise.
*/
const isIfStatement = (node) => {
return node.type == 'IfStatement';
};
exports.isIfStatement = isIfStatement;
/**
* Typeguard for esBlockStatement. To verify if a Node is a block statement.
*
* @param node a Node
* @returns true if node is an esBlockStatement, false otherwise.
*/
const isBlockStatement = (node) => {
return node.type == 'BlockStatement';
};
exports.isBlockStatement = isBlockStatement;
/**
* Typeguard for StatementSequence. To verify if a ControlItem is a statement sequence.
*
* @param node a ControlItem
* @returns true if node is a StatementSequence, false otherwise.
*/
const isStatementSequence = (node) => {
return node.type == 'StatementSequence';
};
exports.isStatementSequence = isStatementSequence;
/**
* Typeguard for esRestElement. To verify if a Node is a block statement.
*
* @param node a Node
* @returns true if node is an esRestElement, false otherwise.
*/
const isRestElement = (node) => {
return node.type == 'RestElement';
};
exports.isRestElement = isRestElement;
/**
* Generate a unique id, for use in environments, arrays and closures.
*
* @param context the context used to provide the new unique id
* @returns a unique id
*/
const uniqueId = (context) => {
return `${context.runtime.objectCount++}`;
};
exports.uniqueId = uniqueId;
/**
* Returns whether `item` is an array with `id` and `environment` properties already attached.
*/
const isEnvArray = (item) => {
return ((0, lodash_1.isArray)(item) &&
{}.hasOwnProperty.call(item, 'id') &&
{}.hasOwnProperty.call(item, 'environment'));
};
exports.isEnvArray = isEnvArray;
/**
* Returns whether `item` is a non-closure function that returns a stream.
* If the function has been called already and we have the result, we can
* pass it in here as a 2nd argument for stronger checking
*/
const isStreamFn = (item, result) => {
if (result == null || !(0, lodash_1.isArray)(result) || result.length !== 2)
return false;
return ((0, lodash_1.isFunction)(item) &&
!(item instanceof closure_1.default) &&
(item.name === 'stream' || {}.hasOwnProperty.call(item, 'environment')));
};
exports.isStreamFn = isStreamFn;
/**
* Adds the properties `id` and `environment` to the given array, and adds the array to the
* current environment's heap. Adds the array to the heap of `envOverride` instead if it's defined.
*
* @param context the context used to provide the current environment and new unique id
* @param array the array to attach properties to, and for addition to the heap
*/
const handleArrayCreation = (context, array, envOverride) => {
const environment = envOverride ?? (0, exports.currentEnvironment)(context);
// Both id and environment are non-enumerable so iterating
// through the array will not return these values
Object.defineProperties(array, {
id: { value: (0, exports.uniqueId)(context) },
// Make environment writable as there are cases on the frontend where
// environments of objects need to be modified
environment: { value: environment, writable: true }
});
environment.heap.add(array);
};
exports.handleArrayCreation = handleArrayCreation;
/**
* A helper function for handling sequences of statements.
* Statements must be pushed in reverse order, and each statement is separated by a pop
* instruction so that only the result of the last statement remains on stash.
* Value producing statements have an extra pop instruction.
*
* @param seq Array of statements.
* @returns Array of commands to be pushed into control.
*/
const handleSequence = (seq) => {
const result = [];
let valueProduced = false;
for (const command of seq) {
if (!isImportDeclaration(command)) {
if ((0, exports.valueProducing)(command)) {
// Value producing statements have an extra pop instruction
if (valueProduced) {
result.push(instr.popInstr(command));
}
else {
valueProduced = true;
}
}
result.push(command);
}
}
// Push statements in reverse order
return result.reverse();
};
exports.handleSequence = handleSequence;
/**
* This function is used for ConditionalExpressions and IfStatements, to create the sequence
* of control items to be added.
*/
const reduceConditional = (node) => {
return [instr.branchInstr(node.consequent, node.alternate, node), node.test];
};
exports.reduceConditional = reduceConditional;
/**
* To determine if a control item is value producing. JavaScript distinguishes value producing
* statements and non-value producing statements.
* Refer to https://sourceacademy.nus.edu.sg/sicpjs/4.1.2 exercise 4.8.
*
* @param command Control item to determine if it is value producing.
* @returns true if it is value producing, false otherwise.
*/
const valueProducing = (command) => {
const type = command.type;
return (type !== 'VariableDeclaration' &&
type !== 'FunctionDeclaration' &&
type !== 'ContinueStatement' &&
type !== 'BreakStatement' &&
type !== 'DebuggerStatement' &&
(type !== 'BlockStatement' || command.body.some(exports.valueProducing)));
};
exports.valueProducing = valueProducing;
/**
* To determine if a control item changes the environment.
* There is a change in the environment when
* 1. pushEnvironment() is called when creating a new frame, if there are variable declarations.
* Called in Program, BlockStatement, and Application instructions.
* 2. there is an assignment.
* Called in Assignment and Array Assignment instructions.
* 3. a new object is created.
* Called in ExpressionStatement that contains an ArrowFunctionExpression, or an ArrowFunctionExpression
*
* @param command Control item to check against.
* @returns true if it changes the environment, false otherwise.
*/
const envChanging = (command) => {
if ((0, exports.isNode)(command)) {
const type = command.type;
return (type === 'Program' ||
type === 'BlockStatement' ||
type === 'ArrowFunctionExpression' ||
(type === 'ExpressionStatement' && command.expression.type === 'ArrowFunctionExpression'));
}
else if ((0, exports.isInstr)(command)) {
const type = command.instrType;
return (type === types_2.InstrType.ENVIRONMENT ||
type === types_2.InstrType.ARRAY_LITERAL ||
type === types_2.InstrType.ASSIGNMENT ||
type === types_2.InstrType.ARRAY_ASSIGNMENT ||
(type === types_2.InstrType.APPLICATION && command.numOfArgs > 0));
}
else {
// TODO deal with scheme control items
// for now, as per the CSE machine paper,
// we decide to ignore environment optimizations
// for scheme control items :P
return true;
}
};
exports.envChanging = envChanging;
/**
* To determine if the function is simple.
* Simple functions contain a single return statement.
*
* @param node The function to check against.
* @returns true if the function is simple, false otherwise.
*/
const isSimpleFunction = (node) => {
if (node.body.type !== 'BlockStatement' && node.body.type !== 'StatementSequence') {
return true;
}
else {
const block = node.body;
return block.body.length === 1 && block.body[0].type === 'ReturnStatement';
}
};
exports.isSimpleFunction = isSimpleFunction;
/**
* Transformers
*/
const currentTransformers = (context) => context.runtime.transformers;
exports.currentTransformers = currentTransformers;
const setTransformers = (context, transformers) => {
context.runtime.transformers = transformers;
};
exports.setTransformers = setTransformers;
/**
* Environments
*/
const currentEnvironment = (context) => context.runtime.environments[0];
exports.currentEnvironment = currentEnvironment;
const createEnvironment = (context, closure, args, callExpression) => {
const environment = {
name: (0, exports.isIdentifier)(callExpression.callee)
? callExpression.callee.name
: (closure.declaredName ?? closure.functionName),
tail: closure.environment,
head: {},
heap: new heap_1.default(),
id: (0, exports.uniqueId)(context),
callExpression: {
...callExpression,
arguments: args.map(ast.primitive)
}
};
closure.node.params.forEach((param, index) => {
if ((0, exports.isRestElement)(param)) {
const array = args.slice(index);
(0, exports.handleArrayCreation)(context, array, environment);
environment.head[param.argument.name] = array;
}
else {
environment.head[param.name] = args[index];
}
});
return environment;
};
exports.createEnvironment = createEnvironment;
const popEnvironment = (context) => context.runtime.environments.shift();
exports.popEnvironment = popEnvironment;
const pushEnvironment = (context, environment) => {
context.runtime.environments.unshift(environment);
context.runtime.environmentTree.insert(environment);
};
exports.pushEnvironment = pushEnvironment;
const createBlockEnvironment = (context, name = 'blockEnvironment') => {
return {
name,
tail: (0, exports.currentEnvironment)(context),
head: {},
heap: new heap_1.default(),
id: (0, exports.uniqueId)(context)
};
};
exports.createBlockEnvironment = createBlockEnvironment;
const createProgramEnvironment = (context, isPrelude) => {
return (0, exports.createBlockEnvironment)(context, isPrelude ? 'prelude' : 'programEnvironment');
};
exports.createProgramEnvironment = createProgramEnvironment;
/**
* Variables
*/
const UNASSIGNED_CONST = Symbol('const declaration');
const UNASSIGNED_LET = Symbol('let declaration');
function declareIdentifier(context, name, node, environment, constant = false) {
if (environment.head.hasOwnProperty(name)) {
const descriptors = Object.getOwnPropertyDescriptors(environment.head);
return (0, exports.handleRuntimeError)(context, new errors.VariableRedeclaration(node, name, descriptors[name].writable));
}
environment.head[name] = constant ? UNASSIGNED_CONST : UNASSIGNED_LET;
return environment;
}
exports.declareIdentifier = declareIdentifier;
function declareVariables(context, node, environment) {
for (const declaration of node.declarations) {
// Retrieve declaration type from node
const constant = node.kind === 'const';
declareIdentifier(context, declaration.id.name, node, environment, constant);
}
}
function declareFunctionsAndVariables(context, node, environment) {
for (const statement of node.body) {
switch (statement.type) {
case 'VariableDeclaration':
declareVariables(context, statement, environment);
break;
case 'FunctionDeclaration':
// FunctionDeclaration is always of type constant
declareIdentifier(context, statement.id.name, statement, environment, true);
break;
}
}
}
exports.declareFunctionsAndVariables = declareFunctionsAndVariables;
function hasDeclarations(node) {
for (const statement of node.body) {
if (statement.type === 'VariableDeclaration' || statement.type === 'FunctionDeclaration') {
return true;
}
}
return false;
}
exports.hasDeclarations = hasDeclarations;
function hasImportDeclarations(node) {
for (const statement of node.body) {
if (statement.type === 'ImportDeclaration') {
return true;
}
}
return false;
}
exports.hasImportDeclarations = hasImportDeclarations;
function isImportDeclaration(node) {
return node.type === 'ImportDeclaration';
}
function defineVariable(context, name, value, constant = false, node) {
const environment = (0, exports.currentEnvironment)(context);
// we disable this check for full scheme due to the inability to scan for variables before usage
if (environment.head[name] !== UNASSIGNED_CONST &&
environment.head[name] !== UNASSIGNED_LET &&
context.chapter !== types_1.Chapter.FULL_SCHEME) {
return (0, exports.handleRuntimeError)(context, new errors.VariableRedeclaration(node, name, !constant));
}
if (constant && value instanceof closure_1.default) {
value.declaredName = name;
}
Object.defineProperty(environment.head, name, {
value,
writable: !constant,
enumerable: true
});
return environment;
}
exports.defineVariable = defineVariable;
const getVariable = (context, name, node) => {
let environment = (0, exports.currentEnvironment)(context);
while (environment) {
if (environment.head.hasOwnProperty(name)) {
if (environment.head[name] === UNASSIGNED_CONST ||
environment.head[name] === UNASSIGNED_LET) {
return (0, exports.handleRuntimeError)(context, new errors.UnassignedVariable(name, node));
}
else {
return environment.head[name];
}
}
else {
environment = environment.tail;
}
}
return (0, exports.handleRuntimeError)(context, new errors.UndefinedVariable(name, node));
};
exports.getVariable = getVariable;
const setVariable = (context, name, value, node) => {
let environment = (0, exports.currentEnvironment)(context);
while (environment) {
if (environment.head.hasOwnProperty(name)) {
if (environment.head[name] === UNASSIGNED_CONST ||
environment.head[name] === UNASSIGNED_LET) {
break;
}
const descriptors = Object.getOwnPropertyDescriptors(environment.head);
if (descriptors[name].writable) {
environment.head[name] = value;
return undefined;
}
return (0, exports.handleRuntimeError)(context, new errors.ConstAssignment(node, name));
}
else {
environment = environment.tail;
}
}
return (0, exports.handleRuntimeError)(context, new errors.UndefinedVariable(name, node));
};
exports.setVariable = setVariable;
const handleRuntimeError = (context, error) => {
context.errors.push(error);
throw error;
};
exports.handleRuntimeError = handleRuntimeError;
const checkNumberOfArguments = (context, callee, args, exp) => {
if (callee instanceof closure_1.default) {
// User-defined or Pre-defined functions
const params = callee.node.params;
const hasVarArgs = params[params.length - 1]?.type === 'RestElement';
if (hasVarArgs ? params.length - 1 > args.length : params.length !== args.length) {
return (0, exports.handleRuntimeError)(context, new errors.InvalidNumberOfArguments(exp, hasVarArgs ? params.length - 1 : params.length, args.length, hasVarArgs));
}
}
else if ((0, continuations_1.isCallWithCurrentContinuation)(callee)) {
// call/cc should have a single argument
if (args.length !== 1) {
return (0, exports.handleRuntimeError)(context, new errors.InvalidNumberOfArguments(exp, 1, args.length, false));
}
return undefined;
}
else if ((0, scheme_macros_1.isEval)(callee)) {
// eval should have a single argument
if (args.length !== 1) {
return (0, exports.handleRuntimeError)(context, new errors.InvalidNumberOfArguments(exp, 1, args.length, false));
}
return undefined;
}
else if ((0, scheme_macros_1.isApply)(callee)) {
// apply should have at least two arguments
if (args.length < 2) {
return (0, exports.handleRuntimeError)(context, new errors.InvalidNumberOfArguments(exp, 2, args.length, false));
}
return undefined;
}
else if (callee instanceof continuations_1.Continuation) {
// Continuations have variadic arguments,
// and so we can let it pass
// TODO: in future, if we can somehow check the number of arguments
// expected by the continuation, we can add a check here.
return undefined;
}
else {
// Pre-built functions
const hasVarArgs = callee.minArgsNeeded != undefined;
if (hasVarArgs ? callee.minArgsNeeded > args.length : callee.length !== args.length) {
return (0, exports.handleRuntimeError)(context, new errors.InvalidNumberOfArguments(exp, hasVarArgs ? callee.minArgsNeeded : callee.length, args.length, hasVarArgs));
}
}
return undefined;
};
exports.checkNumberOfArguments = checkNumberOfArguments;
/**
* This function can be used to check for a stack overflow.
* The current limit is set to be a control size of 1.0 x 10^5, if the control
* flows beyond this limit an error is thrown.
* This corresponds to about 10mb of space according to tests ran.
*/
const checkStackOverFlow = (context, control) => {
if (control.size() > 100000) {
const stacks = [];
let counter = 0;
for (let i = 0; counter < errors.MaximumStackLimitExceeded.MAX_CALLS_TO_SHOW &&
i < context.runtime.environments.length; i++) {
if (context.runtime.environments[i].callExpression) {
stacks.unshift(context.runtime.environments[i].callExpression);
counter++;
}
}
(0, exports.handleRuntimeError)(context, new errors.MaximumStackLimitExceeded(context.runtime.nodes[0], stacks));
}
};
exports.checkStackOverFlow = checkStackOverFlow;
/**
* Checks whether an `if` statement returns in every possible branch.
* @param body The `if` statement to be checked
* @return `true` if every branch has a return statement, else `false`.
*/
const hasReturnStatementIf = (statement) => {
let hasReturn = true;
// Parser enforces that if/else have braces (block statement)
hasReturn = hasReturn && (0, exports.hasReturnStatement)(statement.consequent);
if (statement.alternate) {
if ((0, exports.isIfStatement)(statement.alternate)) {
hasReturn = hasReturn && (0, exports.hasReturnStatementIf)(statement.alternate);
}
else if ((0, exports.isBlockStatement)(statement.alternate) || (0, exports.isStatementSequence)(statement.alternate)) {
hasReturn = hasReturn && (0, exports.hasReturnStatement)(statement.alternate);
}
}
return hasReturn;
};
exports.hasReturnStatementIf = hasReturnStatementIf;
/**
* Checks whether a block returns in every possible branch.
* @param body The block to be checked
* @return `true` if every branch has a return statement, else `false`.
*/
const hasReturnStatement = (block) => {
let hasReturn = false;
for (const statement of block.body) {
if ((0, exports.isReturnStatement)(statement)) {
hasReturn = true;
}
else if ((0, exports.isIfStatement)(statement)) {
// Parser enforces that if/else have braces (block statement)
hasReturn = hasReturn || (0, exports.hasReturnStatementIf)(statement);
}
else if ((0, exports.isBlockStatement)(statement) || (0, exports.isStatementSequence)(statement)) {
hasReturn = hasReturn && (0, exports.hasReturnStatement)(statement);
}
}
return hasReturn;
};
exports.hasReturnStatement = hasReturnStatement;
const hasBreakStatementIf = (statement) => {
let hasBreak = false;
// Parser enforces that if/else have braces (block statement)
hasBreak = hasBreak || (0, exports.hasBreakStatement)(statement.consequent);
if (statement.alternate) {
if ((0, exports.isIfStatement)(statement.alternate)) {
hasBreak = hasBreak || (0, exports.hasBreakStatementIf)(statement.alternate);
}
else if ((0, exports.isBlockStatement)(statement.alternate) || (0, exports.isStatementSequence)(statement.alternate)) {
hasBreak = hasBreak || (0, exports.hasBreakStatement)(statement.alternate);
}
}
return hasBreak;
};
exports.hasBreakStatementIf = hasBreakStatementIf;
/**
* Checks whether a block OR any of its child blocks has a `break` statement.
* @param body The block to be checked
* @return `true` if there is a `break` statement, else `false`.
*/
const hasBreakStatement = (block) => {
let hasBreak = false;
for (const statement of block.body) {
if (statement.type === 'BreakStatement') {
hasBreak = true;
}
else if ((0, exports.isIfStatement)(statement)) {
// Parser enforces that if/else have braces (block statement)
hasBreak = hasBreak || (0, exports.hasBreakStatementIf)(statement);
}
else if ((0, exports.isBlockStatement)(statement) || (0, exports.isStatementSequence)(statement)) {
hasBreak = hasBreak || (0, exports.hasBreakStatement)(statement);
}
}
return hasBreak;
};
exports.hasBreakStatement = hasBreakStatement;
const hasContinueStatementIf = (statement) => {
let hasContinue = false;
// Parser enforces that if/else have braces (block statement)
hasContinue = hasContinue || (0, exports.hasContinueStatement)(statement.consequent);
if (statement.alternate) {
if ((0, exports.isIfStatement)(statement.alternate)) {
hasContinue = hasContinue || (0, exports.hasContinueStatementIf)(statement.alternate);
}
else if ((0, exports.isBlockStatement)(statement.alternate) || (0, exports.isStatementSequence)(statement.alternate)) {
hasContinue = hasContinue || (0, exports.hasContinueStatement)(statement.alternate);
}
}
return hasContinue;
};
exports.hasContinueStatementIf = hasContinueStatementIf;
/**
* Checks whether a block OR any of its child blocks has a `continue` statement.
* @param body The block to be checked
* @return `true` if there is a `continue` statement, else `false`.
*/
const hasContinueStatement = (block) => {
let hasContinue = false;
for (const statement of block.body) {
if (statement.type === 'ContinueStatement') {
hasContinue = true;
}
else if ((0, exports.isIfStatement)(statement)) {
// Parser enforces that if/else have braces (block statement)
hasContinue = hasContinue || (0, exports.hasContinueStatementIf)(statement);
}
else if ((0, exports.isBlockStatement)(statement) || (0, exports.isStatementSequence)(statement)) {
hasContinue = hasContinue || (0, exports.hasContinueStatement)(statement);
}
}
return hasContinue;
};
exports.hasContinueStatement = hasContinueStatement;
const setToTrue = (item) => {
item.isEnvDependent = true;
return item;
};
const setToFalse = (item) => {
item.isEnvDependent = false;
return item;
};
const propertySetter = new Map([
[
'Program',
(node) => {
node = node;
node.isEnvDependent = node.body.some(elem => isEnvDependent(elem));
return node;
}
],
['Literal', setToFalse],
['ImportDeclaration', setToFalse],
['BreakStatement', setToFalse],
['ContinueStatement', setToFalse],
['DebuggerStatement', setToFalse],
['VariableDeclaration', setToTrue],
['FunctionDeclaration', setToTrue],
['ArrowFunctionExpression', setToTrue],
['Identifier', setToTrue],
[
'LogicalExpression',
(node) => {
node = node;
node.isEnvDependent = isEnvDependent(node.left) || isEnvDependent(node.right);
return node;
}
],
[
'BinaryExpression',
(node) => {
node = node;
node.isEnvDependent = isEnvDependent(node.left) || isEnvDependent(node.right);
return node;
}
],
[
'UnaryExpression',
(node) => {
node = node;
node.isEnvDependent = isEnvDependent(node.argument);
return node;
}
],
[
'ConditionalExpression',
(node) => {
node = node;
node.isEnvDependent =
isEnvDependent(node.consequent) ||
isEnvDependent(node.alternate) ||
isEnvDependent(node.test);
return node;
}
],
[
'MemberExpression',
(node) => {
node = node;
node.isEnvDependent = isEnvDependent(node.property) || isEnvDependent(node.object);
return node;
}
],
[
'ArrayExpression',
(node) => {
node = node;
node.isEnvDependent = node.elements.some(elem => isEnvDependent(elem));
return node;
}
],
[
'AssignmentExpression',
(node) => {
node = node;
node.isEnvDependent = isEnvDependent(node.left) || isEnvDependent(node.right);
return node;
}
],
[
'ReturnStatement',
(node) => {
node = node;
node.isEnvDependent = isEnvDependent(node.argument);
return node;
}
],
[
'CallExpression',
(node) => {
node = node;
node.isEnvDependent =
isEnvDependent(node.callee) || node.arguments.some(arg => isEnvDependent(arg));
return node;
}
],
[
'ExpressionStatement',
(node) => {
node = node;
node.isEnvDependent = isEnvDependent(node.expression);
return node;
}
],
[
'IfStatement',
(node) => {
node = node;
node.isEnvDependent =
isEnvDependent(node.test) ||
isEnvDependent(node.consequent) ||
isEnvDependent(node.alternate);
return node;
}
],
[
'ForStatement',
(node) => {
node = node;
node.isEnvDependent =
isEnvDependent(node.body) ||
isEnvDependent(node.init) ||
isEnvDependent(node.test) ||
isEnvDependent(node.update);
return node;
}
],
[
'WhileStatement',
(node) => {
node = node;
node.isEnvDependent = isEnvDependent(node.body) || isEnvDependent(node.test);
return node;
}
],
[
'BlockStatement',
(node) => {
node = node;
node.isEnvDependent = node.body.some(stm => isEnvDependent(stm));
return node;
}
],
[
'StatementSequence',
(node) => {
node = node;
node.isEnvDependent = node.body.some(stm => isEnvDependent(stm));
return node;
}
],
[
'ImportDeclaration',
(node) => {
node = node;
node.isEnvDependent = node.specifiers.some(x => isEnvDependent(x));
return node;
}
],
['ImportSpecifier', setToTrue],
['ImportDefaultSpecifier', setToTrue],
//Instruction
[types_2.InstrType.RESET, setToFalse],
[types_2.InstrType.UNARY_OP, setToFalse],
[types_2.InstrType.BINARY_OP, setToFalse],
[types_2.InstrType.POP, setToFalse],
[types_2.InstrType.ARRAY_ACCESS, setToFalse],
[types_2.InstrType.ARRAY_ASSIGNMENT, setToFalse],
[types_2.InstrType.CONTINUE, setToFalse],
[types_2.InstrType.CONTINUE_MARKER, setToFalse],
[types_2.InstrType.BREAK_MARKER, setToFalse],
[types_2.InstrType.MARKER, setToFalse],
[types_2.InstrType.ENVIRONMENT, setToFalse],
[types_2.InstrType.APPLICATION, setToTrue],
[types_2.InstrType.ASSIGNMENT, setToTrue],
[types_2.InstrType.ARRAY_LITERAL, setToTrue],
[types_2.InstrType.SPREAD, setToFalse],
[
types_2.InstrType.WHILE,
(instr) => {
instr.isEnvDependent = isEnvDependent(instr.test) || isEnvDependent(instr.body);
return instr;
}
],
[
types_2.InstrType.FOR,
(instr) => {
instr.isEnvDependent =
isEnvDependent(instr.init) ||
isEnvDependent(instr.test) ||
isEnvDependent(instr.update) ||
isEnvDependent(instr.body);
return instr;
}
],
[
types_2.InstrType.BRANCH,
(instr) => {
instr.isEnvDependent = isEnvDependent(instr.consequent) || isEnvDependent(instr.alternate);
return instr;
}
]
]);
/**
* Checks whether the evaluation of the given control item depends on the current environment.
* The item is also considered environment dependent if its evaluation introduces
* environment dependent items
* @param item The control item to be checked
* @return `true` if the item is environment depedent, else `false`.
*/
function isEnvDependent(item) {
if (item === null || item === undefined) {
return false;
}
// Scheme primitives are not environment dependent.
if (typeof item === 'string' || typeof item === 'boolean') {
return false;
}
// Scheme symbols represent identifiers, which are environment dependent.
if (item instanceof base_1._Symbol) {
return true;
}
// We assume no optimisations for scheme lists.
if ((0, lodash_1.isArray)(item)) {
return true;
}
// If result is already calculated, return it
if (item.isEnvDependent !== undefined) {
return item.isEnvDependent;
}
const setter = (0, exports.isNode)(item)
? propertySetter.get(item.type)
: (0, exports.isInstr)(item)
? propertySetter.get(item.instrType)
: undefined;
if (setter) {
return setter(item)?.isEnvDependent ?? false;
}
return false;
}
exports.isEnvDependent = isEnvDependent;
//# sourceMappingURL=utils.js.map
;