UNPKG

js-slang

Version:

Javascript-based implementations of Source, written in Typescript

383 lines 17.8 kB
"use strict"; 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