UNPKG

js-slang

Version:

Javascript-based implementations of Source, written in Typescript

299 lines 13 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.runCodeInSource = exports.sourceFilesRunner = void 0; const _ = require("lodash"); const constants_1 = require("../constants"); const interpreter_1 = require("../cse-machine/interpreter"); const errors_1 = require("../errors/errors"); const runtimeSourceError_1 = require("../errors/runtimeSourceError"); const timeoutErrors_1 = require("../errors/timeoutErrors"); const gpu_1 = require("../gpu/gpu"); const errors_2 = require("../infiniteLoops/errors"); const runtime_1 = require("../infiniteLoops/runtime"); const interpreter_2 = require("../interpreter/interpreter"); const interpreter_non_det_1 = require("../interpreter/interpreter-non-det"); const lazy_1 = require("../lazy/lazy"); const preprocessor_1 = require("../modules/preprocessor"); const analyzer_1 = require("../modules/preprocessor/analyzer"); const linker_1 = require("../modules/preprocessor/linker"); const parser_1 = require("../parser/parser"); const schedulers_1 = require("../schedulers"); const stepper_1 = require("../stepper/stepper"); const evalContainer_1 = require("../transpiler/evalContainer"); const transpiler_1 = require("../transpiler/transpiler"); const types_1 = require("../types"); const operators_1 = require("../utils/operators"); const validator_1 = require("../validator/validator"); const svml_compiler_1 = require("../vm/svml-compiler"); const svml_machine_1 = require("../vm/svml-machine"); const mapper_1 = require("../alt-langs/mapper"); const errors_3 = require("./errors"); const fullJSRunner_1 = require("./fullJSRunner"); const utils_1 = require("./utils"); const DEFAULT_SOURCE_OPTIONS = { scheduler: 'async', steps: 1000, stepLimit: -1, executionMethod: 'auto', variant: types_1.Variant.DEFAULT, originalMaxExecTime: 1000, useSubst: false, isPrelude: false, throwInfiniteLoops: true, envSteps: -1, importOptions: { ...analyzer_1.defaultAnalysisOptions, ...linker_1.defaultLinkerOptions, loadTabs: true }, shouldAddFileName: null }; let previousCode = null; let isPreviousCodeTimeoutError = false; function runConcurrent(program, context, options) { if (context.shouldIncreaseEvaluationTimeout) { context.nativeStorage.maxExecTime *= constants_1.JSSLANG_PROPERTIES.factorToIncreaseBy; } else { context.nativeStorage.maxExecTime = options.originalMaxExecTime; } try { return Promise.resolve({ status: 'finished', context, value: (0, svml_machine_1.runWithProgram)((0, svml_compiler_1.compileForConcurrent)(program, context), context) }); } catch (error) { if (error instanceof runtimeSourceError_1.RuntimeSourceError || error instanceof errors_1.ExceptionError) { context.errors.push(error); // use ExceptionErrors for non Source Errors return utils_1.resolvedErrorPromise; } context.errors.push(new errors_1.ExceptionError(error, constants_1.UNKNOWN_LOCATION)); return utils_1.resolvedErrorPromise; } } function runSubstitution(program, context, options) { const steps = (0, stepper_1.getEvaluationSteps)(program, context, options); if (context.errors.length > 0) { return utils_1.resolvedErrorPromise; } const redexedSteps = []; for (const step of steps) { const redex = (0, stepper_1.getRedex)(step[0], step[1]); const redexed = (0, stepper_1.redexify)(step[0], step[1]); redexedSteps.push({ code: redexed[0], redex: redexed[1], explanation: step[2], function: (0, stepper_1.callee)(redex, context) }); } return Promise.resolve({ status: 'finished', context, value: redexedSteps }); } function runInterpreter(program, context, options) { let it = (0, interpreter_2.evaluateProgram)(program, context); let scheduler; if (context.variant === types_1.Variant.NON_DET) { it = (0, interpreter_non_det_1.nonDetEvaluate)(program, context); scheduler = new schedulers_1.NonDetScheduler(); } else if (options.scheduler === 'async') { scheduler = new schedulers_1.AsyncScheduler(); } else { scheduler = new schedulers_1.PreemptiveScheduler(options.steps); } return scheduler.run(it, context); } async function runNative(program, context, options) { if (!options.isPrelude) { if (context.shouldIncreaseEvaluationTimeout && isPreviousCodeTimeoutError) { context.nativeStorage.maxExecTime *= constants_1.JSSLANG_PROPERTIES.factorToIncreaseBy; } else { context.nativeStorage.maxExecTime = options.originalMaxExecTime; } } // For whatever reason, the transpiler mutates the state of the AST as it is transpiling and inserts // a bunch of global identifiers to it. Once that happens, the infinite loop detection instrumentation // ends up generating code that has syntax errors. As such, we need to make a deep copy here to preserve // the original AST for future use, such as with the infinite loop detector. const transpiledProgram = _.cloneDeep(program); let transpiled; let sourceMapJson; try { switch (context.variant) { case types_1.Variant.GPU: (0, gpu_1.transpileToGPU)(transpiledProgram); break; case types_1.Variant.LAZY: (0, lazy_1.transpileToLazy)(transpiledProgram); break; } ; ({ transpiled, sourceMapJson } = (0, transpiler_1.transpile)(transpiledProgram, context)); let value = (0, evalContainer_1.sandboxedEval)(transpiled, context.nativeStorage); if (context.variant === types_1.Variant.LAZY) { value = (0, operators_1.forceIt)(value); } if (!options.isPrelude) { isPreviousCodeTimeoutError = false; } return { status: 'finished', context, value }; } catch (error) { const isDefaultVariant = options.variant === undefined || options.variant === types_1.Variant.DEFAULT; if (isDefaultVariant && (0, errors_2.isPotentialInfiniteLoop)(error)) { const detectedInfiniteLoop = (0, runtime_1.testForInfiniteLoop)(program, context.previousPrograms.slice(1), context.nativeStorage.loadedModules); if (detectedInfiniteLoop !== undefined) { if (options.throwInfiniteLoops) { context.errors.push(detectedInfiniteLoop); return utils_1.resolvedErrorPromise; } else { error.infiniteLoopError = detectedInfiniteLoop; if (error instanceof errors_1.ExceptionError) { ; error.error.infiniteLoopError = detectedInfiniteLoop; } } } } if (error instanceof runtimeSourceError_1.RuntimeSourceError) { context.errors.push(error); if (error instanceof timeoutErrors_1.TimeoutError) { isPreviousCodeTimeoutError = true; } return utils_1.resolvedErrorPromise; } if (error instanceof errors_1.ExceptionError) { // if we know the location of the error, just throw it if (error.location.start.line !== -1) { context.errors.push(error); return utils_1.resolvedErrorPromise; } else { error = error.error; // else we try to get the location from source map } } const sourceError = await (0, errors_3.toSourceError)(error, sourceMapJson); context.errors.push(sourceError); return utils_1.resolvedErrorPromise; } } function runCSEMachine(program, context, options) { const value = (0, interpreter_1.evaluate)(program, context, options); return (0, interpreter_1.CSEResultPromise)(context, value); } async function sourceRunner(program, context, isVerboseErrorsEnabled, options = {}) { // It is necessary to make a copy of the DEFAULT_SOURCE_OPTIONS object because merge() // will modify it rather than create a new object const theOptions = _.merge({ ...DEFAULT_SOURCE_OPTIONS }, options); context.variant = (0, utils_1.determineVariant)(context, options); if (context.chapter === types_1.Chapter.FULL_JS || context.chapter === types_1.Chapter.FULL_TS || context.chapter === types_1.Chapter.PYTHON_1) { return (0, fullJSRunner_1.fullJSRunner)(program, context, theOptions.importOptions); } (0, validator_1.validateAndAnnotate)(program, context); if (context.errors.length > 0) { return utils_1.resolvedErrorPromise; } if (context.variant === types_1.Variant.CONCURRENT) { return runConcurrent(program, context, theOptions); } if (theOptions.useSubst) { return runSubstitution(program, context, theOptions); } (0, utils_1.determineExecutionMethod)(theOptions, context, program, isVerboseErrorsEnabled); // native, don't evaluate prelude if (context.executionMethod === 'native' && context.variant === types_1.Variant.NATIVE) { return await (0, fullJSRunner_1.fullJSRunner)(program, context, theOptions.importOptions); } // All runners after this point evaluate the prelude. if (context.prelude !== null) { context.unTypecheckedCode.push(context.prelude); const prelude = (0, parser_1.parse)(context.prelude, context); if (prelude === null) { return utils_1.resolvedErrorPromise; } context.prelude = null; await sourceRunner(prelude, context, isVerboseErrorsEnabled, { ...options, isPrelude: true }); return sourceRunner(program, context, isVerboseErrorsEnabled, options); } if (context.variant === types_1.Variant.EXPLICIT_CONTROL || context.executionMethod === 'cse-machine') { if (options.isPrelude) { const preludeContext = { ...context, runtime: { ...context.runtime, debuggerOn: false } }; const result = await runCSEMachine(program, preludeContext, theOptions); // Update object count in main program context after prelude is run context.runtime.objectCount = preludeContext.runtime.objectCount; return result; } return runCSEMachine(program, context, theOptions); } if (context.executionMethod === 'native') { return runNative(program, context, theOptions); } return runInterpreter(program, context, theOptions); } /** * Returns both the Result of the evaluated program, as well as * `verboseErrors`. */ async function sourceFilesRunner(filesInput, entrypointFilePath, context, options = {}) { const preprocessResult = await (0, preprocessor_1.default)(filesInput, entrypointFilePath, context, options); if (!preprocessResult.ok) { return { result: { status: 'error' }, verboseErrors: preprocessResult.verboseErrors }; } const { files, verboseErrors, program: preprocessedProgram } = preprocessResult; context.variant = (0, utils_1.determineVariant)(context, options); // FIXME: The type checker does not support the typing of multiple files, so // we only push the code in the entrypoint file. Ideally, all files // involved in the program evaluation should be type-checked. Either way, // the type checker is currently not used at all so this is not very // urgent. context.unTypecheckedCode.push(files[entrypointFilePath]); const currentCode = { files, entrypointFilePath }; context.shouldIncreaseEvaluationTimeout = _.isEqual(previousCode, currentCode); previousCode = currentCode; context.previousPrograms.unshift(preprocessedProgram); const result = await sourceRunner(preprocessedProgram, context, verboseErrors, options); const resultMapper = (0, mapper_1.mapResult)(context); return { result: resultMapper(result), verboseErrors }; } exports.sourceFilesRunner = sourceFilesRunner; /** * Useful for just running a single line of code with the given context * However, if this single line of code is an import statement, * then the FileGetter is necessary, otherwise all local imports will * fail with ModuleNotFoundError */ function runCodeInSource(code, context, options = {}, defaultFilePath = '/default.js', fileGetter) { return sourceFilesRunner(path => { if (path === defaultFilePath) return Promise.resolve(code); if (!fileGetter) return Promise.resolve(undefined); return fileGetter(path); }, defaultFilePath, context, options); } exports.runCodeInSource = runCodeInSource; //# sourceMappingURL=sourceRunner.js.map