text: /**
   * @license
   * Copyright 2020 Daniel Wirtz / The AssemblyScript Authors.
   *
   * Licensed under the Apache License, Version 2.0 (the "License");
   * you may not use this file except in compliance with the License.
   * You may obtain a copy of the License at
   *
   *     http://www.apache.org/licenses/LICENSE-2.0
   *
   * Unless required by applicable law or agreed to in writing, software
   * distributed under the License is distributed on an "AS IS" BASIS,
   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   * See the License for the specific language governing permissions and
   * limitations under the License.
   *
   * SPDX-License-Identifier: Apache-2.0
   */
  
  /**
   * @fileoverview Compiler frontend for node.js
   *
   * Uses the low-level API exported from src/index.ts so it works with the compiler compiled to
   * JavaScript as well as the compiler compiled to WebAssembly (eventually). Runs the sources
   * directly through ts-node if distribution files are not present.
   *
   * Can also be packaged as a bundle suitable for in-browser use with the standard library injected
   * in the build step. See dist/asc.js for the bundle and webpack.config.js for building details.
   */
  
  /* global BUNDLE_VERSION, BUNDLE_LIBRARY, BUNDLE_DEFINITIONS */
  
  const fs = require("fs");
  const path = require("path");
  const process = require("process"); // ensure shim
  const utf8 = require("./util/utf8");
  const colorsUtil = require("./util/colors");
  const optionsUtil = require("./util/options");
  const mkdirp = require("./util/mkdirp");
  const find = require("./util/find");
  const binaryen = global.binaryen || (global.binaryen = require("binaryen"));
  
  const dynrequire = typeof __webpack_require__ === "function"
    ? __non_webpack_require__
    : require;
  
  const WIN = process.platform === "win32";
  const EOL = WIN ? "\r\n" : "\n";
  const SEP = WIN ? "\\" : "/";
  
  // Sets up an extension with its definition counterpart and relevant regexes.
  function setupExtension(ext) {
    if (!ext.startsWith(".")) ext = "." + ext;
    return {
      ext,
      ext_d: ".d" + ext,
      re: new RegExp("\\" + ext + "$"),
      re_d: new RegExp("\\.d\\" + ext + "$"),
      re_except_d: new RegExp("^(?!.*\\.d\\" + ext + "$).*\\" + ext + "$"),
      re_index: new RegExp("(?:^|[\\\\\\/])index\\" + ext + "$")
    };
  }
  
  const defaultExtension = setupExtension(".ts");
  
  // Proxy Binaryen's ready event
  Object.defineProperty(exports, "ready", {
    get() { return binaryen.ready; }
  });
  
  // Emscripten adds an `uncaughtException` listener to Binaryen that results in an additional
  // useless code fragment on top of an actual error. suppress this:
  if (process.removeAllListeners) process.removeAllListeners("uncaughtException");
  
  // Use distribution files if present, otherwise run the sources directly.
  function loadAssemblyScriptJS() {
    var exports;
    try {
      // note that this case will always trigger in recent node.js versions for typical installs
      // see: https://nodejs.org/api/packages.html#packages_self_referencing_a_package_using_its_name
      exports = require("assemblyscript");
    } catch (e) {
      try { // `asc` on the command line (unnecessary in recent node)
        exports = dynrequire("../dist/assemblyscript.js");
      } catch (e) {
        try { // `asc` on the command line without dist files (unnecessary in recent node)
          dynrequire("ts-node").register({
            project: path.join(__dirname, "..", "src", "tsconfig.json"),
            skipIgnore: true,
            compilerOptions: { target: "ES2016" }
          });
          dynrequire("../src/glue/js");
          exports = dynrequire("../src");
        } catch (e_ts) {
          try { // `require("dist/asc.js")` in explicit browser tests
            exports = dynrequire("./assemblyscript");
          } catch (e) {
            throw Error(e_ts.stack + "\n---\n" + e.stack);
          }
        }
      }
    }
    return exports;
  }
  
  // Loads the specified bootstrapped Wasm binary of the compiler.
  function loadAssemblyScriptWasm(binaryPath) {
    const loader = require("../lib/loader/umd/index");
    const rtrace = new (require("../lib/rtrace/umd/index").Rtrace)({
      onerror(err, info) { console.log(err, info); },
      getMemory() { return exports.memory; },
      oncollect() {
        var gcProfile = rtrace.gcProfile;
        if (gcProfile && gcProfile.length && fs.writeFileSync) {
          let timestamp = Date.now();
          fs.writeFileSync(`rtrace-gc-profile-${timestamp}.json`, JSON.stringify(gcProfile));
          fs.writeFileSync(`rtrace-gc-profile-${timestamp}.csv`, `time,memory,pause\n${gcProfile.join("\n")}`);
        }
      }
    });
    var { exports } = loader.instantiateSync(fs.readFileSync(binaryPath), rtrace.install({ binaryen }));
    if (exports._start) exports._start();
    return exports;
  }
  
  /** Ensures that an object is a wrapper class instead of just a pointer. */
  function __wrap(ptrOrObj, wrapperClass) {
    if (typeof ptrOrObj === "number") {
      return ptrOrObj === 0 ? null : wrapperClass.wrap(ptrOrObj);
    }
    return ptrOrObj;
  }
  
  var assemblyscript, __newString, __getString, __pin, __unpin, __collect;
  
  function loadAssemblyScript() {
    const wasmArg = process.argv.findIndex(arg => arg == "--wasm");
    if (~wasmArg) {
      let binaryPath = process.argv[wasmArg + 1];
      process.argv.splice(wasmArg, 2);
      assemblyscript = loadAssemblyScriptWasm(binaryPath);
      __newString = assemblyscript.__newString;
      __getString = assemblyscript.__getString;
      __pin = assemblyscript.__pin;
      __unpin = assemblyscript.__unpin;
      __collect = assemblyscript.__collect;
    } else {
      assemblyscript = loadAssemblyScriptJS();
      __newString = str => str;
      __getString = ptr => ptr;
      __pin = ptr => ptr;
      __unpin = ptr => undefined;
      __collect = incremental => undefined;
    }
  }
  loadAssemblyScript();
  
  /** Whether this is a webpack bundle or not. */
  exports.isBundle = typeof BUNDLE_VERSION === "string";
  
  /** AssemblyScript version. */
  exports.version = exports.isBundle ? BUNDLE_VERSION : dynrequire("../package.json").version;
  
  /** Available CLI options. */
  exports.options = require("./asc.json");
  
  /** Prefix used for library files. */
  exports.libraryPrefix = __getString(assemblyscript.LIBRARY_PREFIX.valueOf());
  
  /** Default Binaryen optimization level. */
  exports.defaultOptimizeLevel = 3;
  
  /** Default Binaryen shrink level. */
  exports.defaultShrinkLevel = 1;
  
  /** Bundled library files. */
  exports.libraryFiles = exports.isBundle ? BUNDLE_LIBRARY : (() => { // set up if not a bundle
    const libDir = path.join(__dirname, "..", "std", "assembly");
    const bundled = {};
    find
      .files(libDir, defaultExtension.re_except_d)
      .forEach(file => {
        bundled[file.replace(defaultExtension.re, "")] = fs.readFileSync(path.join(libDir, file), "utf8");
      });
    return bundled;
  })();
  
  /** Bundled definition files. */
  exports.definitionFiles = exports.isBundle ? BUNDLE_DEFINITIONS : (() => { // set up if not a bundle
    const readDefinition = name => fs.readFileSync(
      path.join(__dirname, "..", "std", name, "index" + defaultExtension.ext_d),
      "utf8"
    );
    return {
      assembly: readDefinition("assembly"),
      portable: readDefinition("portable")
    };
  })();
  
  /** Convenience function that parses and compiles source strings directly. */
  exports.compileString = (sources, options) => {
    if (typeof sources === "string") sources = { ["input" + defaultExtension.ext]: sources };
    const output = Object.create({
      stdout: createMemoryStream(),
      stderr: createMemoryStream()
    });
    var argv = [
      "--binaryFile", "binary",
      "--textFile", "text",
    ];
    Object.keys(options || {}).forEach(key => {
      var val = options[key];
      var opt = exports.options[key];
      if (opt && opt.type === "b") {
        if (val) argv.push("--" + key);
      } else {
        if (Array.isArray(val)) {
          val.forEach(val => { argv.push("--" + key, String(val)); });
        }
        else argv.push("--" + key, String(val));
      }
    });
    exports.main(argv.concat(Object.keys(sources)), {
      stdout: output.stdout,
      stderr: output.stderr,
      readFile: name => Object.prototype.hasOwnProperty.call(sources, name) ? sources[name] : null,
      writeFile: (name, contents) => { output[name] = contents; },
      listFiles: () => []
    });
    return output;
  };
  
  /** Runs the command line utility using the specified arguments array. */
  exports.main = function main(argv, options, callback) {
    if (typeof options === "function") {
      callback = options;
      options = {};
    } else if (!options) {
      options = {};
    }
  
    // Bundle semantic version
    let bundleMinorVersion = 0, bundleMajorVersion = 0, bundlePatchVersion = 0;
    const versionParts = (exports.version || "").split(".");
    if (versionParts.length === 3) {
      bundleMajorVersion = parseInt(versionParts[0]) | 0;
      bundleMinorVersion = parseInt(versionParts[1]) | 0;
      bundlePatchVersion = parseInt(versionParts[2]) | 0;
    }
  
    const stdout = options.stdout || process.stdout;
    const stderr = options.stderr || process.stderr;
    const readFile = options.readFile || readFileNode;
    const writeFile = options.writeFile || writeFileNode;
    const listFiles = options.listFiles || listFilesNode;
    const stats = options.stats || createStats();
    let extension = defaultExtension;
  
    // Output must be specified if not present in the environment
    if (!stdout) throw Error("'options.stdout' must be specified");
    if (!stderr) throw Error("'options.stderr' must be specified");
  
    // Parse command line options but do not populate option defaults yet
    const optionsResult = optionsUtil.parse(argv, exports.options, false);
    let opts = optionsResult.options;
    argv = optionsResult.arguments;
  
    if (opts.noColors) {
      colorsUtil.stdout.supported =
      colorsUtil.stderr.supported = false;
    } else {
      colorsUtil.stdout = colorsUtil.from(stdout);
      colorsUtil.stderr = colorsUtil.from(stderr);
    }
  
    // Check for unknown options
    const unknownOpts = optionsResult.unknown;
    if (unknownOpts.length) {
      unknownOpts.forEach(arg => {
        stderr.write(colorsUtil.stderr.yellow("WARNING ") + "Unknown option '" + arg + "'" + EOL);
      });
    }
  
    // Check for trailing arguments
    const trailingArgv = optionsResult.trailing;
    if (trailingArgv.length) {
      stderr.write(colorsUtil.stderr.yellow("WARNING ") + "Unsupported trailing arguments: " + trailingArgv.join(" ") + EOL);
    }
  
    // Use default callback if none is provided
    if (!callback) callback = function defaultCallback(err) {
      var code = 0;
      if (err) {
        stderr.write(colorsUtil.stderr.red("FAILURE ") + err.stack.replace(/^ERROR: /i, "") + EOL);
        code = 1;
      }
      return code;
    };
  
    // Just print the version if requested
    if (opts.version) {
      stdout.write("Version " + exports.version + EOL);
      return callback(null);
    }
  
    // Use another extension if requested
    if (typeof opts.extension === "string") {
      if (/^\.?[0-9a-zA-Z]{1,14}$/.test(opts.extension)) {
        extension = setupExtension(opts.extension);
      } else {
        return callback(Error("Invalid extension: " + opts.extension));
      }
    }
  
    // Set up base directory
    const baseDir = path.normalize(opts.baseDir || ".");
  
    // Check if a config file is present
    let asconfigPath = optionsUtil.resolvePath(opts.config || "asconfig.json", baseDir);
    let asconfigFile = path.basename(asconfigPath);
    let asconfigDir = path.dirname(asconfigPath);
    let asconfig = getAsconfig(asconfigFile, asconfigDir, readFile);
    let asconfigHasEntries = asconfig != null && Array.isArray(asconfig.entries) && asconfig.entries.length;
  
    // Print the help message if requested or no source files are provided
    if (opts.help || (!argv.length && !asconfigHasEntries)) {
      var out = opts.help ? stdout : stderr;
      var color = opts.help ? colorsUtil.stdout : colorsUtil.stderr;
      out.write([
        color.white("SYNTAX"),
        "  " + color.cyan("asc") + " [entryFile ...] [options]",
        "",
        color.white("EXAMPLES"),
        "  " + color.cyan("asc") + " hello" + extension.ext,
        "  " + color.cyan("asc") + " hello" + extension.ext + " -b hello.wasm -t hello.wat",
        "  " + color.cyan("asc") + " hello1" + extension.ext + " hello2" + extension.ext + " -b -O > hello.wasm",
        "  " + color.cyan("asc") + " --config asconfig.json --target release",
        "",
        color.white("OPTIONS"),
      ].concat(
        optionsUtil.help(exports.options, 24, EOL)
      ).join(EOL) + EOL);
      return callback(null);
    }
  
    // I/O must be specified if not present in the environment
    if (!fs.readFileSync) {
      if (readFile === readFileNode) throw Error("'options.readFile' must be specified");
      if (writeFile === writeFileNode) throw Error("'options.writeFile' must be specified");
      if (listFiles === listFilesNode) throw Error("'options.listFiles' must be specified");
    }
  
    // Load additional options from asconfig.json
    const seenAsconfig = new Set();
    seenAsconfig.add(asconfigPath);
    const target = opts.target || "release";
    while (asconfig) {
      // Merge target first
      if (asconfig.targets) {
        const targetOptions = asconfig.targets[target];
        if (targetOptions) {
          opts = optionsUtil.merge(exports.options, opts, targetOptions, asconfigDir);
        }
      }
      // Merge general options
      const generalOptions = asconfig.options;
      if (generalOptions) {
        opts = optionsUtil.merge(exports.options, opts, generalOptions, asconfigDir);
      }
  
      // Append entries
      if (asconfig.entries) {
        for (let entry of asconfig.entries) {
          argv.push(optionsUtil.resolvePath(entry, asconfigDir));
        }
      }
  
      // Look up extended asconfig and repeat
      if (asconfig.extends) {
        asconfigPath = optionsUtil.resolvePath(asconfig.extends, asconfigDir, true);
        asconfigFile = path.basename(asconfigPath);
        asconfigDir = path.dirname(asconfigPath);
        if (seenAsconfig.has(asconfigPath)) break;
        seenAsconfig.add(asconfigPath);
        asconfig = getAsconfig(asconfigFile, asconfigDir, readFile);
      } else {
        break;
      }
    }
  
    // Populate option defaults once user-defined options are set
    optionsUtil.addDefaults(exports.options, opts);
  
    // If showConfig print options and exit
    if (opts.showConfig) {
      stderr.write(JSON.stringify({
        options: opts,
        entries: argv
      }, null, 2));
      return callback(null);
    }
  
    // create a unique set of values
    function unique(values) {
      return [...new Set(values)];
    }
  
    // Set up options
    var program;
    const compilerOptions = __pin(assemblyscript.newOptions());
    assemblyscript.setTarget(compilerOptions, 0);
    assemblyscript.setNoAssert(compilerOptions, opts.noAssert);
    assemblyscript.setExportMemory(compilerOptions, !opts.noExportMemory);
    assemblyscript.setImportMemory(compilerOptions, opts.importMemory);
    assemblyscript.setInitialMemory(compilerOptions, opts.initialMemory >>> 0);
    assemblyscript.setMaximumMemory(compilerOptions, opts.maximumMemory >>> 0);
    assemblyscript.setSharedMemory(compilerOptions, opts.sharedMemory);
    assemblyscript.setImportTable(compilerOptions, opts.importTable);
    assemblyscript.setExportTable(compilerOptions, opts.exportTable);
    assemblyscript.setExplicitStart(compilerOptions, opts.explicitStart);
    assemblyscript.setMemoryBase(compilerOptions, opts.memoryBase >>> 0);
    assemblyscript.setTableBase(compilerOptions, opts.tableBase >>> 0);
    assemblyscript.setSourceMap(compilerOptions, opts.sourceMap != null);
    assemblyscript.setNoUnsafe(compilerOptions, opts.noUnsafe);
    assemblyscript.setPedantic(compilerOptions, opts.pedantic);
    assemblyscript.setLowMemoryLimit(compilerOptions, opts.lowMemoryLimit >>> 0);
    assemblyscript.setExportRuntime(compilerOptions, opts.exportRuntime);
    assemblyscript.setBundleVersion(compilerOptions, bundleMajorVersion, bundleMinorVersion, bundlePatchVersion);
    if (!opts.stackSize && opts.runtime == "incremental") {
      opts.stackSize = assemblyscript.DEFAULT_STACK_SIZE;
    }
    assemblyscript.setStackSize(compilerOptions, opts.stackSize);
  
    // Instrument callback to perform GC
    callback = (function(callback) {
      return function wrappedCallback(err) {
        __unpin(compilerOptions);
        if (program) __unpin(program);
        __collect();
        return callback(err);
      };
    })(callback);
  
    // Add or override aliases if specified
    if (opts.use) {
      let aliases = opts.use;
      for (let i = 0, k = aliases.length; i < k; ++i) {
        let part = aliases[i];
        let p = part.indexOf("=");
        if (p < 0) return callback(Error("Global alias '" + part + "' is invalid."));
        let alias = part.substring(0, p).trim();
        let name = part.substring(p + 1).trim();
        if (!alias.length) return callback(Error("Global alias '" + part + "' is invalid."));
        {
          let aliasPtr = __pin(__newString(alias));
          let namePtr = __newString(name);
          assemblyscript.setGlobalAlias(compilerOptions, aliasPtr, namePtr);
          __unpin(aliasPtr);
        }
      }
    }
  
    // Disable default features if specified
    var features;
    if ((features = opts.disable) != null) {
      if (typeof features === "string") features = features.split(",");
      for (let i = 0, k = features.length; i < k; ++i) {
        let name = features[i].trim();
        let flag = assemblyscript["FEATURE_" + name.replace(/-/g, "_").toUpperCase()];
        if (!flag) return callback(Error("Feature '" + name + "' is unknown."));
        assemblyscript.disableFeature(compilerOptions, flag);
      }
    }
  
    // Enable experimental features if specified
    if ((features = opts.enable) != null) {
      if (typeof features === "string") features = features.split(",");
      for (let i = 0, k = features.length; i < k; ++i) {
        let name = features[i].trim();
        let flag = assemblyscript["FEATURE_" + name.replace(/-/g, "_").toUpperCase()];
        if (!flag) return callback(Error("Feature '" + name + "' is unknown."));
        assemblyscript.enableFeature(compilerOptions, flag);
      }
    }
  
    // Set up optimization levels
    var optimizeLevel = 0;
    var shrinkLevel = 0;
    if (opts.optimize) {
      optimizeLevel = exports.defaultOptimizeLevel;
      shrinkLevel = exports.defaultShrinkLevel;
    }
    if (typeof opts.optimizeLevel === "number") optimizeLevel = opts.optimizeLevel;
    if (typeof opts.shrinkLevel === "number") shrinkLevel = opts.shrinkLevel;
    optimizeLevel = Math.min(Math.max(optimizeLevel, 0), 3);
    shrinkLevel = Math.min(Math.max(shrinkLevel, 0), 2);
    assemblyscript.setOptimizeLevelHints(compilerOptions, optimizeLevel, shrinkLevel);
  
    // Initialize the program
    program = __pin(assemblyscript.newProgram(compilerOptions));
  
    // Collect transforms *constructors* from the `--transform` CLI flag as well
    // as the `transform` option into the `transforms` array.
    let transforms = [];
    // `transform` option from `main()`
    if (Array.isArray(options.transforms)) {
      transforms.push(...options.transforms);
    }
    // `--transform` CLI flag
    if (opts.transform) {
      let tsNodeRegistered = false;
      let transformArgs = unique(opts.transform);
      for (let i = 0, k = transformArgs.length; i < k; ++i) {
        let filename = transformArgs[i].trim();
        if (!tsNodeRegistered && filename.endsWith(".ts")) { // ts-node requires .ts specifically
          dynrequire("ts-node").register({ transpileOnly: true, skipProject: true, compilerOptions: { target: "ES2016" } });
          tsNodeRegistered = true;
        }
        try {
          transforms.push(dynrequire(dynrequire.resolve(filename, { paths: [baseDir, process.cwd()] })));
        } catch (e) {
          return callback(e);
        }
      }
    }
  
    // Fix up the prototype of the transforms’ constructors and instantiate them.
    try {
      transforms = transforms.map(classOrModule => {
        // Except if it’s a legacy module, just pass it through.
        if (typeof classOrModule !== "function") {
          return classOrModule; 
        }
        Object.assign(classOrModule.prototype, {
          program,
          baseDir,
          stdout,
          stderr,
          log: console.error,
          readFile,
          writeFile,
          listFiles
        });
        return new classOrModule();
      });
    } catch (e) {
      return callback(e);
    }
  
    function applyTransform(name, ...args) {
      for (let i = 0, k = transforms.length; i < k; ++i) {
        let transform = transforms[i];
        if (typeof transform[name] === "function") {
          try {
            stats.transformCount++;
            stats.transfromTime += measure(() => {
              transform[name](...args);
            });
          } catch (e) {
            return e;
          }
        }
      }
    }
  
    // Parse library files
    Object.keys(exports.libraryFiles).forEach(libPath => {
      if (libPath.indexOf("/") >= 0) return; // in sub-directory: imported on demand
      stats.parseCount++;
      stats.parseTime += measure(() => {
        let textPtr = __pin(__newString(exports.libraryFiles[libPath]));
        let pathPtr = __newString(exports.libraryPrefix + libPath + extension.ext);
        assemblyscript.parse(program, textPtr, pathPtr, false);
        __unpin(textPtr);
      });
    });
    let customLibDirs = [];
    if (opts.lib) {
      let lib = opts.lib;
      if (typeof lib === "string") lib = lib.split(",");
      customLibDirs.push(...lib.map(p => p.trim()));
      customLibDirs = unique(customLibDirs); // `lib` and `customLibDirs` may include duplicates
      for (let i = 0, k = customLibDirs.length; i < k; ++i) { // custom
        let libDir = customLibDirs[i];
        let libFiles;
        if (libDir.endsWith(extension.ext)) {
          libFiles = [ path.basename(libDir) ];
          libDir = path.dirname(libDir);
        } else {
          libFiles = listFiles(libDir, baseDir) || [];
        }
        for (let j = 0, l = libFiles.length; j < l; ++j) {
          let libPath = libFiles[j];
          let libText = readFile(libPath, libDir);
          if (libText === null) return callback(Error("Library file '" + libPath + "' not found."));
          stats.parseCount++;
          exports.libraryFiles[libPath.replace(extension.re, "")] = libText;
          stats.parseTime += measure(() => {
            let textPtr = __pin(__newString(libText));
            let pathPtr = __newString(exports.libraryPrefix + libPath);
            assemblyscript.parse(program, textPtr, pathPtr, false);
            __unpin(textPtr);
          });
        }
      }
    }
    opts.path = opts.path || [];
  
    // Maps package names to parent directory
    var packageMains = new Map();
    var packageBases = new Map();
  
    // Gets the file matching the specified source path, imported at the given dependee path
    function getFile(internalPath, dependeePath) {
      var sourceText = null; // text reported back to the compiler
      var sourcePath = null; // path reported back to the compiler
  
      const libraryPrefix = exports.libraryPrefix;
      const libraryFiles = exports.libraryFiles;
  
      // Try file.ext, file/index.ext, file.d.ext
      if (!internalPath.startsWith(libraryPrefix)) {
        if ((sourceText = readFile(sourcePath = internalPath + extension.ext, baseDir)) == null) {
          if ((sourceText = readFile(sourcePath = internalPath + "/index" + extension.ext, baseDir)) == null) {
            // portable d.ext: uses the .js file next to it in JS or becomes an import in Wasm
            sourcePath = internalPath + extension.ext;
            sourceText = readFile(internalPath + extension.ext_d, baseDir);
          }
        }
  
      // Search library in this order: stdlib, custom lib dirs, paths
      } else {
        const plainName = internalPath.substring(libraryPrefix.length);
        const indexName = plainName + "/index";
        if (Object.prototype.hasOwnProperty.call(libraryFiles, plainName)) {
          sourceText = libraryFiles[plainName];
          sourcePath = libraryPrefix + plainName + extension.ext;
        } else if (Object.prototype.hasOwnProperty.call(libraryFiles, indexName)) {
          sourceText = libraryFiles[indexName];
          sourcePath = libraryPrefix + indexName + extension.ext;
        } else { // custom lib dirs
          for (const libDir of customLibDirs) {
            if ((sourceText = readFile(plainName + extension.ext, libDir)) != null) {
              sourcePath = libraryPrefix + plainName + extension.ext;
              break;
            } else {
              if ((sourceText = readFile(indexName + extension.ext, libDir)) != null) {
                sourcePath = libraryPrefix + indexName + extension.ext;
                break;
              }
            }
          }
          if (sourceText == null) { // paths
            const match = internalPath.match(/^~lib\/((?:@[^/]+\/)?[^/]+)(?:\/(.+))?/); // ~lib/(pkg)/(path), ~lib/(@org/pkg)/(path)
            if (match) {
              const packageName = match[1];
              const isPackageRoot = match[2] === undefined;
              const filePath = isPackageRoot ? "index" : match[2];
              const basePath = packageBases.has(dependeePath) ? packageBases.get(dependeePath) : ".";
              if (opts.traceResolution) stderr.write("Looking for package '" + packageName + "' file '" + filePath + "' relative to '" + basePath + "'" + EOL);
              const paths = [];
              const parts = path.resolve(baseDir, basePath).split(SEP);
              for (let i = parts.length, k = WIN ? 1 : 0; i >= k; --i) {
                if (parts[i - 1] !== "node_modules") paths.push(parts.slice(0, i).join(SEP) + SEP + "node_modules");
              }
              for (const currentPath of paths.concat(...opts.path).map(p => path.relative(baseDir, p))) {
                if (opts.traceResolution) stderr.write("  in " + path.join(currentPath, packageName) + EOL);
                let mainPath = "assembly";
                if (packageMains.has(packageName)) { // use cached
                  mainPath = packageMains.get(packageName);
                } else { // evaluate package.json
                  let jsonPath = path.join(currentPath, packageName, "package.json");
                  let jsonText = readFile(jsonPath, baseDir);
                  if (jsonText != null) {
                    try {
                      let json = JSON.parse(jsonText);
                      if (typeof json.ascMain === "string") {
                        mainPath = json.ascMain.replace(extension.re_index, "");
                        packageMains.set(packageName, mainPath);
                      }
                    } catch (e) { /* nop */ }
                  }
                }
                const mainDir = path.join(currentPath, packageName, mainPath);
                const plainName = filePath;
                if ((sourceText = readFile(path.join(mainDir, plainName + extension.ext), baseDir)) != null) {
                  sourcePath = libraryPrefix + packageName + "/" + plainName + extension.ext;
                  packageBases.set(sourcePath.replace(extension.re, ""), path.join(currentPath, packageName));
                  if (opts.traceResolution) stderr.write("  -> " + path.join(mainDir, plainName + extension.ext) + EOL);
                  break;
                } else if (!isPackageRoot) {
                  const indexName = filePath + "/index";
                  if ((sourceText = readFile(path.join(mainDir, indexName + extension.ext), baseDir)) !== null) {
                    sourcePath = libraryPrefix + packageName + "/" + indexName + extension.ext;
                    packageBases.set(sourcePath.replace(extension.re, ""), path.join(currentPath, packageName));
                    if (opts.traceResolution) stderr.write("  -> " + path.join(mainDir, indexName + extension.ext) + EOL);
                    break;
                  }
                }
              }
            }
          }
        }
      }
      // No such file
      if (sourceText == null) return null;
      return { sourceText, sourcePath };
    }
  
    // Parses the backlog of imported files after including entry files
    function parseBacklog() {
      var internalPath;
      while ((internalPath = __getString(assemblyscript.nextFile(program)))) {
        let file = getFile(internalPath, assemblyscript.getDependee(program, internalPath));
        if (file) {
          stats.parseCount++;
          stats.parseTime += measure(() => {
            let textPtr = __pin(__newString(file.sourceText));
            let pathPtr = __newString(file.sourcePath);
            assemblyscript.parse(program, textPtr, pathPtr, false);
            __unpin(textPtr);
          });
        } else {
          stats.parseTime += measure(() => {
            let textPtr = __newString(null); // no need to pin
            let pathPtr = __newString(internalPath + extension.ext);
            assemblyscript.parse(program, textPtr, pathPtr, false);
          });
        }
      }
      var numErrors = checkDiagnostics(program, stderr, options.reportDiagnostic);
      if (numErrors) {
        const err = Error(numErrors + " parse error(s)");
        err.stack = err.message; // omit stack
        return callback(err);
      }
    }
  
    // Include runtime before entry files so its setup runs first
    {
      let runtimeName = String(opts.runtime);
      let runtimePath = "rt/index-" + runtimeName;
      let runtimeText = exports.libraryFiles[runtimePath];
      if (runtimeText == null) {
        runtimePath = runtimeName;
        runtimeText = readFile(runtimePath + extension.ext, baseDir);
        if (runtimeText == null) return callback(Error(`Runtime '${runtimeName}' not found.`));
      } else {
        runtimePath = "~lib/" + runtimePath;
      }
      stats.parseCount++;
      stats.parseTime += measure(() => {
        let textPtr = __pin(__newString(runtimeText));
        let pathPtr = __newString(runtimePath + extension.ext);
        assemblyscript.parse(program, textPtr, pathPtr, true);
        __unpin(textPtr);
      });
    }
  
    // Include entry files
    for (let i = 0, k = argv.length; i < k; ++i) {
      const filename = argv[i];
  
      let sourcePath = String(filename).replace(/\\/g, "/").replace(extension.re, "").replace(/[\\/]$/, "");
  
      // Setting the path to relative path
      sourcePath = path.isAbsolute(sourcePath) ? path.relative(baseDir, sourcePath).replace(/\\/g, "/") : sourcePath;
  
      // Try entryPath.ext, then entryPath/index.ext
      let sourceText = readFile(sourcePath + extension.ext, baseDir);
      if (sourceText == null) {
        sourceText = readFile(sourcePath + "/index" + extension.ext, baseDir);
        if (sourceText != null) sourcePath += "/index" + extension.ext;
        else sourcePath += extension.ext;
      } else {
        sourcePath += extension.ext;
      }
  
      stats.parseCount++;
      stats.parseTime += measure(() => {
        let textPtr = __pin(__newString(sourceText));
        let pathPtr = __newString(sourcePath);
        assemblyscript.parse(program, textPtr, pathPtr, true);
        __unpin(textPtr);
      });
    }
  
    // Parse entry files
    {
      let code = parseBacklog();
      if (code) return code;
    }
  
    // Call afterParse transform hook
    {
      let error = applyTransform("afterParse", program.parser);
      if (error) return callback(error);
    }
  
    // Parse additional files, if any
    {
      let code = parseBacklog();
      if (code) return code;
    }
  
    // Print files and exit if listFiles
    if (opts.listFiles) {
      // FIXME: not a proper C-like API
      stderr.write(program.sources.map(s => s.normalizedPath).sort().join(EOL) + EOL);
      return callback(null);
    }
  
    // Pre-emptively initialize the program
    stats.initializeCount++;
    stats.initializeTime += measure(() => {
      try {
        assemblyscript.initializeProgram(program);
      } catch (e) {
        crash("initialize", e);
      }
    });
  
    // Call afterInitialize transform hook
    {
      let error = applyTransform("afterInitialize", program);
      if (error) return callback(error);
    }
  
    var module;
    stats.compileCount++;
    stats.compileTime += measure(() => {
      try {
        module = assemblyscript.compile(program);
      } catch (e) {
        crash("compile", e);
      }
      // From here on we are going to use Binaryen.js, except that we keep pass
      // order as defined in the compiler.
      if (typeof module === "number") { // Wasm
        const original = assemblyscript.Module.wrap(module);
        module = binaryen.wrapModule(original.ref);
        module.optimize = function(...args) {
          original.optimize(...args);
        };
      } else { // JS
        const original = module;
        module = binaryen.wrapModule(module.ref);
        module.optimize = function(...args) {
          original.optimize(...args);
        };
      }
    });
    var numErrors = checkDiagnostics(program, stderr, options.reportDiagnostic);
    if (numErrors) {
      if (module) module.dispose();
      const err = Error(numErrors + " compile error(s)");
      err.stack = err.message; // omit stack
      return callback(err);
    }
  
    // Call afterCompile transform hook
    {
      let error = applyTransform("afterCompile", module);
      if (error) return callback(error);
    }
  
    // Validate the module if requested
    if (!opts.noValidate) {
      stats.validateCount++;
      let isValid;
      stats.validateTime += measure(() => {
        isValid = module.validate();
      });
      if (!isValid) {
        module.dispose();
        return callback(Error("validate error"));
      }
    }
  
    // Set Binaryen-specific options
    if (opts.trapMode === "clamp") {
      stats.optimizeCount++;
      stats.optimizeTime += measure(() => {
        module.runPass("trap-mode-clamp");
      });
    } else if (opts.trapMode === "js") {
      stats.optimizeCount++;
      stats.optimizeTime += measure(() => {
        module.runPass("trap-mode-js");
      });
    } else if (opts.trapMode !== "allow") {
      module.dispose();
      return callback(Error("Unsupported trap mode"));
    }
  
    // Optimize the module
    const debugInfo = opts.debug;
    const converge = opts.converge;
    const runPasses = [];
    if (opts.runPasses) {
      if (typeof opts.runPasses === "string") {
        opts.runPasses = opts.runPasses.split(",");
      }
      if (opts.runPasses.length) {
        opts.runPasses.forEach(pass => {
          if (runPasses.indexOf(pass = pass.trim()) < 0) {
            runPasses.push(pass);
          }
        });
      }
    }
  
    stats.optimizeTime += measure(() => {
      stats.optimizeCount++;
      try {
        module.optimize(optimizeLevel, shrinkLevel, debugInfo);
      } catch (e) {
        crash("optimize", e);
      }
      try {
        module.runPasses(runPasses);
      } catch (e) {
        crash("runPasses", e);
      }
      if (converge) {
        let last;
        try {
          last = module.emitBinary();
        } catch (e) {
          crash("emitBinary (converge)", e);
        }
        do {
          stats.optimizeCount++;
          try {
            module.optimize(optimizeLevel, shrinkLevel, debugInfo);
          } catch (e) {
            crash("optimize (converge)", e);
          }
          try {
            module.runPasses(runPasses);
          } catch (e) {
            crash("runPasses (converge)", e);
          }
          let next;
          try {
            next = module.emitBinary();
          } catch (e) {
            crash("emitBinary (converge)", e);
          }
          if (next.length >= last.length) {
            if (next.length > last.length) {
              stderr.write("Last converge was suboptimial." + EOL);
            }
            break;
          }
          last = next;
        } while (true);
      }
    });
  
    // Prepare output
    if (!opts.noEmit) {
      if (opts.outFile != null) {
        if (/\.was?t$/.test(opts.outFile) && opts.textFile == null) {
          opts.textFile = opts.outFile;
        } else if (/\.js$/.test(opts.outFile) && opts.jsFile == null) {
          opts.jsFile = opts.outFile;
        } else if (opts.binaryFile == null) {
          opts.binaryFile = opts.outFile;
        }
      }
  
      let hasStdout = false;
      let hasOutput = opts.textFile != null
                   || opts.binaryFile != null
                   || opts.jsFile != null
                   || opts.tsdFile != null
                   || opts.idlFile != null;
  
      // Write binary
      if (opts.binaryFile != null) {
        let basename = path.basename(opts.binaryFile);
        let sourceMapURL = opts.sourceMap != null
          ? opts.sourceMap.length
            ? opts.sourceMap
            : "./" + basename + ".map"
          : null;
  
        let wasm;
        stats.emitCount++;
        stats.emitTime += measure(() => {
          try {
            wasm = module.emitBinary(sourceMapURL);
          } catch (e) {
            crash("emitBinary", e);
          }
        });
  
        if (opts.binaryFile.length) {
          writeFile(opts.binaryFile, wasm.binary, baseDir);
        } else {
          writeStdout(wasm.binary);
          hasStdout = true;
        }
  
        // Post-process source map
        if (wasm.sourceMap != "") {
          if (opts.binaryFile.length) {
            let map = JSON.parse(wasm.sourceMap);
            map.sourceRoot = "./" + basename;
            let contents = [];
            map.sources.forEach((name, index) => {
              let text = assemblyscript.getSource(program, __newString(name.replace(extension.re, "")));
              if (text == null) return callback(Error("Source of file '" + name + "' not found."));
              contents[index] = text;
            });
            map.sourcesContent = contents;
            writeFile(path.join(
              path.dirname(opts.binaryFile),
              path.basename(sourceMapURL)
            ).replace(/^\.\//, ""), JSON.stringify(map), baseDir);
          } else {
            stderr.write("Skipped source map (stdout already occupied)" + EOL);
          }
        }
      }
  
      // Write text (also fallback)
      if (opts.textFile != null || !hasOutput) {
        let out;
        if (opts.textFile != null && opts.textFile.length) {
          // use superset text format when extension is `.wast`.
          // Otherwise use official stack IR format (wat).
          let wastFormat = opts.textFile.endsWith('.wast');
          stats.emitCount++;
          stats.emitTime += measure(() => {
            try {
              if (wastFormat) {
                out = module.emitText();
              } else {
                out = module.emitStackIR(true);
              }
            } catch (e) {
              crash("emitText", e);
            }
          });
          writeFile(opts.textFile, out, baseDir);
        } else if (!hasStdout) {
          stats.emitCount++;
          stats.emitTime += measure(() => {
            try {
              out = module.emitStackIR(true);
            } catch (e) {
              crash("emitText", e);
            }
          });
          writeStdout(out);
        }
      }
  
      // Write WebIDL
      if (opts.idlFile != null) {
        let idl;
        if (opts.idlFile.length) {
          stats.emitCount++;
          stats.emitTime += measure(() => {
            try {
              idl = assemblyscript.buildIDL(program);
            } catch (e) {
              crash("buildIDL", e);
            }
          });
          writeFile(opts.idlFile, __getString(idl), baseDir);
        } else if (!hasStdout) {
          stats.emitCount++;
          stats.emitTime += measure(() => {
            try {
              idl = assemblyscript.buildIDL(program);
            } catch (e) {
              crash("buildIDL", e);
            }
          });
          writeStdout(__getString(idl));
          hasStdout = true;
        }
      }
  
      // Write TypeScript definition
      if (opts.tsdFile != null) {
        let tsd;
        if (opts.tsdFile.length) {
          stats.emitCount++;
          stats.emitTime += measure(() => {
            try {
              tsd = assemblyscript.buildTSD(program);
            } catch (e) {
              crash("buildTSD", e);
            }
          });
          writeFile(opts.tsdFile, __getString(tsd), baseDir);
        } else if (!hasStdout) {
          stats.emitCount++;
          stats.emitTime += measure(() => {
            try {
              tsd = assemblyscript.buildTSD(program);
            } catch (e) {
              crash("buildTSD", e);
            }
          });
          writeStdout(__getString(tsd));
          hasStdout = true;
        }
      }
  
      // Write JS (modifies the binary, so must be last)
      if (opts.jsFile != null) {
        let js;
        if (opts.jsFile.length) {
          stats.emitCount++;
          stats.emitTime += measure(() => {
            try {
              js = module.emitAsmjs();
            } catch (e) {
              crash("emitJS", e);
            }
          });
          writeFile(opts.jsFile, js, baseDir);
        } else if (!hasStdout) {
          stats.emitCount++;
          stats.emitTime += measure(() => {
            try {
              js = module.emitAsmjs();
            } catch (e) {
              crash("emitJS", e);
            }
          });
          writeStdout(js);
        }
      }
    }
  
    module.dispose();
    if (opts.measure) {
      printStats(stats, stderr);
    }
  
    return callback(null);
  
    function readFileNode(filename, baseDir) {
      let name = path.resolve(baseDir, filename);
      try {
        let text;
        stats.readCount++;
        stats.readTime += measure(() => {
          text = fs.readFileSync(name, "utf8");
        });
        return text;
      } catch (e) {
        return null;
      }
    }
  
    function writeFileNode(filename, contents, baseDir) {
      try {
        stats.writeCount++;
        stats.writeTime += measure(() => {
          const dirPath = path.resolve(baseDir, path.dirname(filename));
          filename = path.basename(filename);
          const outputFilePath = path.join(dirPath, filename);
          if (!fs.existsSync(dirPath)) mkdirp(dirPath);
          fs.writeFileSync(outputFilePath, contents);
        });
        return true;
      } catch (e) {
        return false;
      }
    }
  
    function listFilesNode(dirname, baseDir) {
      var files;
      try {
        stats.readCount++;
        stats.readTime += measure(() => {
          files = fs.readdirSync(path.join(baseDir, dirname))
            .filter(file => extension.re_except_d.test(file));
        });
        return files;
      } catch (e) {
        return null;
      }
    }
  
    function writeStdout(contents) {
      if (!writeStdout.used) {
        stats.writeCount++;
        writeStdout.used = true;
      }
      stats.writeTime += measure(() => {
        stdout.write(contents);
      });
    }
  };
  
  const toString = Object.prototype.toString;
  
  function isObject(arg) {
    return toString.call(arg) === "[object Object]";
  }
  
  function getAsconfig(file, baseDir, readFile) {
    const contents = readFile(file, baseDir);
    const location = path.join(baseDir, file);
    if (!contents) return null;
  
    // obtain the configuration
    let config;
    try {
      config = JSON.parse(contents);
    } catch(ex) {
      throw new Error("Asconfig is not valid json: " + location);
    }
  
    // validate asconfig shape
    if (config.options && !isObject(config.options)) {
      throw new Error("Asconfig.options is not an object: " + location);
    }
  
    if (config.include && !Array.isArray(config.include)) {
      throw new Error("Asconfig.include is not an array: " + location);
    }
  
    if (config.targets) {
      if (!isObject(config.targets)) {
        throw new Error("Asconfig.targets is not an object: " + location);
      }
      const targets = Object.keys(config.targets);
      for (let i = 0; i < targets.length; i++) {
        const target = targets[i];
        if (!isObject(config.targets[target])) {
          throw new Error("Asconfig.targets." + target + " is not an object: " + location);
        }
      }
    }
  
    if (config.extends && typeof config.extends !== "string") {
      throw new Error("Asconfig.extends is not a string: " + location);
    }
  
    return config;
  }
  
  exports.getAsconfig = getAsconfig;
  
  /** Checks diagnostics emitted so far for errors. */
  function checkDiagnostics(program, stderr, reportDiagnostic) {
    var numErrors = 0;
    do {
      let diagnosticPtr = assemblyscript.nextDiagnostic(program);
      if (!diagnosticPtr) break;
      __pin(diagnosticPtr);
      if (stderr) {
        stderr.write(
          __getString(assemblyscript.formatDiagnostic(diagnosticPtr, stderr.isTTY, true)) +
          EOL + EOL
        );
      }
      if (reportDiagnostic) {
        const diagnostic = __wrap(diagnosticPtr, assemblyscript.DiagnosticMessage);
        const range = __wrap(diagnostic.range, assemblyscript.Range);
        const relatedRange = __wrap(diagnostic.relatedRange, assemblyscript.Range);
        const rangeSource = range ? __wrap(range.source, assemblyscript.Source) : null;
        const relatedRangeSource = relatedRange ? __wrap(relatedRange.source, assemblyscript.Source) : null;
  
        reportDiagnostic({
          message: __getString(diagnostic.message),
          code: diagnostic.code,
          category: diagnostic.category,
          range: range ? {
            start: range.start,
            end: range.end,
            source: rangeSource ? {
              normalizedPath: __getString(rangeSource.normalizedPath)
            } : null,
          } : null,
          relatedRange: relatedRange ? {
            start: relatedRange.start,
            end: relatedRange.end,
            source: relatedRangeSource ? {
              normalizedPath: __getString(relatedRangeSource.normalizedPath)
            } : null
          } : null
        });
      }
      if (assemblyscript.isError(diagnosticPtr)) ++numErrors;
      __unpin(diagnosticPtr);
    } while (true);
    return numErrors;
  }
  
  exports.checkDiagnostics = checkDiagnostics;
  
  /** Creates an empty set of stats. */
  function createStats() {
    return {
      readTime: 0,
      readCount: 0,
      writeTime: 0,
      writeCount: 0,
      parseTime: 0,
      parseCount: 0,
      initializeTime: 0,
      initializeCount: 0,
      compileTime: 0,
      compileCount: 0,
      emitTime: 0,
      emitCount: 0,
      validateTime: 0,
      validateCount: 0,
      optimizeTime: 0,
      optimizeCount: 0,
      transformTime: 0,
      transformCount: 0
    };
  }
  
  exports.createStats = createStats;
  
  /** Measures the execution time of the specified function.  */
  function measure(fn) {
    const start = process.hrtime();
    fn();
    const times = process.hrtime(start);
    return times[0] * 1e9 + times[1];
  }
  
  exports.measure = measure;
  
  function pad(str, len) {
    while (str.length < len) str = " " + str;
    return str;
  }
  
  /** Formats a high resolution time to a human readable string. */
  function formatTime(time) {
    return time ? (time / 1e6).toFixed(3) + " ms" : "n/a";
  }
  
  exports.formatTime = formatTime;
  
  /** Formats and prints out the contents of a set of stats. */
  function printStats(stats, output) {
    const format = (time, count) => pad(formatTime(time), 12) + "  n=" + count;
    (output || process.stdout).write([
      "I/O Read   : " + format(stats.readTime, stats.readCount),
      "I/O Write  : " + format(stats.writeTime, stats.writeCount),
      "Parse      : " + format(stats.parseTime, stats.parseCount),
      "Initialize : " + format(stats.initializeTime, stats.initializeCount),
      "Compile    : " + format(stats.compileTime, stats.compileCount),
      "Emit       : " + format(stats.emitTime, stats.emitCount),
      "Validate   : " + format(stats.validateTime, stats.validateCount),
      "Optimize   : " + format(stats.optimizeTime, stats.optimizeCount),
      "Transform  : " + format(stats.transformTime, stats.transformCount),
      ""
    ].join(EOL) + EOL);
  }
  
  exports.printStats = printStats;
  
  var allocBuffer = typeof global !== "undefined" && global.Buffer
    ? global.Buffer.allocUnsafe || (len => new global.Buffer(len))
    : len => new Uint8Array(len);
  
  /** Creates a memory stream that can be used in place of stdout/stderr. */
  function createMemoryStream(fn) {
    var stream = [];
    stream.write = function(chunk) {
      if (fn) fn(chunk);
      if (typeof chunk === "string") {
        let buffer = allocBuffer(utf8.length(chunk));
        utf8.write(chunk, buffer, 0);
        chunk = buffer;
      }
      this.push(chunk);
    };
    stream.reset = function() {
      stream.length = 0;
    };
    stream.toBuffer = function() {
      var offset = 0, i = 0, k = this.length;
      while (i < k) offset += this[i++].length;
      var buffer = allocBuffer(offset);
      offset = i = 0;
      while (i < k) {
        buffer.set(this[i], offset);
        offset += this[i].length;
        ++i;
      }
      return buffer;
    };
    stream.toString = function() {
      var buffer = this.toBuffer();
      return utf8.read(buffer, 0, buffer.length);
    };
    return stream;
  }
  
  exports.createMemoryStream = createMemoryStream;
  
  /** Compatible TypeScript compiler options for syntax highlighting etc. */
  exports.tscOptions = {
    alwaysStrict: true,
    noImplicitAny: true,
    noImplicitReturns: true,
    noImplicitThis: true,
    noEmitOnError: true,
    strictNullChecks: true,
    experimentalDecorators: true,
    target: "esnext",
    module: "commonjs",
    noLib: true,
    types: [],
    allowJs: false
  };
  
  // Gracefully handle crashes
  function crash(stage, e) {
    const BAR = colorsUtil.red("▌ ");
    console.error([
      EOL,
      BAR, "Whoops, the AssemblyScript compiler has crashed during ", stage, " :-(", EOL,
      BAR, EOL,
      BAR, "Here is a stack trace that may or may not be useful:", EOL,
      BAR, EOL,
      e.stack.replace(/^/mg, BAR), EOL,
      BAR, EOL,
      BAR, "If it refers to the dist files, try to 'npm install source-map-support' and", EOL,
      BAR, "run again, which should then show the actual code location in the sources.", EOL,
      BAR, EOL,
      BAR, "If you see where the error is, feel free to send us a pull request. If not,", EOL,
      BAR, "please let us know: https://github.com/AssemblyScript/assemblyscript/issues", EOL,
      BAR, EOL,
      BAR, "Thank you!", EOL
    ].join(""));
    process.exit(1);
  }
  