mirror of
https://github.com/graphql-python/graphene.git
synced 2024-11-30 13:33:55 +03:00
977 lines
32 KiB
JavaScript
977 lines
32 KiB
JavaScript
//
|
|
// 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;
|
|
|
|
})();
|