, print, shouldLoadESM = false) {
  const origModule = globalThis.module;  // Set e.g. when called from the REPL.
  const module = createModule(name);
  const baseUrl = pathToFileURL(module.filename).href;

  if (shouldUseModuleEntryPoint(name, body)) {
    return getOptionValue('--experimental-strip-types') ?
      evalTypeScriptModuleEntryPoint(body, print) :
      evalModuleEntryPoint(body, print);
  }

  const evalFunction = () => runScriptInContext(name,
                                                body,
                                                breakFirstLine,
                                                print,
                                                module,
                                                baseUrl,
                                                undefined,
                                                origModule);

  if (shouldLoadESM) {
    return require('internal/modules/run_main').runEntryPointWithESMLoader(evalFunction);
  }
  evalFunction();
}

const exceptionHandlerState = {
  captureFn: null,
  reportFlag: false,
};

function setUncaughtExceptionCaptureCallback(fn) {
  if (fn === null) {
    exceptionHandlerState.captureFn = fn;
    shouldAbortOnUncaughtToggle[0] = 1;
    process.report.reportOnUncaughtException = exceptionHandlerState.reportFlag;
    return;
  }
  if (typeof fn !== 'function') {
    throw new ERR_INVALID_ARG_TYPE('fn', ['Function', 'null'], fn);
  }
  if (exceptionHandlerState.captureFn !== null) {
    throw new ERR_UNCAUGHT_EXCEPTION_CAPTURE_ALREADY_SET();
  }
  exceptionHandlerState.captureFn = fn;
  shouldAbortOnUncaughtToggle[0] = 0;
  exceptionHandlerState.reportFlag =
    process.report.reportOnUncaughtException === true;
  process.report.reportOnUncaughtException = false;
}

function hasUncaughtExceptionCaptureCallback() {
  return exceptionHandlerState.captureFn !== null;
}

function noop() {}

// XXX(joyeecheung): for some reason this cannot be defined at the top-level
// and exported to be written to process._fatalException, it has to be
// returned as an *anonymous function* wrapped inside a factory function,
// otherwise it breaks the test-timers.setInterval async hooks test -
// this may indicate that node::errors::TriggerUncaughtException() should
// fix up the callback scope before calling into process._fatalException,
// or this function should take extra care of the async hooks before it
// schedules a setImmediate.
function createOnGlobalUncaughtException() {
  // The C++ land node::errors::TriggerUncaughtException() will
  // exit the process if it returns false, and continue execution if it
  // returns true (which indicates that the exception is handled by the user).
  return (er, fromPromise) => {
    // It's possible that defaultTriggerAsyncId was set for a constructor
    // call that threw and was never cleared. So clear it now.
    clearDefaultTriggerAsyncId();

    const type = fromPromise ? 'unhandledRejection' : 'uncaughtException';
    process.emit('uncaughtExceptionMonitor', er, type);
    if (exceptionHandlerState.captureFn !== null) {
      exceptionHandlerState.captureFn(er);
    } else if (!process.emit('uncaughtException', er, type)) {
      // If someone handled it, then great. Otherwise, die in C++ land
      // since that means that we'll exit the process, emit the 'exit' event.
      try {
        if (!process._exiting) {
          process._exiting = true;
          process.exitCode = kGenericUserError;
          process.emit('exit', kGenericUserError);
        }
      } catch {
        // Nothing to be done about it at this point.
      }
      return false;
    }

    // If we handled an error, then make sure any ticks get processed
    // by ensuring that the next Immediate cycle isn't empty.
    require('timers').setImmediate(noop);

    // Emit the after() hooks now that the exception has been handled.
    if (afterHooksExist()) {
      do {
        const asyncId = executionAsyncId();
        if (asyncId === 0)
          popAsyncContext(0);
        else
          emitAfter(asyncId);
      } while (hasAsyncIdStack());
    }
    // And completely empty the id stack, including anything that may be
    // cached on the native side.
    clearAsyncIdStack();

    return true;
  };
}

function readStdin(callback) {
  process.stdin.setEncoding('utf8');

  let code = '';
  process.stdin.on('data', (d) => {
    code += d;
  });

  process.stdin.on('end', () => {
    callback(code);
  });
}

/**
 * Adds the TS message to the error stack.
 *
 * At the 3rd line of the stack, the message is added.
 * @param {string} originalStack The stack to decorate
 * @param {string} newMessage the message to add to the error stack
 * @returns {void}
 */
function decorateCJSErrorWithTSMessage(originalStack, newMessage) {
  let index;
  for (let i = 0; i < 3; i++) {
    index = StringPrototypeIndexOf(originalStack, '\n', index + 1);
  }
  return StringPrototypeSlice(originalStack, 0, index) +
         '\n' + newMessage +
         StringPrototypeSlice(originalStack, index);
}

