UNPKG

js-slang

Version:

Javascript-based implementations of Source, written in Typescript

1,066 lines 132 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.callee = exports.isStepperOutput = exports.getEvaluationSteps = exports.getRedex = exports.redexify = exports.javascriptify = exports.codify = void 0; const astring_1 = require("astring"); const errors = require("../errors/errors"); const parser_1 = require("../parser/parser"); const ast = require("../utils/ast/astCreator"); const dummyAstCreator_1 = require("../utils/ast/dummyAstCreator"); const helpers_1 = require("../utils/ast/helpers"); const operators_1 = require("../utils/operators"); const rttc = require("../utils/rttc"); const validator_1 = require("../validator/validator"); const runtimeSourceError_1 = require("../errors/runtimeSourceError"); const converter_1 = require("./converter"); const builtin = require("./lib"); const util_1 = require("./util"); const irreducibleTypes = new Set([ 'Literal', 'FunctionExpression', 'ArrowFunctionExpression', 'ArrayExpression' ]); function isIrreducible(node, context) { return ((0, util_1.isBuiltinFunction)(node) || (0, util_1.isImportedFunction)(node, context) || (0, util_1.isAllowedLiterals)(node) || (0, util_1.isNegNumber)(node) || irreducibleTypes.has(node.type)); } function isStatementsReducible(progs, context) { if (progs.body.length === 0) return false; if (progs.body.length > 1) return true; const [lastStatement] = progs.body; if (lastStatement.type !== 'ExpressionStatement') { return true; } return !isIrreducible(lastStatement.expression, context); } function scanOutBoundNames(node) { const declaredIds = []; if (node.type == 'ArrowFunctionExpression') { for (const param of node.params) { declaredIds.push(param); } } else if (node.type == 'BlockExpression' || node.type == 'BlockStatement') { for (const stmt of node.body) { // if stmt is assignment or functionDeclaration // add stmt into a set of identifiers // return that set if (stmt.type === 'VariableDeclaration') { stmt.declarations .map(decn => decn.id) .forEach(name => declaredIds.push(name)); for (const decn of stmt.declarations) { if (decn.init !== null && decn.init !== undefined && decn.init.type == 'ArrowFunctionExpression') { for (const param of decn.init.params) { declaredIds.push(param); } } } } else if (stmt.type === 'FunctionDeclaration' && stmt.id) { declaredIds.push(stmt.id); stmt.params.forEach(param => declaredIds.push(param)); } } } return declaredIds; } function scanOutDeclarations(node) { const declaredIds = []; if (node.type === 'BlockExpression' || node.type === 'BlockStatement' || node.type === 'Program') { for (const stmt of node.body) { // if stmt is assignment or functionDeclaration // add stmt into a set of identifiers // return that set if (stmt.type === 'VariableDeclaration') { stmt.declarations .map(decn => decn.id) .forEach(name => declaredIds.push(name)); } else if (stmt.type === 'FunctionDeclaration' && stmt.id) { declaredIds.push(stmt.id); } } } return declaredIds; } function getFreshName(paramName, counter, freeTarget, freeReplacement, boundTarget, boundUpperScope, boundReplacement) { let added = true; while (added) { added = false; for (const f of freeTarget) { if (paramName + '_' + counter === f) { counter++; added = true; } } for (const free of freeReplacement) { if (paramName + '_' + counter === free) { counter++; added = true; } } for (const notFree of boundTarget) { if (paramName + '_' + counter === notFree.name) { counter++; added = true; } } for (const boundName of boundUpperScope) { if (paramName + '_' + counter === boundName) { counter++; added = true; } } for (const identifier of boundReplacement) { if (paramName + '_' + counter === identifier.name) { counter++; added = true; } } } return paramName + '_' + counter; } function findMain(target, seenBefore) { const params = []; if (target.type == 'FunctionExpression' || target.type == 'ArrowFunctionExpression' || target.type === 'FunctionDeclaration') { if (target.type == 'FunctionExpression' || target.type === 'FunctionDeclaration') { params.push(target.id.name); } for (let i = 0; i < target.params.length; i++) { params.push(target.params[i].name); } } const freeNames = []; const finders = { Identifier(target) { seenBefore.set(target, target); let bound = false; for (let i = 0; i < params.length; i++) { if (target.name == params[i]) { bound = true; break; } } if (!bound) { freeNames.push(target.name); } }, ExpressionStatement(target) { seenBefore.set(target, target); find(target.expression); }, BinaryExpression(target) { seenBefore.set(target, target); find(target.left); find(target.right); }, UnaryExpression(target) { seenBefore.set(target, target); find(target.argument); }, ConditionalExpression(target) { seenBefore.set(target, target); find(target.test); find(target.consequent); find(target.alternate); }, LogicalExpression(target) { seenBefore.set(target, target); find(target.left); find(target.right); }, CallExpression(target) { seenBefore.set(target, target); for (let i = 0; i < target.arguments.length; i++) { find(target.arguments[i]); } find(target.callee); }, FunctionDeclaration(target) { seenBefore.set(target, target); const freeInNested = findMain(target, seenBefore); for (const free of freeInNested) { let bound = false; for (const param of params) { if (free === param) { bound = true; } } if (!bound) { freeNames.push(free); } } }, ArrowFunctionExpression(target) { seenBefore.set(target, target); const freeInNested = findMain(target, seenBefore); for (const free of freeInNested) { let bound = false; for (const param of params) { if (free === param) { bound = true; } } if (!bound) { freeNames.push(free); } } }, Program(target) { seenBefore.set(target, target); target.body.forEach(stmt => { find(stmt); }); }, BlockStatement(target) { seenBefore.set(target, target); const declaredNames = (0, util_1.getDeclaredNames)(target); for (const item of declaredNames.values()) { params.push(item); } target.body.forEach(stmt => { find(stmt); }); }, BlockExpression(target) { seenBefore.set(target, target); const declaredNames = (0, util_1.getDeclaredNames)(target); for (const item of declaredNames.values()) { params.push(item); } target.body.forEach(stmt => { find(stmt); }); }, ReturnStatement(target) { seenBefore.set(target, target); find(target.argument); }, VariableDeclaration(target) { seenBefore.set(target, target); target.declarations.forEach(dec => { find(dec); }); }, VariableDeclarator(target) { seenBefore.set(target, target); find(target.init); }, IfStatement(target) { seenBefore.set(target, target); find(target.test); find(target.consequent); find(target.alternate); }, ArrayExpression(target) { seenBefore.set(target, target); target.elements.forEach(ele => { find(ele); }); } }; function find(target) { const result = seenBefore.get(target); if (!result) { const finder = finders[target.type]; if (finder === undefined) { seenBefore.set(target, target); } else { return finder(target); } } } find(target.body); return freeNames; } /* tslint:disable:no-shadowed-variable */ // wrapper function, calls substitute immediately. function substituteMain(name, replacement, target, paths) { const seenBefore = new Map(); // initialises array to keep track of all paths visited // without modifying input path array const allPaths = []; let allPathsIndex = 0; const endMarker = '$'; if (paths[0] === undefined) { allPaths.push([]); } else { allPaths.push([...paths[0]]); } // substituters will stop expanding the path if index === -1 const pathNotEnded = (index) => index > -1; // branches out path into two different paths, // returns array index of branched path function branch(index) { allPathsIndex++; allPaths[allPathsIndex] = [...allPaths[index]]; return allPathsIndex; } // keeps track of names in upper scope so that it doesnt rename to these names const boundUpperScope = []; /** * Substituters are invoked only when the target is not seen before, * therefore each function has the responsbility of registering the * [target, replacement] pair in seenBefore. * How substituters work: * 1. Create dummy replacement and push [target, dummyReplacement] * into the seenBefore array. * 2. [Recursive step] substitute the children; * for each child, branch out the current path * and push the appropriate access string into the path * 3. Return the dummyReplacement */ const substituters = { // if name to be replaced is found, // push endMarker into path Identifier(target, index) { const re = / rename$/; if (replacement.type === 'Literal') { // only accept string, boolean and numbers for arguments if (target.name === name.name) { if (pathNotEnded(index)) { allPaths[index].push(endMarker); } return ast.primitive(replacement.value); } else { return target; } } else if (replacement.type === 'Identifier' && re.test(replacement.name)) { if (target.name === name.name) { if (pathNotEnded(index)) { allPaths[index].push(endMarker); } return ast.identifier(replacement.name.split(' ')[0], replacement.loc); } else { return target; } } else { if (target.name === name.name) { if (pathNotEnded(index)) { allPaths[index].push(endMarker); } return substitute(replacement, -1); } else { return target; } } }, ExpressionStatement(target, index) { const substedExpressionStatement = ast.expressionStatement((0, dummyAstCreator_1.dummyExpression)()); seenBefore.set(target, substedExpressionStatement); if (pathNotEnded(index)) { allPaths[index].push('expression'); } substedExpressionStatement.expression = substitute(target.expression, index); return substedExpressionStatement; }, BinaryExpression(target, index) { const substedBinaryExpression = ast.binaryExpression(target.operator, (0, dummyAstCreator_1.dummyExpression)(), (0, dummyAstCreator_1.dummyExpression)(), target.loc); seenBefore.set(target, substedBinaryExpression); let nextIndex = index; if (pathNotEnded(index)) { nextIndex = branch(index); allPaths[index].push('left'); allPaths[nextIndex].push('right'); } substedBinaryExpression.left = substitute(target.left, index); substedBinaryExpression.right = substitute(target.right, nextIndex); return substedBinaryExpression; }, UnaryExpression(target, index) { const substedUnaryExpression = ast.unaryExpression(target.operator, (0, dummyAstCreator_1.dummyExpression)(), target.loc); seenBefore.set(target, substedUnaryExpression); if (pathNotEnded(index)) { allPaths[index].push('argument'); } substedUnaryExpression.argument = substitute(target.argument, index); return substedUnaryExpression; }, ConditionalExpression(target, index) { const substedConditionalExpression = ast.conditionalExpression((0, dummyAstCreator_1.dummyExpression)(), (0, dummyAstCreator_1.dummyExpression)(), (0, dummyAstCreator_1.dummyExpression)(), target.loc); seenBefore.set(target, substedConditionalExpression); let nextIndex = index; let thirdIndex = index; if (pathNotEnded(index)) { nextIndex = branch(index); thirdIndex = branch(index); allPaths[index].push('test'); allPaths[nextIndex].push('consequent'); allPaths[thirdIndex].push('alternate'); } substedConditionalExpression.test = substitute(target.test, index); substedConditionalExpression.consequent = substitute(target.consequent, nextIndex); substedConditionalExpression.alternate = substitute(target.alternate, thirdIndex); return substedConditionalExpression; }, LogicalExpression(target, index) { const substedLocialExpression = ast.logicalExpression(target.operator, target.left, target.right); seenBefore.set(target, substedLocialExpression); let nextIndex = index; if (pathNotEnded(index)) { nextIndex = branch(index); allPaths[index].push('left'); allPaths[nextIndex].push('right'); } substedLocialExpression.left = substitute(target.left, index); substedLocialExpression.right = substitute(target.right, nextIndex); return substedLocialExpression; }, CallExpression(target, index) { const dummyArgs = target.arguments.map(() => (0, dummyAstCreator_1.dummyExpression)()); const substedCallExpression = ast.callExpression((0, dummyAstCreator_1.dummyExpression)(), dummyArgs, target.loc); seenBefore.set(target, substedCallExpression); const arr = []; let nextIndex = index; for (let i = 0; i < target.arguments.length; i++) { if (pathNotEnded(index)) { nextIndex = branch(index); allPaths[nextIndex].push('arguments[' + i + ']'); } arr[i] = nextIndex; dummyArgs[i] = substitute(target.arguments[i], nextIndex); } if (pathNotEnded(index)) { allPaths[index].push('callee'); } substedCallExpression.callee = substitute(target.callee, index); return substedCallExpression; }, FunctionDeclaration(target, index) { const substedParams = []; // creates a copy of the params so that the renaming only happens during substitution. for (let i = 0; i < target.params.length; i++) { const param = target.params[i]; substedParams.push(ast.identifier(param.name, param.loc)); } const re = / rename$/; let newID; let newBody = target.body; if (replacement.type === 'Identifier' && re.test(replacement.name)) { // renaming function name newID = ast.identifier(replacement.name.split(' ')[0], replacement.loc); } else { newID = ast.identifier(target.id.name, target.loc); } const substedFunctionDeclaration = ast.functionDeclaration(newID, substedParams, (0, dummyAstCreator_1.dummyBlockStatement)()); seenBefore.set(target, substedFunctionDeclaration); let freeReplacement = []; let boundReplacement = []; if (replacement.type == 'FunctionExpression' || replacement.type == 'ArrowFunctionExpression') { freeReplacement = findMain(replacement, new Map()); boundReplacement = scanOutBoundNames(replacement.body); } const freeTarget = findMain(target, new Map()); const boundTarget = scanOutBoundNames(target.body); for (let i = 0; i < target.params.length; i++) { const param = target.params[i]; if (param.type === 'Identifier' && param.name === name.name) { substedFunctionDeclaration.body = target.body; return substedFunctionDeclaration; } if (param.type == 'Identifier') { if (freeReplacement.includes(param.name)) { // change param name const re = /_\d+$/; let newNum; if (re.test(param.name)) { const num = param.name.split('_'); newNum = Number(num[1]) + 1; const changedName = getFreshName(num[0], newNum, freeTarget, freeReplacement, boundTarget, boundUpperScope, boundReplacement); const changed = ast.identifier(changedName, param.loc); newBody = substituteMain(param, changed, target.body, [[]])[0]; substedFunctionDeclaration.params[i].name = changedName; } else { newNum = 1; const changedName = getFreshName(param.name, newNum, freeTarget, freeReplacement, boundTarget, boundUpperScope, boundReplacement); const changed = ast.identifier(changedName, param.loc); newBody = substituteMain(param, changed, target.body, [[]])[0]; substedFunctionDeclaration.params[i].name = changedName; } } } } for (const param of substedParams) { boundUpperScope.push(param.name); } if (pathNotEnded(index)) { allPaths[index].push('body'); } substedFunctionDeclaration.body = substitute(newBody, index); return substedFunctionDeclaration; }, FunctionExpression(target, index) { const substedParams = []; // creates a copy of the params so that the renaming only happens during substitution. for (let i = 0; i < target.params.length; i++) { const param = target.params[i]; substedParams.push(ast.identifier(param.name, param.loc)); } const substedFunctionExpression = target.id ? ast.functionDeclarationExpression(target.id, substedParams, (0, dummyAstCreator_1.dummyBlockStatement)()) : ast.functionExpression(substedParams, (0, dummyAstCreator_1.dummyBlockStatement)()); seenBefore.set(target, substedFunctionExpression); // check for free/bounded variable in replacement let freeReplacement = []; let boundReplacement = []; if (replacement.type == 'FunctionExpression' || replacement.type == 'ArrowFunctionExpression') { freeReplacement = findMain(replacement, new Map()); boundReplacement = scanOutBoundNames(replacement.body); } const freeTarget = findMain(target, new Map()); const boundTarget = scanOutBoundNames(target.body); for (let i = 0; i < target.params.length; i++) { const param = target.params[i]; if (param.type === 'Identifier' && param.name === name.name) { substedFunctionExpression.body = target.body; return substedFunctionExpression; } if (param.type == 'Identifier') { if (freeReplacement.includes(param.name)) { // change param name const re = /_\d+$/; let newNum; if (re.test(param.name)) { const num = param.name.split('_'); newNum = Number(num[1]) + 1; const changedName = getFreshName(num[0], newNum, freeTarget, freeReplacement, boundTarget, boundUpperScope, boundReplacement); const changed = ast.identifier(changedName, param.loc); target.body = substituteMain(param, changed, target.body, [ [] ])[0]; substedFunctionExpression.params[i].name = changedName; } else { newNum = 1; const changedName = getFreshName(param.name, newNum, freeTarget, freeReplacement, boundTarget, boundUpperScope, boundReplacement); const changed = ast.identifier(changedName, param.loc); target.body = substituteMain(param, changed, target.body, [ [] ])[0]; substedFunctionExpression.params[i].name = changedName; } } } } for (const param of substedParams) { boundUpperScope.push(param.name); } if (pathNotEnded(index)) { allPaths[index].push('body'); } substedFunctionExpression.body = substitute(target.body, index); return substedFunctionExpression; }, Program(target, index) { const substedBody = target.body.map(() => (0, dummyAstCreator_1.dummyStatement)()); const substedProgram = ast.program(substedBody); seenBefore.set(target, substedProgram); const declaredNames = (0, util_1.getDeclaredNames)(target); const re = / same/; // checks if the replacement is a functionExpression or arrowFunctionExpression and not from within the same block if ((replacement.type == 'FunctionExpression' || replacement.type == 'ArrowFunctionExpression') && !re.test(name.name)) { const freeTarget = findMain(target, new Map()); const declaredIds = scanOutDeclarations(target); const freeReplacement = findMain(replacement, new Map()); const boundReplacement = scanOutDeclarations(replacement.body); for (const declaredId of declaredIds) { if (freeReplacement.includes(declaredId.name)) { const re = /_\d+$/; let newNum; if (re.test(declaredId.name)) { const num = declaredId.name.split('_'); newNum = Number(num[1]) + 1; const changedName = getFreshName(num[0], newNum, freeTarget, freeReplacement, declaredIds, boundUpperScope, boundReplacement); const changed = ast.identifier(changedName + ' rename', declaredId.loc); const newName = ast.identifier(declaredId.name + ' rename', declaredId.loc); target = substituteMain(newName, changed, target, [[]])[0]; } else { newNum = 1; const changedName = getFreshName(declaredId.name, newNum, freeTarget, freeReplacement, declaredIds, boundUpperScope, boundReplacement); const changed = ast.identifier(changedName + ' rename', declaredId.loc); const newName = ast.identifier(declaredId.name + ' rename', declaredId.loc); target = substituteMain(newName, changed, target, [[]])[0]; } } } } const re2 = / rename/; if (declaredNames.has(name.name) && !re2.test(name.name)) { substedProgram.body = target.body; return substedProgram; } // if it is from the same block then the name would be name + " same", hence need to remove " same" // if not this statement does nothing as variable names should not have spaces name.name = name.name.split(' ')[0]; const arr = []; let nextIndex = index; for (let i = 1; i < target.body.length; i++) { if (pathNotEnded(index)) { nextIndex = branch(index); allPaths[nextIndex].push('body[' + i + ']'); } arr[i] = nextIndex; } if (pathNotEnded(index)) { allPaths[index].push('body[0]'); } arr[0] = index; let arrIndex = -1; substedProgram.body = target.body.map(stmt => { arrIndex++; return substitute(stmt, arr[arrIndex]); }); return substedProgram; }, BlockStatement(target, index) { const substedBody = target.body.map(() => (0, dummyAstCreator_1.dummyStatement)()); const substedBlockStatement = ast.blockStatement(substedBody); seenBefore.set(target, substedBlockStatement); const declaredNames = (0, util_1.getDeclaredNames)(target); const re = / same/; // checks if the replacement is a functionExpression or arrowFunctionExpression and not from within the same block if ((replacement.type == 'FunctionExpression' || replacement.type == 'ArrowFunctionExpression') && !re.test(name.name)) { const freeTarget = findMain(target, new Map()); const declaredIds = scanOutDeclarations(target); const freeReplacement = findMain(replacement, new Map()); const boundReplacement = scanOutDeclarations(replacement.body); for (const declaredId of declaredIds) { if (freeReplacement.includes(declaredId.name)) { const re = /_\d+$/; let newNum; if (re.test(declaredId.name)) { const num = declaredId.name.split('_'); newNum = Number(num[1]) + 1; const changedName = getFreshName(num[0], newNum, freeTarget, freeReplacement, declaredIds, boundUpperScope, boundReplacement); const changed = ast.identifier(changedName + ' rename', declaredId.loc); const newName = ast.identifier(declaredId.name + ' rename', declaredId.loc); target = substituteMain(newName, changed, target, [[]])[0]; } else { newNum = 1; const changedName = getFreshName(declaredId.name, newNum, freeTarget, freeReplacement, declaredIds, boundUpperScope, boundReplacement); const changed = ast.identifier(changedName + ' rename', declaredId.loc); const newName = ast.identifier(declaredId.name + ' rename', declaredId.loc); target = substituteMain(newName, changed, target, [[]])[0]; } } } } const re2 = / rename/; if (declaredNames.has(name.name) && !re2.test(name.name)) { substedBlockStatement.body = target.body; return substedBlockStatement; } // if it is from the same block then the name would be name + " same", hence need to remove " same" // if not this statement does nothing as variable names should not have spaces name.name = name.name.split(' ')[0]; const arr = []; let nextIndex = index; for (let i = 1; i < target.body.length; i++) { if (pathNotEnded(index)) { nextIndex = branch(index); allPaths[nextIndex].push('body[' + i + ']'); } arr[i] = nextIndex; } if (pathNotEnded(index)) { allPaths[index].push('body[0]'); } arr[0] = index; let arrIndex = -1; substedBlockStatement.body = target.body.map(stmt => { arrIndex++; return substitute(stmt, arr[arrIndex]); }); return substedBlockStatement; }, BlockExpression(target, index) { const substedBody = target.body.map(() => (0, dummyAstCreator_1.dummyStatement)()); const substedBlockExpression = ast.blockExpression(substedBody); seenBefore.set(target, substedBlockExpression); const declaredNames = (0, util_1.getDeclaredNames)(target); const re = / same/; // checks if the replacement is a functionExpression or arrowFunctionExpression and not from within the same block if ((replacement.type == 'FunctionExpression' || replacement.type == 'ArrowFunctionExpression') && !re.test(name.name)) { const freeTarget = findMain(target, new Map()); const declaredIds = scanOutDeclarations(target); const freeReplacement = findMain(replacement, new Map()); const boundReplacement = scanOutDeclarations(replacement.body); for (const declaredId of declaredIds) { if (freeReplacement.includes(declaredId.name)) { const re = /_\d+$/; let newNum; if (re.test(declaredId.name)) { const num = declaredId.name.split('_'); newNum = Number(num[1]) + 1; const changedName = getFreshName(num[0], newNum, freeTarget, freeReplacement, declaredIds, boundUpperScope, boundReplacement); const changed = ast.identifier(changedName + ' rename', declaredId.loc); const newName = ast.identifier(declaredId.name + ' rename', declaredId.loc); target = substituteMain(newName, changed, target, [[]])[0]; } else { newNum = 1; const changedName = getFreshName(declaredId.name, newNum, freeTarget, freeReplacement, declaredIds, boundUpperScope, boundReplacement); const changed = ast.identifier(changedName + ' rename', declaredId.loc); const newName = ast.identifier(declaredId.name + ' rename', declaredId.loc); target = substituteMain(newName, changed, target, [[]])[0]; } } } } const re2 = / rename/; if (declaredNames.has(name.name) && !re2.test(name.name)) { substedBlockExpression.body = target.body; return substedBlockExpression; } // if it is from the same block then the name would be name + " same", hence need to remove " same" // if not this statement does nothing as variable names should not have spaces name.name = name.name.split(' ')[0]; const arr = []; let nextIndex = index; for (let i = 1; i < target.body.length; i++) { if (pathNotEnded(index)) { nextIndex = branch(index); allPaths[nextIndex].push('body[' + i + ']'); } arr[i] = nextIndex; } if (pathNotEnded(index)) { allPaths[index].push('body[0]'); } arr[0] = index; let arrIndex = -1; substedBlockExpression.body = target.body.map(stmt => { arrIndex++; return substitute(stmt, arr[arrIndex]); }); return substedBlockExpression; }, ReturnStatement(target, index) { const substedReturnStatement = ast.returnStatement((0, dummyAstCreator_1.dummyExpression)(), target.loc); seenBefore.set(target, substedReturnStatement); if (pathNotEnded(index)) { allPaths[index].push('argument'); } substedReturnStatement.argument = substitute(target.argument, index); return substedReturnStatement; }, // source 1 ArrowFunctionExpression(target, index) { // creates a copy of the parameters so that renaming only happens during substitution const substedParams = []; for (let i = 0; i < target.params.length; i++) { const param = target.params[i]; substedParams.push(ast.identifier(param.name, param.loc)); } let newBody = target.body; const substedArrow = ast.arrowFunctionExpression(substedParams, (0, dummyAstCreator_1.dummyBlockStatement)()); seenBefore.set(target, substedArrow); // check for free/bounded variable let freeReplacement = []; let boundReplacement = []; if (replacement.type == 'FunctionExpression' || replacement.type == 'ArrowFunctionExpression') { freeReplacement = findMain(replacement, new Map()); boundReplacement = scanOutBoundNames(replacement.body); } for (let i = 0; i < target.params.length; i++) { const param = target.params[i]; if (param.type === 'Identifier' && param.name === name.name) { substedArrow.body = target.body; substedArrow.expression = target.body.type !== 'BlockStatement'; return substedArrow; } const freeTarget = findMain(target, new Map()); const boundTarget = scanOutBoundNames(target.body); if (param.type == 'Identifier') { if (freeReplacement.includes(param.name)) { // change param name const re = /_\d+$/; let newNum; if (re.test(param.name)) { const num = param.name.split('_'); newNum = Number(num[1]) + 1; const changedName = getFreshName(num[0], newNum, freeTarget, freeReplacement, boundTarget, boundUpperScope, boundReplacement); const changed = ast.identifier(changedName, param.loc); newBody = substituteMain(param, changed, target.body, [[]])[0]; substedArrow.params[i].name = changedName; // num[0] + '_' + newNum } else { newNum = 1; const changedName = getFreshName(param.name, newNum, freeTarget, freeReplacement, boundTarget, boundUpperScope, boundReplacement); const changed = ast.identifier(changedName, param.loc); newBody = substituteMain(param, changed, target.body, [[]])[0]; substedArrow.params[i].name = changedName; } } } } for (const param of substedParams) { boundUpperScope.push(param.name); } for (const param of target.params) { if (param.type === 'Identifier' && param.name === name.name) { substedArrow.body = target.body; substedArrow.expression = target.body.type !== 'BlockStatement'; return substedArrow; } } if (pathNotEnded(index)) { allPaths[index].push('body'); } substedArrow.body = substitute(newBody, index); substedArrow.expression = target.body.type !== 'BlockStatement'; return substedArrow; }, VariableDeclaration(target, index) { const substedVariableDeclaration = ast.variableDeclaration([(0, dummyAstCreator_1.dummyVariableDeclarator)()]); seenBefore.set(target, substedVariableDeclaration); const arr = []; let nextIndex = index; for (let i = 1; i < target.declarations.length; i++) { if (pathNotEnded(index)) { nextIndex = branch(index); allPaths[nextIndex].push('declarations[' + i + ']'); } arr[i] = nextIndex; } if (pathNotEnded(index)) { allPaths[index].push('declarations[0]'); } arr[0] = index; let arrIndex = -1; substedVariableDeclaration.declarations = target.declarations.map(dec => { arrIndex++; return substitute(dec, arr[arrIndex]); }); return substedVariableDeclaration; }, VariableDeclarator(target, index) { const subbed = ast.identifier(target.id.name); let substedVariableDeclarator = ast.variableDeclarator(subbed, (0, dummyAstCreator_1.dummyExpression)()); seenBefore.set(target, substedVariableDeclarator); const re = / rename$/; if (target.id.type === 'Identifier' && name.name === target.id.name) { if (replacement.type == 'Identifier' && re.test(replacement.name)) { const newName = ast.identifier(replacement.name.split(' ')[0], replacement.loc); substedVariableDeclarator = ast.variableDeclarator(newName, (0, dummyAstCreator_1.dummyExpression)()); } substedVariableDeclarator.init = target.init; } else { if (pathNotEnded(index)) { allPaths[index].push('init'); } substedVariableDeclarator.init = substitute(target.init, index); } return substedVariableDeclarator; }, IfStatement(target, index) { const substedIfStatement = ast.ifStatement((0, dummyAstCreator_1.dummyExpression)(), (0, dummyAstCreator_1.dummyBlockStatement)(), (0, dummyAstCreator_1.dummyBlockStatement)(), target.loc); seenBefore.set(target, substedIfStatement); let nextIndex = index; let thirdIndex = index; if (pathNotEnded(index)) { nextIndex = branch(index); thirdIndex = branch(index); allPaths[index].push('test'); allPaths[nextIndex].push('consequent'); allPaths[thirdIndex].push('alternate'); } substedIfStatement.test = substitute(target.test, index); substedIfStatement.consequent = substitute(target.consequent, nextIndex); substedIfStatement.alternate = target.alternate ? substitute(target.alternate, thirdIndex) : null; return substedIfStatement; }, ArrayExpression(target, index) { const substedArray = ast.arrayExpression([(0, dummyAstCreator_1.dummyExpression)()]); seenBefore.set(target, substedArray); const arr = []; let nextIndex = index; for (let i = 1; i < target.elements.length; i++) { if (pathNotEnded(index)) { nextIndex = branch(index); allPaths[nextIndex].push('elements[' + i + ']'); } arr[i] = nextIndex; } if (pathNotEnded(index)) { allPaths[index].push('elements[0]'); } arr[0] = index; let arrIndex = -1; substedArray.elements = target.elements.map(ele => { arrIndex++; return substitute(ele, arr[arrIndex]); }); return substedArray; } }; /** * For mapper use, maps a [symbol, value] pair to the node supplied. * @param name the name to be replaced * @param replacement the expression to replace the name with * @param node a node holding the target symbols * @param seenBefore a list of nodes that are seen before in substitution */ function substitute(target, index) { const result = seenBefore.get(target); if (result) { return result; } const substituter = substituters[target.type]; if (substituter === undefined) { seenBefore.set(target, target); return target; // no need to subst, such as literals } else { // substituters are responsible of registering seenBefore return substituter(target, index); } } // after running substitute, // find paths that contain endMarker // and return only those paths const substituted = substitute(target, 0); const validPaths = []; for (const path of allPaths) { if (path[path.length - 1] === endMarker) { validPaths.push(path.slice(0, path.length - 1)); } } return [substituted, validPaths]; } /** * Substitutes a call expression with the body of the callee (funExp) * and the body will have all ocurrences of parameters substituted * with the arguments. * @param callee call expression with callee as functionExpression * @param args arguments supplied to the call expression */ function apply(callee, args) { let substedBody = callee.body; let substedParams = callee.params; for (let i = 0; i < args.length; i++) { // source discipline requires parameters to be identifiers. const arg = args[i]; if (arg.type === 'ArrowFunctionExpression' || arg.type === 'FunctionExpression') { const freeTarget = findMain(ast.arrowFunctionExpression(substedParams, substedBody), new Map()); const declaredIds = substedParams; const freeReplacement = findMain(arg, new Map()); const boundReplacement = scanOutDeclarations(arg.body); for (const declaredId of declaredIds) { if (freeReplacement.includes(declaredId.name)) { const re = /_\d+$/; let newNum; if (re.test(declaredId.name)) { const num = declaredId.name.split('_'); newNum = Number(num[1]) + 1; const changedName = getFreshName(num[0], newNum, freeTarget, freeReplacement, declaredIds, [], boundReplacement); const changed = ast.identifier(changedName + ' rename', declaredId.loc); const newName = ast.identifier(declaredId.name + ' rename', declaredId.loc); substedBody = substituteMain(newName, changed, substedBody, [ [] ])[0]; substedParams = substedParams.map(param => param.name === declaredId.name ? changed : param); } else { newNum = 1; const changedName = getFreshName(declaredId.name, newNum, freeTarget, freeReplacement, declaredIds, [], boundReplacement); const changed = ast.identifier(changedName + ' rename', declaredId.loc); const newName = ast.identifier(declaredId.name + ' rename', declaredId.loc); substedBody = substituteMain(newName, changed, substedBody, [ [] ])[0]; substedParams = substedParams.map(param => param.name === declaredId.name ? changed : param); } } } } // source discipline requires parameters to be identifiers. const param = substedParams[i]; substedBody = substituteMain(param, arg, substedBody, [[]])[0]; } if (callee.type === 'ArrowFunctionExpression' && callee.expression) { return substedBody; } const firstStatement = substedBody.body[0]; return firstStatement && firstStatement.type === 'ReturnStatement' ? firstStatement.argument : ast.blockExpression(substedBody.body); } // Wrapper function to house reduce, explain and bodify function reduceMain(node, context) { // variable to control verbosity of bodify let verbose = true; // converts body of code to string function bodify(target) { const bodifiers = { Literal: (target) => target.raw !== undefined ? target.raw : String(target.value), Identifier: (target) => target.name.startsWith('anonymous_') ? 'anonymous function' : target.name, ExpressionStatement: (target) => bodify(target.expression) + ' finished evaluating', BinaryExpression: (target) => bodify(target.left) + ' ' + target.operator + ' ' + bodify(target.right), UnaryExpression: (target) => target.operator + bodify(target.argument), ConditionalExpression: (target) => bodify(target.test) + ' ? ' + bodify(target.consequent) + ' : ' + bodify(target.alternate), LogicalExpression: (target) => bodify(target.left) + ' ' + target.operator + ' ' + bodify(target.right), CallExpression: (target) => { if (target.callee.type === 'ArrowFunctionExpression') { return '(' + bodify(target.callee) + ')(' + target.arguments.map(bodify) + ')'; } else { return bodify(target.callee) + '(' + target.arguments.map(bodify) + ')'; } }, FunctionDeclaration: (target) => { const funcName = target.id !== null ? target.id.name : 'error'; return ('Function ' + funcName + ' declared' + (target.params.length > 0 ? ', parameter(s) ' + target.params.map(bodify) + ' required' : '')); }, FunctionExpression: (target) => { const id = target.id; return id === null || id === undefined ? '...' : id.name; }, ReturnStatement: (target) => bodify(target.argument) + ' returned', // guards against infinite text generation ArrowFunctionExpression: (target) => { if (verbose) { verbose = false; const redacted = (target.params.length > 0 ? target.params.map(bodify) : '()') + ' => ' + bod