diff --git a/.gitignore b/.gitignore
index b1df9e59fa..bdf973f033 100644
--- a/.gitignore
+++ b/.gitignore
@@ -6,3 +6,4 @@ __pycache__
**/*.pytest_cache
.*sw*
.repl_history.txt
+wasm-pack.log
diff --git a/wasm/.gitignore b/wasm/.gitignore
new file mode 100644
index 0000000000..882d44992f
--- /dev/null
+++ b/wasm/.gitignore
@@ -0,0 +1,2 @@
+bin/
+pkg/
diff --git a/wasm/Cargo.lock b/wasm/Cargo.lock
index e4cc8e60e8..9bad7eebad 100644
--- a/wasm/Cargo.lock
+++ b/wasm/Cargo.lock
@@ -577,6 +577,7 @@ name = "rustpython_wasm"
version = "0.1.0"
dependencies = [
"cfg-if 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
+ "js-sys 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
"rustpython_parser 0.0.1",
"rustpython_vm 0.1.0",
"wasm-bindgen 0.2.29 (registry+https://github.com/rust-lang/crates.io-index)",
diff --git a/wasm/Cargo.toml b/wasm/Cargo.toml
index cfca2acb71..4be409dfc2 100644
--- a/wasm/Cargo.toml
+++ b/wasm/Cargo.toml
@@ -14,6 +14,7 @@ rustpython_parser = {path = "../parser"}
rustpython_vm = {path = "../vm"}
cfg-if = "0.1.2"
wasm-bindgen = "0.2"
+js-sys = "0.3"
[dependencies.web-sys]
version = "0.3"
diff --git a/wasm/app/.gitignore b/wasm/app/.gitignore
new file mode 100644
index 0000000000..1eae0cf670
--- /dev/null
+++ b/wasm/app/.gitignore
@@ -0,0 +1,2 @@
+dist/
+node_modules/
diff --git a/wasm/app/.prettierrc b/wasm/app/.prettierrc
new file mode 100644
index 0000000000..96c36f53c9
--- /dev/null
+++ b/wasm/app/.prettierrc
@@ -0,0 +1,4 @@
+{
+ "singleQuote": true,
+ "tabWidth": 4
+}
diff --git a/wasm/app/bootstrap.js b/wasm/app/bootstrap.js
index 7934d627e8..61136ee9b8 100644
--- a/wasm/app/bootstrap.js
+++ b/wasm/app/bootstrap.js
@@ -1,5 +1,6 @@
// A dependency graph that contains any wasm must all be imported
// asynchronously. This `bootstrap.js` file does the single async import, so
// that no one else needs to worry about it again.
-import("./index.js")
- .catch(e => console.error("Error importing `index.js`:", e));
+import('./index.js').catch(e =>
+ console.error('Error importing `index.js`:', e)
+);
diff --git a/wasm/app/index.html b/wasm/app/index.html
index bb7f2a98fe..f46ce1251a 100644
--- a/wasm/app/index.html
+++ b/wasm/app/index.html
@@ -1,35 +1,50 @@
-
-
- RustPython Demo
-
-
-
-
RustPython Demo
-
RustPython is a Python interpreter writter in Rust. This demo is compiled from Rust to WebAssembly so it runs in the browser
-
Please input your python code below and click Run:
-
+
+
+
+
Standard Output
+
Loading...
+
+
Here's some info regarding the rp.eval_py() function
+
+
+ You can return variables from python and get them returned to
+ JS, with the only requirement being that they're serializable
+ with json.dumps.
+
+
+ You can pass an object as the second argument to the function,
+ and that will be available in python as the variable
+ js_vars. Again, only values that can be serialized
+ with JSON.stringify() will go through.
+
+
+
+
+
+
diff --git a/wasm/app/index.js b/wasm/app/index.js
index ef223a5a04..d21ac7a045 100644
--- a/wasm/app/index.js
+++ b/wasm/app/index.js
@@ -1,26 +1,27 @@
-import * as rp from "rustpython_wasm";
+import * as rp from 'rustpython_wasm';
-function runCodeFromTextarea(_) {
- const consoleElement = document.getElementById('console');
- // Clean the console
- consoleElement.value = '';
-
- const code = document.getElementById('code').value;
- try {
- if (!code.endsWith('\n')) { // HACK: if the code doesn't end with newline it crashes.
- rp.run_code(code + '\n');
- return;
- }
+// so people can play around with it
+window.rp = rp;
- rp.run_code(code);
+function runCodeFromTextarea(_) {
+ const consoleElement = document.getElementById('console');
+ const errorElement = document.getElementById('error');
- } catch(e) {
- consoleElement.value = 'Execution failed. Please check if your Python code has any syntax error.';
- console.error(e);
- }
+ // Clean the console and errors
+ consoleElement.value = '';
+ errorElement.textContent = '';
+ const code = document.getElementById('code').value;
+ try {
+ rp.run_from_textbox(code);
+ } catch (e) {
+ errorElement.textContent = e;
+ console.error(e);
+ }
}
-document.getElementById('run-btn').addEventListener('click', runCodeFromTextarea);
+document
+ .getElementById('run-btn')
+ .addEventListener('click', runCodeFromTextarea);
runCodeFromTextarea(); // Run once for demo
diff --git a/wasm/src/lib.rs b/wasm/src/lib.rs
index 36cdf8d089..9ac5677d79 100644
--- a/wasm/src/lib.rs
+++ b/wasm/src/lib.rs
@@ -1,33 +1,143 @@
mod wasm_builtins;
+extern crate js_sys;
extern crate rustpython_vm;
extern crate wasm_bindgen;
extern crate web_sys;
-use rustpython_vm::VirtualMachine;
use rustpython_vm::compile;
-use rustpython_vm::pyobject::AttributeProtocol;
+use rustpython_vm::pyobject::{self, PyObjectRef, PyResult};
+use rustpython_vm::VirtualMachine;
use wasm_bindgen::prelude::*;
use web_sys::console;
+fn py_str_err(vm: &mut VirtualMachine, py_err: &PyObjectRef) -> String {
+ vm.to_pystr(&py_err)
+ .unwrap_or_else(|_| "Error, and error getting error message".into())
+}
+
+fn py_to_js(vm: &mut VirtualMachine, py_obj: PyObjectRef) -> JsValue {
+ let dumps = rustpython_vm::import::import(
+ vm,
+ std::path::PathBuf::default(),
+ "json",
+ &Some("dumps".into()),
+ )
+ .expect("Couldn't get json.dumps function");
+ match vm.invoke(dumps, pyobject::PyFuncArgs::new(vec![py_obj], vec![])) {
+ Ok(value) => {
+ let json = vm.to_pystr(&value).unwrap();
+ js_sys::JSON::parse(&json).unwrap_or(JsValue::UNDEFINED)
+ }
+ Err(_) => JsValue::UNDEFINED,
+ }
+}
+
+fn js_to_py(vm: &mut VirtualMachine, js_val: JsValue) -> PyObjectRef {
+ let json = match js_sys::JSON::stringify(&js_val) {
+ Ok(json) => String::from(json),
+ Err(_) => return vm.get_none(),
+ };
+
+ let loads = rustpython_vm::import::import(
+ vm,
+ std::path::PathBuf::default(),
+ "json",
+ &Some("loads".into()),
+ )
+ .expect("Couldn't get json.loads function");
+
+ let py_json = vm.new_str(json);
+
+ vm.invoke(loads, pyobject::PyFuncArgs::new(vec![py_json], vec![]))
+ // can safely unwrap because we know it's valid JSON
+ .unwrap()
+}
+
+fn eval(vm: &mut VirtualMachine, source: &str, setup_scope: F) -> PyResult
+where
+ F: Fn(&mut VirtualMachine, &PyObjectRef),
+{
+ // HACK: if the code doesn't end with newline it crashes.
+ let mut source = source.to_string();
+ if !source.ends_with('\n') {
+ source.push('\n');
+ }
+
+ let code_obj = compile::compile(vm, &source, compile::Mode::Exec, None)?;
+
+ let builtins = vm.get_builtin_scope();
+ let mut vars = vm.context().new_scope(Some(builtins));
+
+ setup_scope(vm, &mut vars);
+
+ vm.run_code_obj(code_obj, vars)
+}
+
+#[wasm_bindgen]
+pub fn eval_py(source: &str, js_injections: Option) -> Result {
+ if let Some(js_injections) = js_injections.clone() {
+ if !js_injections.is_object() {
+ return Err(js_sys::TypeError::new("The second argument must be an object").into());
+ }
+ }
+
+ let mut vm = VirtualMachine::new();
+
+ vm.ctx.set_attr(
+ &vm.builtins,
+ "print",
+ vm.context()
+ .new_rustfunc(wasm_builtins::builtin_print_console),
+ );
+
+ let res = eval(&mut vm, source, |vm, vars| {
+ let injections = if let Some(js_injections) = js_injections.clone() {
+ js_to_py(vm, js_injections.into())
+ } else {
+ vm.new_dict()
+ };
+
+ vm.ctx.set_item(vars, "js_vars", injections);
+ });
+
+ res.map(|value| py_to_js(&mut vm, value))
+ .map_err(|err| py_str_err(&mut vm, &err).into())
+}
+
#[wasm_bindgen]
-pub fn run_code(source: &str) -> () {
+pub fn run_from_textbox(source: &str) -> Result {
//add hash in here
console::log_1(&"Running RustPython".into());
console::log_1(&"Running code:".into());
console::log_1(&source.to_string().into());
let mut vm = VirtualMachine::new();
- // We are monkey-patching the builtin print to use console.log
- // TODO: moneky-patch sys.stdout instead, after print actually uses sys.stdout
- vm.builtins.set_attr("print", vm.context().new_rustfunc(wasm_builtins::builtin_print));
- let code_obj = compile::compile(&mut vm, &source.to_string(), compile::Mode::Exec, None);
+ // We are monkey-patching the builtin print to use console.log
+ // TODO: monkey-patch sys.stdout instead, after print actually uses sys.stdout
+ vm.ctx.set_attr(
+ &vm.builtins,
+ "print",
+ vm.context().new_rustfunc(wasm_builtins::builtin_print_html),
+ );
- let builtins = vm.get_builtin_scope();
- let vars = vm.context().new_scope(Some(builtins));
- match vm.run_code_obj(code_obj.unwrap(), vars) {
- Ok(_value) => console::log_1(&"Execution successful".into()),
- Err(_) => console::log_1(&"Execution failed".into()),
+ match eval(&mut vm, source, |_, _| {}) {
+ Ok(value) => {
+ console::log_1(&"Execution successful".into());
+ match value.borrow().kind {
+ pyobject::PyObjectKind::None => {}
+ _ => {
+ if let Ok(text) = vm.to_pystr(&value) {
+ wasm_builtins::print_to_html(&text);
+ }
+ }
+ }
+ Ok(JsValue::UNDEFINED)
+ }
+ Err(err) => {
+ console::log_1(&"Execution failed".into());
+ Err(py_str_err(&mut vm, &err).into())
+ }
}
}
diff --git a/wasm/src/wasm_builtins.rs b/wasm/src/wasm_builtins.rs
index c3d601a8cd..99e59f6bee 100644
--- a/wasm/src/wasm_builtins.rs
+++ b/wasm/src/wasm_builtins.rs
@@ -4,27 +4,31 @@
//! desktop.
//! Implements functions listed here: https://docs.python.org/3/library/builtins.html
//!
+extern crate js_sys;
extern crate wasm_bindgen;
extern crate web_sys;
+use js_sys::Array;
use rustpython_vm::obj::objstr;
+use rustpython_vm::pyobject::{PyFuncArgs, PyResult};
use rustpython_vm::VirtualMachine;
-use rustpython_vm::pyobject::{ PyFuncArgs, PyResult };
use wasm_bindgen::JsCast;
-use web_sys::{HtmlTextAreaElement, window};
+use web_sys::{console, window, HtmlTextAreaElement};
// The HTML id of the textarea element that act as our STDOUT
const CONSOLE_ELEMENT_ID: &str = "console";
-fn print_to_html(text: &str) {
+pub fn print_to_html(text: &str) {
let document = window().unwrap().document().unwrap();
- let element = document.get_element_by_id(CONSOLE_ELEMENT_ID).expect("Can't find the console textarea");
+ let element = document
+ .get_element_by_id(CONSOLE_ELEMENT_ID)
+ .expect("Can't find the console textarea");
let textarea = element.dyn_ref::().unwrap();
let value = textarea.value();
textarea.set_value(&format!("{}{}", value, text));
}
-pub fn builtin_print(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult {
+pub fn builtin_print_html(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult {
let mut first = true;
for a in args.args {
if first {
@@ -38,3 +42,14 @@ pub fn builtin_print(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult {
}
Ok(vm.get_none())
}
+
+pub fn builtin_print_console(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult {
+ let arr = Array::new();
+ for a in args.args {
+ let v = vm.to_str(&a)?;
+ let s = objstr::get_value(&v);
+ arr.push(&s.into());
+ }
+ console::log(&arr);
+ Ok(vm.get_none())
+}