/**
 *
 * Wrapper of evalScript
 *
 * This function wraps the evaluation of the source code in a try-catch block.
 * If the source code fails to be evaluated, it will retry evaluating the source code
 * with the TypeScript parser.
 *
 * If the source code fails to be evaluated with the TypeScript parser,
 * it will rethrow the original error, adding the TypeScript error message to the stack.
 *
 * This way we don't change the behavior of the code, but we provide a better error message
 * in case of a typescript error.
 * @param {string} name The name of the file
 * @param {string} source The source code to evaluate
 * @param {boolean} breakFirstLine Whether to break on the first line
 * @param {boolean} print If the result should be printed
 * @param {boolean} shouldLoadESM If the code should be loaded as an ESM module
 * @returns {void}
 */
function evalTypeScript(name, source, breakFirstLine, print, shouldLoadESM = false) {
  const origModule = globalThis.module;  // Set e.g. when called from the REPL.
  const module = createModule(name);
  const baseUrl = pathToFileURL(module.filename).href;

  if (shouldUseModuleEntryPoint(name, source)) {
    return evalTypeScriptModuleEntryPoint(source, print);
  }

  let compiledScript;
  // This variable can be modified if the source code is stripped.
  let sourceToRun = source;
  try {
    compiledScript = compileScript(name, source, baseUrl);
  } catch (originalError) {
    try {
      sourceToRun = stripTypeScriptModuleTypes(source, name, false);
      // Retry the CJS/ESM syntax detection after stripping the types.
      if (shouldUseModuleEntryPoint(name, sourceToRun)) {
        return evalTypeScriptModuleEntryPoint(source, print);
      }
      // If the ContextifiedScript was successfully created, execute it.
      // outside the try-catch block to avoid catching runtime errors.
      compiledScript = compileScript(name, sourceToRun, baseUrl);
      // Emit the experimental warning after the code was successfully evaluated.
      emitExperimentalWarning('Type Stripping');
    } catch (tsError) {
      // If it's invalid or unsupported TypeScript syntax, rethrow the original error
      // with the TypeScript error message added to the stack.
      if (tsError.code === 'ERR_INVALID_TYPESCRIPT_SYNTAX' || tsError.code === 'ERR_UNSUPPORTED_TYPESCRIPT_SYNTAX') {
        originalError.stack = decorateCJSErrorWithTSMessage(originalError.stack, tsError.message);
        throw originalError;
      }

      throw tsError;
    }
  }

  const evalFunction = () => runScriptInContext(name,
                                                sourceToRun,
                                                breakFirstLine,
                                                print,
                                                module,
                                                baseUrl,
                                                compiledScript,
                                                origModule);

  if (shouldLoadESM) {
    return require('internal/modules/run_main').runEntryPointWithESMLoader(evalFunction);
  }
  evalFunction();
}

/**
 * Wrapper of evalModuleEntryPoint
 *
 * This function wraps the compilation of the source code in a try-catch block.
 * If the source code fails to be compiled, it will retry transpiling the source code
 * with the TypeScript parser.
 * @param {string} source The source code to evaluate
 * @param {boolean} print If the result should be printed
 * @returns {Promise} The module evaluation promise
 */
function evalTypeScriptModuleEntryPoint(source, print) {
  if (print) {
    throw new ERR_EVAL_ESM_CANNOT_PRINT();
  }

  RegExpPrototypeExec(/^/, ''); // Necessary to reset RegExp statics before user code runs.

  return require('internal/modules/run_main').runEntryPointWithESMLoader(
    async (loader) => {
      const url = getEvalModuleUrl();
      let moduleWrap;
      try {
        // Compile the module to check for syntax errors.
        moduleWrap = loader.createModuleWrap(source, url);
      } catch (originalError) {
        try {
          const strippedSource = stripTypeScriptModuleTypes(source, url, false);
          // If the moduleWrap was successfully created, execute the module job.
          // outside the try-catch block to avoid catching runtime errors.
          moduleWrap = loader.createModuleWrap(strippedSource, url);
          // Emit the experimental warning after the code was successfully compiled.
          emitExperimentalWarning('Type Stripping');
        } catch (tsError) {
          // If it's invalid or unsupported TypeScript syntax, rethrow the original error
          // with the TypeScript error message added to the stack.
          if (tsError.code === 'ERR_INVALID_TYPESCRIPT_SYNTAX' ||
            tsError.code === 'ERR_UNSUPPORTED_TYPESCRIPT_SYNTAX') {
            originalError.stack = `${tsError.message}\n\n${originalError.stack}`;
            throw originalError;
          }

          throw tsError;
        }
      }
      // If the moduleWrap was successfully created either with by just compiling
      // or after transpilation, execute the module job.
      return loader.executeModuleJob(url, moduleWrap, true);
    },
  );
};

