From b428f2e3b326d3f6b91795cefeb20560c498860d Mon Sep 17 00:00:00 2001 From: coolreader18 <33094578+coolreader18@users.noreply.github.com> Date: Sat, 15 Dec 2018 00:49:47 -0600 Subject: [PATCH 1/8] Improve demo site --- .gitignore | 1 + wasm/.gitignore | 2 + wasm/Cargo.lock | 2 + wasm/Cargo.toml | 2 + wasm/app/.gitignore | 2 + wasm/app/index.html | 6 ++ wasm/app/index.js | 24 ++++---- wasm/src/lib.rs | 114 ++++++++++++++++++++++++++++++++++---- wasm/src/wasm_builtins.rs | 23 ++++++-- 9 files changed, 151 insertions(+), 25 deletions(-) create mode 100644 wasm/.gitignore create mode 100644 wasm/app/.gitignore 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..6c59146aa0 100644 --- a/wasm/Cargo.lock +++ b/wasm/Cargo.lock @@ -577,6 +577,8 @@ 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)", + "num-bigint 0.2.1 (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..2c9d22b403 100644 --- a/wasm/Cargo.toml +++ b/wasm/Cargo.toml @@ -14,6 +14,8 @@ rustpython_parser = {path = "../parser"} rustpython_vm = {path = "../vm"} cfg-if = "0.1.2" wasm-bindgen = "0.2" +js-sys = "0.3" +num-bigint = "0.2.1" [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/index.html b/wasm/app/index.html index bb7f2a98fe..ac57dd17db 100644 --- a/wasm/app/index.html +++ b/wasm/app/index.html @@ -23,6 +23,11 @@ height: 2em; font-size: 24px; } + + #error { + color: tomato; + margin-top: 10px; + } @@ -42,6 +47,7 @@

RustPython Demo

count += 1 +

Standard Output

diff --git a/wasm/app/index.js b/wasm/app/index.js index ef223a5a04..c718372eb2 100644 --- a/wasm/app/index.js +++ b/wasm/app/index.js @@ -1,26 +1,30 @@ import * as rp from "rustpython_wasm"; +window.rp = rp; + function runCodeFromTextarea(_) { - const consoleElement = document.getElementById('console'); + const consoleElement = document.getElementById("console"); + const errorElement = document.getElementById("error"); // Clean the console - consoleElement.value = ''; + consoleElement.value = ""; - const code = document.getElementById('code').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'); + if (!code.endsWith("\n")) { + // HACK: if the code doesn't end with newline it crashes. + rp.run_code(code + "\n"); return; } rp.run_code(code); - - } catch(e) { - consoleElement.value = 'Execution failed. Please check if your Python code has any syntax error.'; + } 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..a241e45f8f 100644 --- a/wasm/src/lib.rs +++ b/wasm/src/lib.rs @@ -1,17 +1,95 @@ mod wasm_builtins; +extern crate js_sys; +extern crate num_bigint; extern crate rustpython_vm; extern crate wasm_bindgen; extern crate web_sys; -use rustpython_vm::VirtualMachine; +use num_bigint::BigInt; use rustpython_vm::compile; -use rustpython_vm::pyobject::AttributeProtocol; +use rustpython_vm::pyobject::{self, IdProtocol, 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 { + use pyobject::PyObjectKind; + let py_obj = py_obj.borrow(); + match py_obj.kind { + PyObjectKind::String { ref value } => value.into(), + PyObjectKind::Integer { ref value } => { + if let Some(ref typ) = py_obj.typ { + if typ.is(&vm.ctx.bool_type()) { + let out_bool = value == &BigInt::new(num_bigint::Sign::Plus, vec![1]); + return out_bool.into(); + } + } + let int = vm.ctx.new_int(value.clone()); + rustpython_vm::obj::objfloat::make_float(vm, &int) + .unwrap() + .into() + } + PyObjectKind::Float { ref value } => JsValue::from_f64(*value), + PyObjectKind::Bytes { ref value } => { + let arr = js_sys::Uint8Array::new(&JsValue::from(value.len() as u32)); + for (i, byte) in value.iter().enumerate() { + console::log_1(&JsValue::from(i as u32)); + js_sys::Reflect::set(&arr, &JsValue::from(i as u32), &JsValue::from(*byte)) + .unwrap(); + } + arr.into() + } + PyObjectKind::Sequence { ref elements } => { + let arr = js_sys::Array::new(); + for val in elements { + arr.push(&py_to_js(vm, val)); + } + arr.into() + } + PyObjectKind::Dict { ref elements } => { + let obj = js_sys::Object::new(); + for (key, (_, val)) in elements { + js_sys::Reflect::set(&obj, &key.into(), &py_to_js(vm, val)) + .expect("couldn't set property of object"); + } + obj.into() + } + PyObjectKind::None => JsValue::UNDEFINED, + _ => JsValue::UNDEFINED, + } +} + +fn eval(vm: &mut VirtualMachine, source: &str) -> PyResult { + let code_obj = compile::compile(vm, &source.to_string(), compile::Mode::Exec, None)?; + + let builtins = vm.get_builtin_scope(); + let vars = vm.context().new_scope(Some(builtins)); + vm.run_code_obj(code_obj, vars) +} + +#[wasm_bindgen] +pub fn eval_py(source: &str) -> Result { + let mut vm = VirtualMachine::new(); + + vm.ctx.set_attr( + &vm.builtins, + "print", + vm.context().new_rustfunc(wasm_builtins::builtin_log), + ); + + eval(&mut vm, source) + .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_code(source: &str) -> Result { //add hash in here console::log_1(&"Running RustPython".into()); console::log_1(&"Running code:".into()); @@ -20,14 +98,28 @@ pub fn run_code(source: &str) -> () { 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); + vm.ctx.set_attr( + &vm.builtins, + "print", + vm.context().new_rustfunc(wasm_builtins::builtin_print), + ); - 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..1bf5b1aa4f 100644 --- a/wasm/src/wasm_builtins.rs +++ b/wasm/src/wasm_builtins.rs @@ -4,21 +4,25 @@ //! 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)); @@ -38,3 +42,14 @@ pub fn builtin_print(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { } Ok(vm.get_none()) } + +pub fn builtin_log(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()) +} From f8cce25f695c5b0eb4bf7220728e5a5747236f9c Mon Sep 17 00:00:00 2001 From: coolreader18 <33094578+coolreader18@users.noreply.github.com> Date: Sat, 15 Dec 2018 01:31:44 -0600 Subject: [PATCH 2/8] Formatting; move the `+ '\n'` hack to eval(). --- wasm/app/.prettierrc | 4 ++++ wasm/app/bootstrap.js | 5 +++-- wasm/app/index.html | 24 +++++++++++++++++++----- wasm/app/index.js | 33 ++++++++++++++------------------- wasm/src/lib.rs | 12 ++++++++++-- 5 files changed, 50 insertions(+), 28 deletions(-) create mode 100644 wasm/app/.prettierrc 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 ac57dd17db..a8bcaf44f0 100644 --- a/wasm/app/index.html +++ b/wasm/app/index.html @@ -1,7 +1,7 @@ - + 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:

-

- Alternatively, open up your browser's devtools and play with - rp.eval_py('print("a")') -

- - -
- -

Standard Output

- + + +
+ +

Standard Output

+ - Fork me on GitHub - +

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. +
  • +
+ + + Fork me on GitHub + diff --git a/wasm/app/index.js b/wasm/app/index.js index 4463b47746..d21ac7a045 100644 --- a/wasm/app/index.js +++ b/wasm/app/index.js @@ -6,8 +6,10 @@ window.rp = rp; function runCodeFromTextarea(_) { const consoleElement = document.getElementById('console'); const errorElement = document.getElementById('error'); - // Clean the console + + // Clean the console and errors consoleElement.value = ''; + errorElement.textContent = ''; const code = document.getElementById('code').value; try { diff --git a/wasm/src/lib.rs b/wasm/src/lib.rs index 5a9b97c5da..9ac5677d79 100644 --- a/wasm/src/lib.rs +++ b/wasm/src/lib.rs @@ -5,7 +5,6 @@ extern crate rustpython_vm; extern crate wasm_bindgen; extern crate web_sys; -use js_sys::Reflect; use rustpython_vm::compile; use rustpython_vm::pyobject::{self, PyObjectRef, PyResult}; use rustpython_vm::VirtualMachine; @@ -77,6 +76,12 @@ where #[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( @@ -87,24 +92,11 @@ pub fn eval_py(source: &str, js_injections: Option) -> Result