makeExtensionParser = (
  preferSynchronous,
  languageForExtension,
  languageForModuleSpecifier,
  parserForLanguage,
  moduleTransforms,
  syncModuleTransforms,
) => {
  /** @type {Record<string, ModuleTransform | SyncModuleTransform>} */
  let transforms;

  /**
   * Function returning a generator which executes a parser for a module in
   * either sync or async context.
   *
   * @param {Uint8Array} bytes
   * @param {string} specifier
   * @param {string} location
   * @param {string} packageLocation
   * @param {*} options
   * @returns {Generator<ReturnType<ModuleTransform>|ReturnType<SyncModuleTransform>, ParseResult, EReturn<ModuleTransform|SyncModuleTransform>>}
   */
  function* getParserGenerator(
    bytes,
    specifier,
    location,
    packageLocation,
    options,
  ) {
    /** @type {string} */
    let language;
    const extension = parseExtension(location);

    if (
      !extensionImpliesLanguage(extension) &&
      has(languageForModuleSpecifier, specifier)
    ) {
      language = languageForModuleSpecifier[specifier];
    } else {
      // We should revisit this design decision:
      // Defaulting the language to the extension conflates those namespaces.
      // So, a transform keyed by extension can be used to coerce a language
      // (e.g., .mts to mjs) as a shorthand for configuring a parser for that
      // extension that pre-processes the file before handing off to the
      // parser.
      // But, this forces us to support the case of using weird language
      // names like pre-mjs-json as valid, unconfigured extensions.
      language = languageForExtension[extension] || extension;
    }

    /** @type {string|undefined} */
    let sourceMap;

    if (has(transforms, language)) {
      try {
        ({
          bytes,
          parser: language,
          sourceMap,
        } = yield transforms[language](
          bytes,
          specifier,
          location,
          packageLocation,
          {
            // At time of writing, sourceMap is always undefined, but keeping
            // it here is more resilient if the surrounding if block becomes a
            // loop for multi-step transforms.
            sourceMap,
          },
        ));
      } catch (err) {
        throw Error(
          `Error transforming ${q(language)} source in ${q(location)}: ${err.message}`,
          { cause: err },
        );
      }
    }
    if (!has(parserForLanguage, language)) {
      throw Error(
        `Cannot parse module ${specifier} at ${location}, no parser configured for the language ${language}`,
      );
    }
    const { parse } = parserForLanguage[language];
    return parse(bytes, specifier, location, packageLocation, {
      sourceMap,
      ...options,
    });
  }

  /**
   * @type {ParseFn}
   */
  const syncParser = (bytes, specifier, location, packageLocation, options) => {
    const result = syncTrampoline(
      getParserGenerator,
      bytes,
      specifier,
      location,
      packageLocation,
      options,
    );
    if ('then' in result && typeof result.then === 'function') {
      throw new TypeError(
        'Sync parser cannot return a Thenable; ensure parser is actually synchronous',
      );
    }
    return result;
  };
  syncParser.isSyncParser = true;

  /**
   * @type {AsyncParseFn}
   */
  const asyncParser = async (
    bytes,
    specifier,
    location,
    packageLocation,
    options,
  ) => {
    return asyncTrampoline(
      getParserGenerator,
      bytes,
      specifier,
      location,
      packageLocation,
      options,
    );
  };

  // Unfortunately, typescript was not smart enough to figure out the return
  // type depending on a boolean in arguments, so it has to be
  // AsyncParseFn|ParseFn
  if (preferSynchronous) {
    transforms = syncModuleTransforms;
    return syncParser;
  } else {
    // we can fold syncModuleTransforms into moduleTransforms because
    // async supports sync, but not vice-versa
    transforms = {
      ...syncModuleTransforms,
      ...moduleTransforms,
    };

    return asyncParser;
  }
}
Produces a `parser` that parses the content of a module according to the
corresponding module language, given the extension of the module specifier
and the configuration of the containing compartment. We do not yet support
import assertions and we do not have a mechanism for validating the MIME type
of the module content against the language implied by the extension or file
name.

@param {boolean} preferSynchronous
@param {Record<string, string>} languageForExtension - maps a file extension
to the corresponding language.
@param {Record<string, string>} languageForModuleSpecifier - In a rare case,
the type of a module is implied by package.json and should not be inferred
from its extension.
@param {ParserForLanguage} parserForLanguage
@param {ModuleTransforms} moduleTransforms
@param {SyncModuleTransforms} syncModuleTransforms
@returns {AsyncParseFn|ParseFn}