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