//
//  pypyjs:  an experimental in-browser python environment.
//

(function() {

// Expose the main pypyjs function at global scope for this file,
// as well as in any module exports or 'window' object we can find.
if (this) {
  this.pypyjs = pypyjs;
}
if (typeof window !== "undefined") {
  window.pypyjs = pypyjs;
}
if (typeof module !== "undefined") {
  if (typeof module.exports !== "undefined") {
    module.exports = pypyjs;
  }
}


// Generic debugging printf.
var debug = function(){};
if (typeof console !== "undefined") {
  debug = console.log.bind(console);
} else if (typeof print !== "undefined" && typeof window === "undefined") {
  debug = print;
}


// Find the directory containing this very file.
// It can be quite difficult depending on execution environment...
if (typeof __dirname === "undefined" || true) {
  var __dirname = "./";
  // A little hackery to find the URL of this very file.
  // Throw an error, then parse the stack trace looking for filenames.
  var errlines = (new Error()).stack.split("\n");
  for (var i = 0; i < errlines.length; i++) {
    var match = /(at Anonymous function \(|at |@)(.+\/)pypyjs.js/.exec(errlines[i]);
    if (match) {
      __dirname = match[2];
      break;
    }
  }
}
if (__dirname.charAt(__dirname.length - 1) !== "/") {
  __dirname += "/";
}


if (typeof Promise === "undefined") {
  var Promise = require('es6-promise').Promise;
}


// Ensure we have reference to a 'FunctionPromise' constructor.
var FunctionPromise = require("./FunctionPromise.js");

if (typeof FunctionPromise === "undefined") {
  throw "FunctionPromise object not found";
}


// Create functions for handling default stdio streams.
// These will be shared by all VM instances by default.
//
// We default stdout and stderr to process outputs if available,
// printing/logging functions otherwise, and /dev/null if nothing
// else is available.  Unfortunately there's no good way to read
// synchronously from stdin in javascript, so that's always /dev/null.

var devNull = {
  stdin: function() { return null; },
  stdout: function() { },
  stderr: function() { }
}

var stdio = {
  stdin: null,
  stdout: null,
  stderr: null
}

stdio.stdin = devNull.stdin;

if (typeof process !== "undefined") {
  if (typeof process.stdout !== "undefined") {
    stdio.stdout = function(x) { process.stdout.write(x); }
  }
  if (typeof process.stderr !== "undefined") {
    stdio.stderr = function(x) { process.stderr.write(x); }
  }
}

var _print, _printErr;
if (typeof window === "undefined") {
  // print, printErr from v8, spidermonkey
  if (typeof print !== "undefined") {
    _print = print;
  }
  if (typeof printErr !== "undefined") {
    _printErr = printErr;
  }
}
if (typeof console !== "undefined") {
  if (typeof _print === "undefined") {
    _print = console.log.bind(console);
  }
  if (typeof _printErr === "undefined") {
    _printErr = console.error.bind(console);
  }
}

if (stdio.stdout == null && typeof _print !== "undefined") {
  // print()/console.log() will add a newline, so we buffer until we
  // receive one and then let it add it for us.
  stdio.stdout = (function() {
    var buffer = [];
    return function(data) {
      for (var i = 0; i < data.length; i++) {
        var x = data.charAt(i);
        if (x !== "\n") {
          buffer.push(x);
        } else {
          _print(buffer.join(""));
          buffer.splice(undefined, buffer.length);
        }
      }
    }
  })();
}

if (stdio.stderr == null && typeof _printErr !== "undefined") {
  // printErr()/console.error() will add a newline, so we buffer until we
  // receive one and then let it add it for us.
  stdio.stderr = (function() {
    var buffer = [];
    return function(data) {
      for (var i = 0; i < data.length; i++) {
        var x = data.charAt(i);
        if (x !== "\n") {
          buffer.push(x);
        } else {
          _printErr(buffer.join(""));
          buffer.splice(undefined, buffer.length);
        }
      }
    }
  })();
}

if (stdio.stdout === null) {
  stdio.stdout = devNull.stdout;
}

if (stdio.stderr === null) {
  stdio.stderr = devNull.stderr;
}

function pypyjs(opts) {

  opts = opts || {};
  this.rootURL = opts.rootURL;
  this.totalMemory = opts.totalMemory || 128 * 1024 * 1024;
  this.autoLoadModules = opts.autoLoadModules || true;
  this._pendingModules = {};
  this._loadedModules = {};
  this._allModules = {};

  // Allow opts to override default IO streams.
  this.stdin = opts.stdin || stdio.stdin;
  this.stdout = opts.stdout || stdio.stdout;
  this.stderr = opts.stderr || stdio.stderr;

  // Default to finding files relative to this very file.
  if (!this.rootURL && !pypyjs.rootURL) {
    pypyjs.rootURL = __dirname;
  }
  if (this.rootURL && this.rootURL.charAt(this.rootURL.length - 1) !== "/") {
    this.rootURL += "/";
  } 

  // If we haven't already done so, fetch and load the code for the VM.
  // We do this once and cache the result for re-use, so that we don't
  // have to pay asmjs compilation overhead each time we create the VM.

  if (! pypyjs._vmBuilderPromise) {
    pypyjs._vmBuilderPromise = this.fetch("pypyjs.vm.js").then((function(xhr) {
      // Parse the compiled code, hopefully asynchronously.
      // Unfortunately our use of Function constructor here doesn't
      // play very well with nodejs, where things like 'module' and
      // 'require' are not in the global scope.  We have to pass them
      // in explicitly as arguments.
      var funcBody = [
        // This is the compiled code for the VM.
        xhr.responseText,
        '\n',
        // Ensure that some functions are available on the Module,
        // for linking with jitted code.
        'if (!Module._jitInvoke && typeof _jitInvoke !== "undefined") {',
        '  Module._jitInvoke = _jitInvoke;',
        '}',
        // Keep some functions that are not exported by default, but
        // which appear in this scope when evaluating the above.
        "Module._emjs_make_handle = _emjs_make_handle;",
        "Module._emjs_free = _emjs_free;",
        // Call dependenciesFulfilled if it won't be done automatically.
        "dependenciesFulfilled=function() { inDependenciesFulfilled(FS); };",
        "if(!memoryInitializer||(!ENVIRONMENT_IS_WEB&&!ENVIRONMENT_IS_WORKER))dependenciesFulfilled();",
      ].join("");
      return FunctionPromise("Module", "inDependenciesFulfilled", "require",
                             "module", "__filename", "__dirname", funcBody)
    }).bind(this));
  }

  // Create a new instance of the compiled VM, bound to local state
  // and a local Module object.

  this._ready = new Promise((function(resolve, reject) {

    // Initialize the Module object.
    // We make it available on this object so that we can use
    // its methods to execute code in the VM.
    var Module = {};
    this._module = Module;
    Module.TOTAL_MEMORY = this.totalMemory;

    // We will set up the filesystem manually when we're ready.
    Module.noFSInit = true;
    Module.thisProgram = "/lib/pypyjs/pypyjs.js";
    Module.filePackagePrefixURL = this.rootURL || pypyjs.rootURL;
    Module.memoryInitializerPrefixURL = this.rootURL || pypyjs.rootURL;
    Module.locateFile = function(name) {
      return (this.rootURL || pypyjs.rootURL) + name;
    }

    // Don't start or stop the program, just set it up.
    // We'll call the API functions ourself.
    Module.noInitialRun = true;
    Module.noExitRuntime = true;

    // Route stdin to an overridable method on the object.
    var stdin = (function stdin() {
      return this.stdin();
    }).bind(this);
 
    // Route stdout to an overridable method on the object.
    // We buffer the output for efficiency.
    var stdout_buffer = []
    var stdout = (function stdout(x) {
      var c = String.fromCharCode(x);
      stdout_buffer.push(c);
      if (c === "\n" || stdout_buffer.length >= 128) {
        this.stdout(stdout_buffer.join(""));
        stdout_buffer = [];
      }
    }).bind(this);

    // Route stderr to an overridable method on the object.
    // We do not buffer stderr.
    var stderr = (function stderr(x) {
      var c = String.fromCharCode(x);
      this.stderr(c);
    }).bind(this);

    // This is where execution will continue after loading
    // the memory initialization data, if any.
    var initializedResolve, initializedReject;
    var initializedP = new Promise(function(resolve, reject) {
      initializedResolve = resolve;
      initializedReject = reject;
    });
    var FS;
    var dependenciesFulfilled = function(fs) {
      FS = fs;
      // Initialize the filesystem state.
      try {
        FS.init(stdin, stdout, stderr);
        Module.FS_createPath("/", "lib/pypyjs/lib_pypy", true, false);
        Module.FS_createPath("/", "lib/pypyjs/lib-python/2.7", true, false);
        initializedResolve();
      } catch (err) {
        initializedReject(err);
      }
    }
 
    // Begin fetching the metadata for available python modules.
    // With luck these can download while we jank around compiling
    // all of that javascript.
    // XXX TODO: also load memory initializer this way.
    var moduleDataP = this.fetch("modules/index.json");

    pypyjs._vmBuilderPromise.then((function(vmBuilder) {
      var args = [
        Module,
        dependenciesFulfilled,
        typeof undefined,
        typeof undefined,
        typeof undefined,
        typeof __dirname
      ];
      // This links the async-compiled module into our Module object.
      vmBuilder.apply(null, args);
      return initializedP;
    }).bind(this)).then((function() {
      // Continue with processing the downloaded module metadata.
      return moduleDataP.then((function(xhr) {
        // Store the module index, and load any preload modules.
        var modIndex = JSON.parse(xhr.responseText);
        this._allModules = modIndex.modules;
        if (modIndex.preload) {
          for (var name in modIndex.preload) {
            this._writeModuleFile(name, modIndex.preload[name]);
          }
        }
        // It's finally safe to launch the VM.
        Module.run();
        Module._rpython_startup_code();
        var pypy_home = Module.intArrayFromString("/lib/pypyjs/pypyjs.js");
        pypy_home = Module.allocate(pypy_home, 'i8', Module.ALLOC_NORMAL);
        Module._pypy_setup_home(pypy_home, 0);
        Module._free(pypy_home);
        var initCode = [
          "import js",
          "import sys; sys.platform = 'js'",
          "import traceback",
          "top_level_scope = {'__name__': '__main__'}"
        ];
        initCode.forEach(function(codeStr) {
          var code = Module.intArrayFromString(codeStr);
          var code = Module.allocate(code, 'i8', Module.ALLOC_NORMAL);
          if (!code) {
            throw new pypyjs.Error('Failed to allocate memory');
          }
          var res = Module._pypy_execute_source(code);
          if (res < 0) {
            throw new pypyjs.Error('Failed to execute python code');
          }
          Module._free(code);
        });
      }).bind(this))
    }).bind(this))
    .then(resolve, reject);
  }).bind(this));

};


// A simple file-fetching wrapper around XMLHttpRequest,
// that treats paths as relative to the pypyjs.js root url.
//
pypyjs.prototype.fetch = function (relpath, responseType) {
  if (typeof window === "undefined") {
    var localStorage = false;
  }
  else {
    var localStorage = window.localStorage;
  }
  var use_cache = pypyjs.cacheKey && localStorage && relpath != "pypyjs.vm.js";
  if (use_cache) {
    var item = localStorage.getItem(pypyjs.cacheKey+':'+relpath);
    if (item) {
      return new Promise((function(resolve, reject) {
        resolve({ responseText: item });
      }))
    }
  }
  // For the web, use XMLHttpRequest.
  if (typeof XMLHttpRequest !== "undefined") {
    return new Promise((function(resolve, reject) {
      var xhr = new XMLHttpRequest();
      xhr.onload = function() {
        if (xhr.status >= 400) {
          reject(xhr)
        } else {
          if (use_cache && xhr.responseText) {
            localStorage.setItem(pypyjs.cacheKey+':'+relpath, xhr.responseText);
          }
          resolve(xhr);
        }
      };
      var rootURL = this.rootURL || pypyjs.rootURL;
      xhr.open('GET', rootURL + relpath, true);
      xhr.responseType = responseType || "text";
      xhr.send(null);
    }).bind(this));
  }
  // For nodejs, use fs.readFile.
  if (typeof fs !== "undefined" && typeof fs.readFile !== "undefined") {
    return new Promise((function(resolve, reject) {
      var rootURL = this.rootURL || pypyjs.rootURL;
      fs.readFile(path.join(rootURL, relpath), function(err, data) {
        if (err) return reject(err);
        resolve({ responseText: data.toString() });
      });
    }).bind(this));
  }
  // For spidermonkey, use snarf (which has a binary read mode).
  if (typeof snarf !== "undefined") {
    return new Promise((function(resolve, reject) {
      var rootURL = this.rootURL || pypyjs.rootURL;
      var data = snarf(rootURL + relpath);
      resolve({ responseText: data });
    }).bind(this));
  }
  // For d8, use read() and readbuffer().
  if (typeof read !== "undefined" && typeof readbuffer !== "undefined") {
    return new Promise((function(resolve, reject) {
      var rootURL = this.rootURL || pypyjs.rootURL;
      var data = read(rootURL + relpath);
      resolve({ responseText: data });
    }).bind(this));
  }
  return new Promise(function(resolve, reject) {
    reject("unable to fetch files");
  });
};

if (typeof localStorage !== "undefined") {
  var localStorage = false;
}

// pypyjs.prototype.fetch = function fetch(relpath, responseType) {
//   // For the web, use XMLHttpRequest.
//   var use_cache = pypyjs.cacheKey && localStorage;
//   if (use_cache) {
//     if (var item = localStorage.getItem(pypyjs.cacheKey+'-'+relpath)) {
//       resolve({ responseText: item });
//     }
//   }
//   if (typeof XMLHttpRequest !== "undefined") {
//     return new Promise((function(resolve, reject) {
//       var xhr = new XMLHttpRequest();
//       xhr.onload = function() {
//         if (xhr.status >= 400) {
//           reject(xhr)
//         } else {
//           console.log(xhr.responseText);
//           if (use_cache && xhr.responseText) {
//             localStorage.setItem(pypyjs.cacheKey+'-'+relpath, xhr.responseText);
//           }
//           resolve(xhr);
//         }
//       };
//       var rootURL = this.rootURL || pypyjs.rootURL;
//       xhr.open('GET', rootURL + relpath, true);
//       xhr.responseType = responseType || "text";
//       xhr.send(null);
//     }).bind(this));
//   }
//   // For nodejs, use fs.readFile.
//   if (typeof fs !== "undefined" && typeof fs.readFile !== "undefined") {
//     return new Promise((function(resolve, reject) {
//       var rootURL = this.rootURL || pypyjs.rootURL;
//       fs.readFile(path.join(rootURL, relpath), function(err, data) {
//         if (err) return reject(err);
//         resolve({ responseText: data.toString() });
//       });
//     }).bind(this));
//   }
//   // For spidermonkey, use snarf (which has a binary read mode).
//   if (typeof snarf !== "undefined") {
//     return new Promise((function(resolve, reject) {
//       var rootURL = this.rootURL || pypyjs.rootURL;
//       var data = snarf(rootURL + relpath);
//       resolve({ responseText: data });
//     }).bind(this));
//   }
//   // For d8, use read() and readbuffer().
//   if (typeof read !== "undefined" && typeof readbuffer !== "undefined") {
//     return new Promise((function(resolve, reject) {
//       var rootURL = this.rootURL || pypyjs.rootURL;
//       var data = read(rootURL + relpath);
//       resolve({ responseText: data });
//     }).bind(this));
//   }
//   return new Promise(function(resolve, reject) {
//     reject("unable to fetch files");
//   });
// };


// Method to execute python source directly in the VM.
//
// This is the basic way to push code into the pypyjs VM.
// Calling code should not use it directly; rather we use it
// as a primitive to build up a nicer execution API.
//
pypyjs.prototype._execute_source = function _execute_source(code) {
  var Module = this._module;
  code = "try:\n" +
         "  " + code + "\n" +
         "except Exception:\n" +
         "  typ, val, tb = sys.exc_info()\n" +
         "  err_name = getattr(typ, '__name__', str(typ))\n" +
         "  err_msg = str(val)\n" +
         "  err_trace = traceback.format_exception(typ, val, tb)\n" +
         "  err_trace = ''.join(err_trace)\n" +
         "  js.globals['pypyjs']._lastErrorName = err_name\n" +
         "  js.globals['pypyjs']._lastErrorMessage = err_msg\n" +
         "  js.globals['pypyjs']._lastErrorTrace = err_trace\n";
  var code_chars = Module.intArrayFromString(code);
  var code_ptr = Module.allocate(code_chars, 'i8', Module.ALLOC_NORMAL);
  if (!code_ptr) {
    return Promise.reject(new pypyjs.Error("Failed to allocate memory"));
  }
  var res = Module._pypy_execute_source(code_ptr);
  Module._free(code_ptr);
  // XXX TODO: races/re-entrancy on _lastError?
  if (pypyjs._lastErrorName) {
    var err = new pypyjs.Error(
      pypyjs._lastErrorName,
      pypyjs._lastErrorMessage,
      pypyjs._lastErrorTrace
    );
    pypyjs._lastErrorName = null;
    pypyjs._lastErrorMessage = null;
    pypyjs._lastErrorTrace = null;
    return Promise.reject(err);
  }
  if (res < 0) {
    return Promise.reject(new pypyjs.Error("Error executing python code"));
  }
  return Promise.resolve(null);
}


function _escape(value) {
  return value.replace(/\\/g, "\\\\").replace(/'/g, "\\'");
}


// Method to determine when the interpreter is ready.
//
// This method returns a promise that will resolve once the interpreter
// is ready for use.
//
pypyjs.prototype.ready = function ready() {
  return this._ready;
}


// Method to execute some python code.
//
// This passes the given python code to the VM for execution.
// It's fairly directly analogous to the "exec" statement in python.
// It is not possible to directly access the result of the code, if any.
// Rather you should store it into a variable and then use the get() method.
//
pypyjs.prototype.exec = function exec(code) {
  return this._ready.then((function() {
    var p = Promise.resolve();
    // Find any "import" statements in the code,
    // and ensure the modules are ready for loading.
    if (this.autoLoadModules) {
      p = p.then((function() {
        return this.findImportedNames(code);
      }).bind(this))
      .then((function(imports) {
        return this.loadModuleData.apply(this, imports);
      }).bind(this))
    }
    // Now we can execute the code in custom top-level scope.
    code = 'exec \'\'\'' + _escape(code) + '\'\'\' in top_level_scope';
    p = p.then((function() {
      return this._execute_source(code);
    }).bind(this));
    return p;
  }).bind(this));
}


// Method to evaluate an expression.
//
// This method evaluates an expression and returns its value (assuming the
// value can be translated into javascript).  It's fairly directly analogous
// to the "eval" function in python.
//
// For backwards-compatibility reasons, it will also evaluate statements.
// This behaviour is deprecated and will be removed in a future release.
//
pypyjs.prototype.eval = function (expr) {
  return this._ready.then((function() {
    // First try to execute it as an expression.
    code = "r = eval('" + _escape(expr) + "', top_level_scope)";
    return this._execute_source(code);
  }).bind(this)).then(
    (function() {
      // If that succeeded, return the result.
      return this.get("r", true)
    }).bind(this),
    (function(err) {
      if (err && err.name && err.name !== "SyntaxError") {
        throw err;
      }
      // If that failed, try again via exec().
      if (typeof console !== "undefined") {
        console.warn("Calling pypyjs.eval() with statements is deprecated.");
        console.warn("Use eval() for expressions, exec() for statements.");
      }
      return this.exec(expr);
    }).bind(this)
  )
}

// Method to evaluate some python code from a file..
//
// This fetches the named file and passes it to the VM for execution.
//
pypyjs.prototype.execfile = function execfile(filename) {
  return this.fetch(filename).then((function(xhr) {
    var code = xhr.responseText;
    return this.exec(code);
  }).bind(this));
}


// Method to read a python variable.
//
// This tries to convert the value in the named python variable into an
// equivalent javascript value and returns it.  It will fail if the variable
// does not exist or contains a value that cannot be converted.
//
pypyjs._resultsID = 0;
pypyjs._resultsMap = {};
pypyjs.prototype.get = function get(name, _fromGlobals) {
  var resid = ""+(pypyjs._resultsID++);
  // We can read from global scope for internal use; don't do this from calling code!
  if (_fromGlobals) {
    var namespace = "globals()";
  } else {
    var namespace = "top_level_scope";
  }
  return this._ready.then((function() {
    var code = namespace + ".get('" + _escape(name) + "', js.undefined)";
    code = "js.convert(" + code + ")"
    code = "js.globals['pypyjs']._resultsMap['" + resid + "'] = " + code;
    return this._execute_source(code);
  }).bind(this)).then((function() {
    var res = pypyjs._resultsMap[resid];
    delete pypyjs._resultsMap[resid];
    return res;
  }).bind(this));
}


// Method to set a python variable to a javascript value.
//
// This generates a handle to the given object, and arranges for the named
// python variable to reference it via that handle.
//
pypyjs.prototype.set = function set(name, value) {
  return this._ready.then((function() {
    var Module = this._module;
    var h = Module._emjs_make_handle(value);
    name = _escape(name);
    var code = "top_level_scope['" + name + "'] = js.Value(" + h + ")";
    return this._execute_source(code);
  }).bind(this));
}


// Method to run an interactive REPL.
//
// This method takes takes callback function implementing the user
// input prompt, and runs a REPL loop using it.  The prompt function
// may either return the input as a string, or a promise resolving to
// the input as a string.  If not specified, we read from stdin (which
// works fine in e.g. nodejs, but is almost certainly not what you want
// in the browser, because it's blocking).
//
pypyjs.prototype.repl = function repl(prmpt) {
  if (!prmpt) {
    // If there's a custom stdin, or we're not in nodejs, then we should
    // default to prompting on stdin/stdout.  For nodejs, we can build
    // an async prompt atop process.stdin.
    var buffer = "";
    if (this.stdin !== devNull.stdin || typeof process === "undefined") {
      prmpt = (function(ps1) {
        var input;
        this.stdout(ps1);
        var c = this.stdin();
        while (c) {
          var idx = c.indexOf("\n");
          if (idx >= 0) {
            var input = buffer + c.substr(0, idx + 1);
            buffer = c.substr(idx + 1);
            return input;
          }
          buffer += c;
          c = this.stdin();
        }
        input = buffer;
        buffer = "";
        return input;
      }).bind(this);
    } else {
      prmpt = (function(ps1) {
        return new Promise((function(resolve, reject) {
          this.stdout(ps1);
          var slurp = function() {
            process.stdin.once("readable", function() {
              var chunk = process.stdin.read();
              if (chunk === null) {
                slurp();
              } else {
                chunk = chunk.toString();
                var idx = chunk.indexOf("\n");
                if (idx < 0) {
                  buffer += chunk;
                  slurp();
                } else {
                  resolve(buffer + chunk.substr(0, idx + 1));
                  buffer = chunk.substr(idx + 1);
                }
              }
            });
          }
          slurp();
        }).bind(this));
      }).bind(this);
    }
  }
  // Set up an InteractiveConsole instance,
  // then loop forever via recursive promises.
  return this._ready.then((function() {
    return this.loadModuleData("code");
  }).bind(this)).then((function() {
    return this._execute_source("import code");
  }).bind(this)).then((function() {
    return this._execute_source("c = code.InteractiveConsole(top_level_scope)");
  }).bind(this)).then((function() {
    return this._repl_loop(prmpt, ">>> ");
  }).bind(this));
}


pypyjs.prototype._repl_loop = function _repl_loop(prmpt, ps1) {
  return Promise.resolve().then((function() {
    // Prompt for input, which may happen via async promise.
    return prmpt.call(this, ps1);
  }).bind(this)).then((function(input) {
    // Push it into the InteractiveConsole, a line at a time.
    var p = Promise.resolve();
    input.split("\n").forEach((function(line) {
      // Find any "import" statements in the code,
      // and ensure the modules are ready for loading.
      if (this.autoLoadModules) {
        p = p.then((function() {
          return this.findImportedNames(line);
        }).bind(this))
        .then((function(imports) {
          return this.loadModuleData.apply(this, imports);
        }).bind(this))
      }
      var code = 'r = c.push(\'' + _escape(line) + '\')';
      p = p.then((function() {
        return this._execute_source(code);
      }).bind(this));
    }).bind(this));
    return p;
  }).bind(this)).then((function() {
    // Check the result from the final push.
    return this.get("r", true)
  }).bind(this)).then((function(r) {
    // If r == 1, we're in a multi-line definition.
    // Adjust the prompt accordingly.
    if (r) {
      return this._repl_loop(prmpt, "... ");
    } else {
      return this._repl_loop(prmpt, ">>> ");
    }
  }).bind(this));
}


// Method to look for "import" statements in a code string.
// Returns a promise that will resolve to a list of imported module names.
//
// XXX TODO: this is far from complete and should not be done with a regex.
// Perhaps we can call into python's "ast" module for this parsing?
//
var importStatementRE = /(from\s+([a-zA-Z0-9_\.]+)\s+)?import\s+\(?\s*([a-zA-Z0-9_\.\*]+(\s+as\s+[a-zA-Z0-9_]+)?[ \t]*,?[ \t]*)+[ \t]*\)?/g
pypyjs.prototype.findImportedNames = function findImportedNames(code) {
  var match = null;
  var imports = [];
  importStatementRE.lastIndex = 0;
  while ((match = importStatementRE.exec(code)) !== null) {
    var relmod = match[2];
    if (relmod) {
      relmod = relmod + ".";
    } else {
      relmod = "";
    }
    var submods = match[0].split("import")[1];
    while (submods && /[\s(]/.test(submods.charAt(0))) {
      submods = submods.substr(1);
    }
    while (submods && /[\s)]/.test(submods.charAt(submods.length - 1))) {
      submods = submods.substr(0, submods.length - 1);
    }
    submods = submods.split(/\s*,\s*/);
    for (var i = 0; i < submods.length; i++) {
      var submod = submods[i];
      submod = submod.split(/\s*as\s*/)[0];
      imports.push(relmod + submod);
    }
  }
  return Promise.resolve(imports);
}


// Method to load the contents of a python module, along with
// any dependencies.  This populates the relevant paths within
// the VMs simulated filesystem so that is can find and import
// the specified module.
//
pypyjs.prototype.loadModuleData = function loadModuleData(/* names */) {
  // Each argument is a name that we want to import.
  // We must find the longest prefix that is an available module
  // and load it along with all its dependencies.
  var modules = Array.prototype.slice.call(arguments);
  return this._ready.then((function() {
    var toLoad = {};
    NEXTNAME: for (var i = 0; i < modules.length; i++) {
      var name = modules[i];
      // Find the nearest containing module for the given name.
      // Note that it may not match a module at all, in which case we ignore it.
      while (true) {
        if (this._allModules[name]) {
          break;
        }
        name = name.substr(0, name.lastIndexOf("."));
        if (!name) continue NEXTNAME;
      }
      this._findModuleDeps(name, toLoad);
    } 
    // Now ensure that each module gets loaded.
    // XXX TODO: we could load these concurrently.
    var p = Promise.resolve();
    for (var name in toLoad) {
      p = p.then(this._makeLoadModuleData(name));
    }
    return p;
  }).bind(this));
}


pypyjs.prototype._findModuleDeps = function _findModuleDeps(name, seen) {
  if (!seen) seen = {};
  var deps = [];
  // If we don't know about this module, ignore it.
  if (!this._allModules[name]) {
    return seen;
  }
  // Depend on any explicitly-named imports.
  var imports = this._allModules[name].imports;
  if (imports) {
    for (var i = 0; i < imports.length; i++) {
      deps.push(imports[i]);
    }
  }
  // Depend on the __init__.py for packages.
  if (this._allModules[name].dir) {
    deps.push(name + ".__init__");
  }
  // Include the parent package, if any.
  var idx = name.lastIndexOf(".");
  if (idx !== -1) {
    deps.push(name.substr(0, idx));
  }
  // Recurse for any previously-unseen dependencies.
  seen[name] = true;
  for (var i = 0; i < deps.length; i++) {
    if (!seen[deps[i]]) {
      this._findModuleDeps(deps[i], seen);
    }
  }
  return seen;
}


pypyjs.prototype._makeLoadModuleData = function _makeLoadModuleData(name) {
  return (function() {
    // If we've already loaded this module, we're done.
    if (this._loadedModules[name]) {
      return Promise.resolve();
    }
    // If we're already in the process of loading it, use the existing promise.
    if (this._pendingModules[name]) {
      return this._pendingModules[name];
    }
    // If it's a package directory, there's not actually anything to do.
    if (this._allModules[name].dir) {
      return Promise.resolve();
    }
    // We need to fetch the module file and write it out.
    var modfile = this._allModules[name].file;
    var p = this.fetch("modules/" + modfile)
    .then((function(xhr) {
      var contents = xhr.responseText;
      this._writeModuleFile(name, contents)
      delete this._pendingModules[name];
    }).bind(this))
    this._pendingModules[name] = p;
    return p;
  }).bind(this);
}


pypyjs.prototype._writeModuleFile = function _writeModuleFile(name, data) {
  var Module = this._module;
  var file = this._allModules[name].file;
  // Create the containing directory first.
  var dir = file.split("/").slice(0, -1).join("/")
  try {
    Module.FS_createPath("/lib/pypyjs/lib_pypy", dir, true, false);
  } catch (e) { }
  // Now we can safely create the file.
  var fullpath = "/lib/pypyjs/lib_pypy/" + file;
  Module.FS_createDataFile(fullpath, "", data, true, false, true);
  this._loadedModules[name] = true;
}


// An error class for reporting python exceptions back to calling code.
// XXX TODO: this could be a lot more user-friendly than a opaque error...

pypyjs.Error = function pypyjsError(name, message, trace) {
  if (name && typeof message === "undefined") {
    message = name;
    name = "";
  }
  this.name = name || "pypyjs.Error";
  this.message = message || "pypyjs Unknown Error";
  this.trace = trace || "";
}
pypyjs.Error.prototype = new Error();
pypyjs.Error.prototype.constructor = pypyjs.Error;



// XXX TODO: expose the filesystem for manipulation by calling code.


// Add convenience methods directly on the 'pypyjs' function, that
// will invoke corresponding methods on a default VM instance.
// This makes it look like 'pypyjs' is a singleton VM instance.

pypyjs._defaultVM = null;
pypyjs.stdin = stdio.stdin
pypyjs.stdout = stdio.stdout
pypyjs.stderr = stdio.stderr

var PUBLIC_NAMES = ['ready', 'exec', 'eval', 'execfile', 'get', 'set',
                    'repl', 'loadModuleData'];

PUBLIC_NAMES.forEach(function(name) {
  pypyjs[name] = function() {
    if (!pypyjs._defaultVM) {
      pypyjs._defaultVM = new pypyjs({
        stdin: function(){ return pypyjs.stdin.apply(this, arguments); },
        stdout: function(){ return pypyjs.stdout.apply(this, arguments); },
        stderr: function(){ return pypyjs.stderr.apply(this, arguments); },
      });
    }
    return pypyjs._defaultVM[name].apply(pypyjs._defaultVM, arguments)
  }
})


// For nodejs, run a repl when invoked directly from the command-line.

return pypyjs;

})();