js-slang
Version:
Javascript-based implementations of Source, written in Typescript
383 lines • 17.8 kB
JavaScript
Object.defineProperty(exports, "__esModule", { value: true });
exports.transpile = exports.evallerReplacer = exports.getBuiltins = exports.getGloballyDeclaredIdentifiers = exports.transformImportDeclarations = void 0;
/* eslint-disable @typescript-eslint/no-unused-vars */
const astring_1 = require("astring");
const source_map_1 = require("source-map");
const constants_1 = require("../constants");
const types_1 = require("../types");
const create = require("../utils/ast/astCreator");
const helpers_1 = require("../utils/ast/helpers");
const uniqueIds_1 = require("../utils/uniqueIds");
const walkers_1 = require("../utils/walkers");
const validator_1 = require("../validator/validator");
const typeGuards_1 = require("../utils/ast/typeGuards");
/**
* This whole transpiler includes many many many many hacks to get stuff working.
* Order in which certain functions are called matter as well.
* There should be an explanation on it coming up soon.
*/
function transformImportDeclarations(program, moduleExpr) {
const [importNodes, otherNodes] = (0, helpers_1.filterImportDeclarations)(program);
const declNodes = importNodes.flatMap((moduleName, nodes) => {
const expr = create.memberExpression(moduleExpr, moduleName);
return nodes.flatMap(({ specifiers }) => specifiers.map(spec => create.constantDeclaration(spec.local.name, (0, typeGuards_1.isNamespaceSpecifier)(spec) ? expr : create.memberExpression(expr, (0, helpers_1.getImportedName)(spec)))));
});
return [declNodes, otherNodes];
}
exports.transformImportDeclarations = transformImportDeclarations;
function getGloballyDeclaredIdentifiers(program) {
return program.body
.filter(statement => statement.type === 'VariableDeclaration')
.map(({ declarations: { 0: { id } }, kind }) => id.name);
}
exports.getGloballyDeclaredIdentifiers = getGloballyDeclaredIdentifiers;
function getBuiltins(nativeStorage) {
const builtinsStatements = [];
nativeStorage.builtins.forEach((_unused, name) => {
builtinsStatements.push(create.constantDeclaration(name, create.callExpression(create.memberExpression(create.memberExpression(create.identifier(constants_1.NATIVE_STORAGE_ID), 'builtins'), 'get'), [create.literal(name)])));
});
return builtinsStatements;
}
exports.getBuiltins = getBuiltins;
function evallerReplacer(nativeStorageId, usedIdentifiers) {
const arg = create.identifier((0, uniqueIds_1.getUniqueId)(usedIdentifiers, 'program'));
return create.expressionStatement(create.assignmentExpression(create.memberExpression(nativeStorageId, 'evaller'), create.arrowFunctionExpression([arg], create.callExpression(create.identifier('eval'), [arg]))));
}
exports.evallerReplacer = evallerReplacer;
function generateFunctionsToStringMap(program) {
const map = new Map();
(0, walkers_1.simple)(program, {
ArrowFunctionExpression(node) {
map.set(node, (0, astring_1.generate)(node));
},
FunctionDeclaration(node) {
map.set(node, (0, astring_1.generate)(node));
}
});
return map;
}
function transformFunctionDeclarationsToArrowFunctions(program, functionsToStringMap) {
(0, walkers_1.simple)(program, {
FunctionDeclaration(node) {
const { id, params, body } = node;
node.type = 'VariableDeclaration';
node = node;
const asArrowFunction = create.blockArrowFunction(params, body);
functionsToStringMap.set(asArrowFunction, functionsToStringMap.get(node));
node.declarations = [
{
type: 'VariableDeclarator',
id: id,
init: asArrowFunction
}
];
node.kind = 'const';
}
});
}
/**
* Transforms all arrow functions
* (arg1, arg2, ...) => { statement1; statement2; return statement3; }
*
* to
*
* <NATIVE STORAGE>.operators.wrap((arg1, arg2, ...) => {
* statement1;statement2;return statement3;
* })
*
* to allow for iterative processes to take place
*/
function wrapArrowFunctionsToAllowNormalCallsAndNiceToString(program, functionsToStringMap, globalIds) {
(0, walkers_1.simple)(program, {
ArrowFunctionExpression(node) {
// If it's undefined then we're dealing with a thunk
if (functionsToStringMap.get(node) !== undefined) {
create.mutateToCallExpression(node, globalIds.wrap, [
{ ...node },
create.literal(functionsToStringMap.get(node)),
create.literal(node.params[node.params.length - 1]?.type === 'RestElement'),
globalIds.native
]);
}
}
});
}
/**
* Transforms all return statements (including expression arrow functions) to return an intermediate value
* return nonFnCall + 1;
* =>
* return {isTail: false, value: nonFnCall + 1};
*
* return fnCall(arg1, arg2);
* => return {isTail: true, function: fnCall, arguments: [arg1, arg2]}
*
* conditional and logical expressions will be recursively looped through as well
*/
function transformReturnStatementsToAllowProperTailCalls(program) {
function transformLogicalExpression(expression) {
switch (expression.type) {
case 'LogicalExpression':
return create.logicalExpression(expression.operator, expression.left, transformLogicalExpression(expression.right), expression.loc);
case 'ConditionalExpression':
return create.conditionalExpression(expression.test, transformLogicalExpression(expression.consequent), transformLogicalExpression(expression.alternate), expression.loc);
case 'CallExpression':
expression = expression;
const { line, column } = (expression.loc ?? constants_1.UNKNOWN_LOCATION).start;
const source = expression.loc?.source ?? null;
const functionName = expression.callee.type === 'Identifier' ? expression.callee.name : '<anonymous>';
const args = expression.arguments;
return create.objectExpression([
create.property('isTail', create.literal(true)),
create.property('function', expression.callee),
create.property('functionName', create.literal(functionName)),
create.property('arguments', create.arrayExpression(args)),
create.property('line', create.literal(line)),
create.property('column', create.literal(column)),
create.property('source', create.literal(source))
]);
default:
return create.objectExpression([
create.property('isTail', create.literal(false)),
create.property('value', expression)
]);
}
}
(0, walkers_1.simple)(program, {
ReturnStatement(node) {
node.argument = transformLogicalExpression(node.argument);
},
ArrowFunctionExpression(node) {
if (node.expression) {
node.body = transformLogicalExpression(node.body);
}
}
});
}
function transformCallExpressionsToCheckIfFunction(program, globalIds) {
(0, walkers_1.simple)(program, {
CallExpression(node) {
const { line, column } = (node.loc ?? constants_1.UNKNOWN_LOCATION).start;
const source = node.loc?.source ?? null;
const args = node.arguments;
node.arguments = [
node.callee,
create.literal(line),
create.literal(column),
create.literal(source),
...args
];
node.callee = globalIds.callIfFuncAndRightArgs;
}
});
}
function transformSomeExpressionsToCheckIfBoolean(program, globalIds) {
function transform(node) {
const { line, column } = (node.loc ?? constants_1.UNKNOWN_LOCATION).start;
const source = node.loc?.source ?? null;
const test = node.type === 'LogicalExpression' ? 'left' : 'test';
node[test] = create.callExpression(globalIds.boolOrErr, [
node[test],
create.literal(line),
create.literal(column),
create.literal(source)
]);
}
(0, walkers_1.simple)(program, {
IfStatement: transform,
ConditionalExpression: transform,
LogicalExpression: transform,
ForStatement: transform,
WhileStatement: transform
});
}
function transformUnaryAndBinaryOperationsToFunctionCalls(program, globalIds, chapter) {
(0, walkers_1.simple)(program, {
BinaryExpression(node) {
const { line, column } = (node.loc ?? constants_1.UNKNOWN_LOCATION).start;
const source = node.loc?.source ?? null;
const { operator, left, right } = node;
create.mutateToCallExpression(node, globalIds.binaryOp, [
create.literal(operator),
create.literal(chapter),
left,
right,
create.literal(line),
create.literal(column),
create.literal(source)
]);
},
UnaryExpression(node) {
const { line, column } = (node.loc ?? constants_1.UNKNOWN_LOCATION).start;
const source = node.loc?.source ?? null;
const { operator, argument } = node;
create.mutateToCallExpression(node, globalIds.unaryOp, [
create.literal(operator),
argument,
create.literal(line),
create.literal(column),
create.literal(source)
]);
}
});
}
function getComputedProperty(computed, property) {
return computed ? property : create.literal(property.name);
}
function transformPropertyAssignment(program, globalIds) {
(0, walkers_1.simple)(program, {
AssignmentExpression(node) {
if (node.left.type === 'MemberExpression') {
const { object, property, computed, loc } = node.left;
const { line, column } = (loc ?? constants_1.UNKNOWN_LOCATION).start;
const source = loc?.source ?? null;
create.mutateToCallExpression(node, globalIds.setProp, [
object,
getComputedProperty(computed, property),
node.right,
create.literal(line),
create.literal(column),
create.literal(source)
]);
}
}
});
}
function transformPropertyAccess(program, globalIds) {
(0, walkers_1.simple)(program, {
MemberExpression(node) {
const { object, property, computed, loc } = node;
const { line, column } = (loc ?? constants_1.UNKNOWN_LOCATION).start;
const source = loc?.source ?? null;
create.mutateToCallExpression(node, globalIds.getProp, [
object,
getComputedProperty(computed, property),
create.literal(line),
create.literal(column),
create.literal(source)
]);
}
});
}
function addInfiniteLoopProtection(program, globalIds, usedIdentifiers) {
const getTimeAst = () => create.callExpression(create.identifier('get_time'), []);
function instrumentLoops(node) {
const newStatements = [];
for (const statement of node.body) {
if (statement.type === 'ForStatement' || statement.type === 'WhileStatement') {
const startTimeConst = (0, uniqueIds_1.getUniqueId)(usedIdentifiers, 'startTime');
newStatements.push(create.constantDeclaration(startTimeConst, getTimeAst()));
if (statement.body.type === 'BlockStatement') {
const { line, column } = (statement.loc ?? constants_1.UNKNOWN_LOCATION).start;
const source = statement.loc?.source ?? null;
statement.body.body.unshift(create.expressionStatement(create.callExpression(globalIds.throwIfTimeout, [
globalIds.native,
create.identifier(startTimeConst),
getTimeAst(),
create.literal(line),
create.literal(column),
create.literal(source)
])));
}
}
newStatements.push(statement);
}
node.body = newStatements;
}
(0, walkers_1.simple)(program, {
Program: instrumentLoops,
BlockStatement: instrumentLoops
});
}
function wrapWithBuiltins(statements, nativeStorage) {
return create.blockStatement([...getBuiltins(nativeStorage), create.blockStatement(statements)]);
}
function getDeclarationsToAccessTranspilerInternals(globalIds) {
return Object.entries(globalIds).map(([key, { name }]) => {
let value;
if (key === 'native') {
value = create.identifier(constants_1.NATIVE_STORAGE_ID);
}
else if (key === 'globals') {
value = create.memberExpression(globalIds.native, 'globals');
}
else {
value = create.callExpression(create.memberExpression(create.memberExpression(globalIds.native, 'operators'), 'get'), [create.literal(key)]);
}
return create.constantDeclaration(name, value);
});
}
function transpileToSource(program, context, skipUndefined) {
if (program.body.length === 0) {
return { transpiled: '' };
}
const usedIdentifiers = new Set([
...(0, uniqueIds_1.getIdentifiersInProgram)(program),
...(0, uniqueIds_1.getIdentifiersInNativeStorage)(context.nativeStorage)
]);
const globalIds = (0, uniqueIds_1.getNativeIds)(program, usedIdentifiers);
const functionsToStringMap = generateFunctionsToStringMap(program);
transformReturnStatementsToAllowProperTailCalls(program);
transformCallExpressionsToCheckIfFunction(program, globalIds);
transformUnaryAndBinaryOperationsToFunctionCalls(program, globalIds, context.chapter);
transformSomeExpressionsToCheckIfBoolean(program, globalIds);
transformPropertyAssignment(program, globalIds);
transformPropertyAccess(program, globalIds);
(0, validator_1.checkForUndefinedVariables)(program, context, globalIds, skipUndefined);
// checkProgramForUndefinedVariables(program, context, skipUndefined)
transformFunctionDeclarationsToArrowFunctions(program, functionsToStringMap);
wrapArrowFunctionsToAllowNormalCallsAndNiceToString(program, functionsToStringMap, globalIds);
addInfiniteLoopProtection(program, globalIds, usedIdentifiers);
const [importNodes, otherNodes] = transformImportDeclarations(program, create.memberExpression(globalIds.native, 'loadedModules'));
program.body = importNodes.concat(otherNodes);
getGloballyDeclaredIdentifiers(program).forEach(id => context.nativeStorage.previousProgramsIdentifiers.add(id));
const newStatements = [
...getDeclarationsToAccessTranspilerInternals(globalIds),
evallerReplacer(globalIds.native, usedIdentifiers),
create.expressionStatement(create.identifier('undefined')),
...program.body
];
program.body =
context.nativeStorage.evaller === null
? [wrapWithBuiltins(newStatements, context.nativeStorage)]
: [create.blockStatement(newStatements)];
const map = new source_map_1.SourceMapGenerator({ file: 'source' });
const transpiled = (0, astring_1.generate)(program, { sourceMap: map });
const sourceMapJson = map.toJSON();
return { transpiled, sourceMapJson };
}
function transpileToFullJS(program, context, skipUndefined) {
const usedIdentifiers = new Set([
...(0, uniqueIds_1.getIdentifiersInProgram)(program),
...(0, uniqueIds_1.getIdentifiersInNativeStorage)(context.nativeStorage)
]);
const globalIds = (0, uniqueIds_1.getNativeIds)(program, usedIdentifiers);
(0, validator_1.checkForUndefinedVariables)(program, context, globalIds, skipUndefined);
const [importNodes, otherNodes] = transformImportDeclarations(program, create.memberExpression(create.identifier(constants_1.NATIVE_STORAGE_ID), 'loadedModules'));
program.body = importNodes.concat(otherNodes);
(0, uniqueIds_1.getFunctionDeclarationNamesInProgram)(program).forEach(id => context.nativeStorage.previousProgramsIdentifiers.add(id));
getGloballyDeclaredIdentifiers(program).forEach(id => context.nativeStorage.previousProgramsIdentifiers.add(id));
const transpiledProgram = create.program([
evallerReplacer(create.identifier(constants_1.NATIVE_STORAGE_ID), new Set()),
create.expressionStatement(create.identifier('undefined')),
...importNodes,
...otherNodes
]);
const sourceMap = new source_map_1.SourceMapGenerator({ file: 'source' });
const transpiled = (0, astring_1.generate)(transpiledProgram, { sourceMap });
const sourceMapJson = sourceMap.toJSON();
return { transpiled, sourceMapJson };
}
function transpile(program, context, skipUndefined = false) {
if (context.chapter === types_1.Chapter.FULL_JS || context.chapter === types_1.Chapter.PYTHON_1) {
return transpileToFullJS(program, context, true);
}
else if (context.variant == types_1.Variant.NATIVE) {
return transpileToFullJS(program, context, false);
}
else {
return transpileToSource(program, context, skipUndefined);
}
}
exports.transpile = transpile;
//# sourceMappingURL=transpiler.js.map
;