UNPKG

js-slang

Version:

Javascript-based implementations of Source, written in Typescript

896 lines 34.1 kB
"use strict"; 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