js-slang
Version:
Javascript-based implementations of Source, written in Typescript
1,066 lines • 132 kB
JavaScript
"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