/**
 *
 * Function used to shortcut when `--input-type=module-typescript` is set.
 * @param {string} source
 * @param {boolean} print
 */
function parseAndEvalModuleTypeScript(source, print) {
  // We know its a TypeScript module, we can safely emit the experimental warning.
  const strippedSource = stripTypeScriptModuleTypes(source, getEvalModuleUrl());
  evalModuleEntryPoint(strippedSource, print);
}

/**
 * Function used to shortcut when `--input-type=commonjs-typescript` is set
 * @param {string} name The name of the file
 * @param {string} source The source code to evaluate
 * @param {boolean} breakFirstLine Whether to break on the first line
 * @param {boolean} print If the result should be printed
 * @param {boolean} shouldLoadESM If the code should be loaded as an ESM module
 * @returns {void}
 */
function parseAndEvalCommonjsTypeScript(name, source, breakFirstLine, print, shouldLoadESM = false) {
  // We know its a TypeScript module, we can safely emit the experimental warning.
  const strippedSource = stripTypeScriptModuleTypes(source, getEvalModuleUrl());
  evalScript(name, strippedSource, breakFirstLine, print, shouldLoadESM);
}

/**
 *
 * @param {string} name - The filename of the script.
 * @param {string} body - The code of the script.
 * @param {string} baseUrl Path of the parent importing the module.
 * @returns {ContextifyScript} The created contextify script.
 */
function compileScript(name, body, baseUrl) {
  const hostDefinedOptionId = Symbol(name);
  async function importModuleDynamically(specifier, _, importAttributes) {
    const cascadedLoader = require('internal/modules/esm/loader').getOrInitializeCascadedLoader();
    return cascadedLoader.import(specifier, baseUrl, importAttributes);
  }
  return makeContextifyScript(
    body,                    // code
    name,                    // filename,
    0,                       // lineOffset
    0,                       // columnOffset,
    undefined,               // cachedData
    false,                   // produceCachedData
    undefined,               // parsingContext
    hostDefinedOptionId,     // hostDefinedOptionId
    importModuleDynamically, // importModuleDynamically
  );
}

/**
 * @param {string} name - The filename of the script.
 * @param {string} body - The code of the script.
 * @returns {boolean} Whether the module entry point should be evaluated as a module.
 */
function shouldUseModuleEntryPoint(name, body) {
  return getOptionValue('--experimental-detect-module') &&
    getOptionValue('--input-type') === '' &&
    containsModuleSyntax(body, name, null, 'no CJS variables');
}

/**
 *
 * @param {string} name - The filename of the script.
 * @returns {import('internal/modules/esm/loader').CJSModule} The created module.
 */
function createModule(name) {
  const CJSModule = require('internal/modules/cjs/loader').Module;
  const cwd = tryGetCwd();
  const module = new CJSModule(name);
  module.filename = path.join(cwd, name);
  module.paths = CJSModule._nodeModulePaths(cwd);
  return module;
}

/**
 *
 * @param {string} name - The filename of the script.
 * @param {string} body - The code of the script.
 * @param {boolean} breakFirstLine Whether to break on the first line
 * @param {boolean} print If the result should be printed
 * @param {import('internal/modules/esm/loader').CJSModule} module The module
 * @param {string} baseUrl Path of the parent importing the module.
 * @param {object} compiledScript The compiled script.
 * @param {any} origModule The original module.
 * @returns {void}
 */
function runScriptInContext(name, body, breakFirstLine, print, module, baseUrl, compiledScript, origModule) {
  // Create wrapper for cache entry
  const script = `
      globalThis.module = module;
      globalThis.exports = exports;
      globalThis.__dirname = __dirname;
      globalThis.require = require;
      return (main) => main();
    `;
  globalThis.__filename = name;
  RegExpPrototypeExec(/^/, ''); // Necessary to reset RegExp statics before user code runs.
  const result = module._compile(script, `${name}-wrapper`)(() => {
    // If the script was already compiled, use it.
    return runScriptInThisContext(
      compiledScript ?? compileScript(name, body, baseUrl),
      true, !!breakFirstLine);
  });
  if (print) {
    const { log } = require('internal/console/global');

    process.on('exit', () => {
      log(result);
    });
  }
  if (origModule !== undefined)
    globalThis.module = origModule;
}

module.exports = {
  parseAndEvalCommonjsTypeScript,
  parseAndEvalModuleTypeScript,
  readStdin,
  tryGetCwd,
  evalModuleEntryPoint,
  evalTypeScript,
  evalScript,
  onGlobalUncaughtException: createOnGlobalUncaughtException(),
  setUncaughtExceptionCaptureCallback,
  hasUncaughtExceptionCaptureCallback,
};
