makeFunctorFromMap = async (
  readPowers,
  compartmentMap,
  options,
) => {
  const {
    moduleTransforms,
    searchSuffixes,
    sourceMapHook = undefined,
    useEvaluate = false,
    sourceUrlPrefix = undefined,
    format = undefined,
    parserForLanguage: parserForLanguageOption = {},
  } = options || {};

  /** @type {((module: BundleExit) => BundlerKit) | undefined} */
  let makeExitBundlerKit;
  if (format === 'cjs') {
    makeExitBundlerKit = makeCjsExitBundlerKit;
  }

  const parserForLanguage = Object.freeze(
    Object.assign(
      Object.create(null),
      defaultParserForLanguage,
      parserForLanguageOption,
    ),
  );

  const bundlerKitParams = {
    useEvaluate,
    sourceUrlPrefix,
  };

  const {
    compartments,
    entry: { compartment: entryCompartmentName, module: entryModuleSpecifier },
  } = compartmentMap;
  /** @type {string[]} */
  const exitModuleSpecifiers = [];
  /** @type {Sources} */
  const sources = Object.create(null);

  /**
   * @param {string} moduleSpecifier
   * @param {string} compartmentName
   */
  const exitModuleImportHook =
    format !== undefined
      ? /**
         * @param {string} moduleSpecifier
         * @param {string} compartmentName
         */
        async (moduleSpecifier, compartmentName) => {
          const compartmentSources =
            sources[compartmentName] || Object.create(null);
          sources[compartmentName] = compartmentSources;
          compartmentSources[moduleSpecifier] = {
            exit: moduleSpecifier,
          };
          exitModuleSpecifiers.push(moduleSpecifier);
          return { imports: [], exports: [], execute() {} };
        }
      : undefined;

  const makeImportHook = makeImportHookMaker(readPowers, entryCompartmentName, {
    archiveOnly: true,
    sources,
    compartmentDescriptors: compartments,
    searchSuffixes,
    entryCompartmentName,
    entryModuleSpecifier,
    sourceMapHook,
    importHook: exitModuleImportHook,
  });

  // Induce importHook to record all the necessary modules to import the given module specifier.
  const { compartment } = link(compartmentMap, {
    resolve,
    makeImportHook,
    moduleTransforms,
    parserForLanguage,
  });
  await compartment.load(entryModuleSpecifier);

  const { modules, aliases } = sortedModules(
    compartmentMap.compartments,
    sources,
    entryCompartmentName,
    entryModuleSpecifier,
    exitModuleSpecifiers,
  );

  // Create an index of modules so we can resolve import specifiers to the
  // index of the corresponding functor.
  const modulesByKey = Object.create(null);
  for (let index = 0; index < modules.length; index += 1) {
    const module = modules[index];
    module.index = index;
    modulesByKey[module.key] = module;
  }
  const parsersInUse = new Set();
  for (const module of modules) {
    if (module.exit !== undefined) {
      if (makeExitBundlerKit === undefined) {
        // makeExitBundlerKit must have been provided to makeImportHookMaker for any modules with an exit property to have been created.
        throw TypeError('Unreachable');
      }
      module.bundlerKit = makeExitBundlerKit(module);
    } else {
      module.indexedImports = Object.fromEntries(
        Object.entries(module.resolvedImports).map(([importSpecifier, key]) => {
          // UNTIL https://github.com/endojs/endo/issues/1514
          // Prefer: key = aliases.get(key) ?? key;
          const alias = aliases.get(key);
          if (alias != null) {
            key = alias;
          }
          const module = modulesByKey[key];
          if (module === undefined) {
            throw new Error(
              `Unable to locate module for key ${q(key)} import specifier ${q(
                importSpecifier,
              )}`,
            );
          }
          const { index } = module;
          return [importSpecifier, index];
        }),
      );
      parsersInUse.add(module.parser);
      module.bundlerKit = getBundlerKitForModule(module, bundlerKitParams);
    }
  }

  // Some bundles appeal to the host module system appropriate to their format
  // like `require` for bundles used as CommonJS modules.
  // Each module in the modules array is constructed by a language-specific bundler kit,
  // and in the case of an exit module, is a bundler kit made with
  // makeExitBundlerKit, like makeCjsExitBundlerKit.
  // This will generate a module initialization runtime that in turn needs this
  // namespaceCells utility function to take a host module exports namespace
  // and turn it into a bank of cells for importing and exporting the
  // properties of the module exports namespace object.
  const exitNamespaces =
    exitModuleSpecifiers.length === 0
      ? ''
      : `\
  const namespaceCells = namespace => fromEntries(
    getOwnPropertyNames(namespace)
    .map(name => [name, {
      get() {
        return get(namespace, name);
      },
      set() {
        throw new TypeError('Non-writable export');
      },
      observe(observer) {
        observer(get(namespace, name));
      },
      enumerable: true,
    }])
  );
`;

  // The linkage runtime creates a cell for every value exported by any of the
  // bundled modules.
  // The interface of a cell is very much like a getter/setter property
  // deescriptor, and additionally has a method for registering an observer to
  // notice when a variable is changed in its originating module, to support
  // live bindings.
  // Each module language defines its own behavior for the generation of its
  // exported cells.
  // After all cells are allocated, each language gets a second opportunity
  // to introduce bindings for cells that the module re-exports from another
  // module, but does not itself own.
  const runtimeLinkageCells = `\
  const cell = (name, value = undefined) => {
    const observers = [];
    return freeze({
      get: freeze(() => {
        return value;
      }),
      set: freeze((newValue) => {
        value = newValue;
        for (let i = 0; i < observers.length; i += 1) {
          const observe = observers[i];
          observe(value);
        }
      }),
      observe: freeze((observe) => {
        observers.push(observe);
        observe(value);
      }),
      enumerable: true,
    });
  };

  const cells = [
${''.concat(...modules.map(m => m.bundlerKit.getCells()))}\
  ];

${''.concat(...modules.map(m => m.bundlerKit.getReexportsWiring()))}\
`;

  // The linker runtime includes a parallel array of module exports namespace
  // objects for each bundled module, for each respective index of the module
  // functors array.
  // Each namespace has a special '*' property for the namespace object itself,
  // which is what modules obtain with `import * as x from 'x'` notation.
  const moduleNamespaces = `\
  const namespaces = cells.map(cells => freeze(create(null, {
    ...cells,
    // Make this appear like an ESM module namespace object.
    [Symbol.toStringTag]: {
      value: 'Module',
      writable: false,
      enumerable: false,
      configurable: false,
    },
  })));

  for (let index = 0; index < namespaces.length; index += 1) {
    cells[index]['*'] = cell('*', namespaces[index]);
  }
`;

  // Each language in use within the bundle has an opportunity to inject
  // utilities into the bundle runtime that it can use in the shared lexical
  // scope of module execution.
  // CommonJS in particular injects a utility function here, if the script
  // entrains any CommonJS modules.
  const languageRuntimeExtensions = `\
${''.concat(...Array.from(parsersInUse).map(parser => getRuntime(parser)))}\
`;

  // This section of the linker runtime causes each of the modules to execute
  // in topological order, using a language-specific calling convention to
  // link its imports and exports to other modules.
  const moduleExecutionRuntime = `\
${''.concat(...modules.map(m => m.bundlerKit.getFunctorCall()))}\
`;

  // The linker runtime receives an array of language-specific representations
  // of each module, which in the simplest case is just a function and a
  // runtime initialization calling convention (a functor).
  // Then, in the style of partial application, it receives runtime options.
  // When driven by makeScript, the script will statically apply the options,
  // but with makeFunctor, the runtime must evaluate and apply runtime options.
  // Scripts are suitable for injection with <script> tags on the web, whereas
  // functors require use of an evaluator at runtime.
  const linkerRuntime = `functors => options => {
  'use strict';

  const {
    Map,
    Object,
    ReferenceError,
    Reflect,
    TypeError,
  } = globalThis;
  const {
    create,
    defineProperties,
    defineProperty,
    freeze,
    fromEntries,
    getOwnPropertyDescriptors,
    getOwnPropertyNames,
    keys,
  } = Object;
  const { get, set } = Reflect;

  const {
${
  !useEvaluate
    ? ''
    : `\
    evaluate = eval,
    sourceUrlPrefix = ${JSON.stringify(sourceUrlPrefix)},
`
}\
${
  format !== 'cjs'
    ? ''
    : `\
    require: tryRequire = typeof require === 'function' ? require : specifier => {
      throw new TypeError('Cannot import host module: ' + specifier);
    },
`
}\
  } = options || {};

${
  !useEvaluate
    ? ''
    : `\
  const evaluateSource = (source, sourceUrl) => {
    return evaluate(source + '\\n//# sourceURL=' + sourceUrlPrefix + sourceUrl + '\\n');
  };`
}\

${exitNamespaces}\

${runtimeLinkageCells}\

${moduleNamespaces}\

${languageRuntimeExtensions}\

${moduleExecutionRuntime}\

  return cells[cells.length - 1]['*'].get();
}`;

  // An array of language-specific representations of each bundled module,
  // which in the simplest case is a function that must be initialized by the
  // linkage runtime using a calling convention.
  // We pass this array into the linkage runtime rather than embedding it in
  // the linkage runtime in order to assure that the runtime's lexical context
  // doesn't overshadow each module's lexical scope.
  const moduleFunctors = `[
${''.concat(
  ...modules.map(
    (m, index) => `\
// === ${index}. ${m.sourceDirname} ${m.moduleSpecifier} ===
${m.bundlerKit.getFunctor()}`,
  ),
)}\
]`;

  // Functors partially apply the linker runtime.
  // Scripts go on to apply static options and execute immediately.
  return `(${linkerRuntime})(${moduleFunctors})`;
}
@param {ReadFn | ReadPowers | MaybeReadPowers} readPowers
@param {CompartmentMapDescriptor} compartmentMap
@param {BundleOptions} [options]
@returns {Promise<string>}