Merge pull request #230 from coolreader18/master

Improve wasm demo website
This commit is contained in:
Shing Lyu
2018-12-21 16:22:34 +01:00
committed by GitHub
11 changed files with 247 additions and 71 deletions

1
.gitignore vendored
View File

@@ -6,3 +6,4 @@ __pycache__
**/*.pytest_cache
.*sw*
.repl_history.txt
wasm-pack.log

2
wasm/.gitignore vendored Normal file
View File

@@ -0,0 +1,2 @@
bin/
pkg/

1
wasm/Cargo.lock generated
View File

@@ -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)",

View File

@@ -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"

2
wasm/app/.gitignore vendored Normal file
View File

@@ -0,0 +1,2 @@
dist/
node_modules/

4
wasm/app/.prettierrc Normal file
View File

@@ -0,0 +1,4 @@
{
"singleQuote": true,
"tabWidth": 4
}

View File

@@ -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)
);

View File

@@ -1,35 +1,50 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>RustPython Demo</title>
<style type="text/css" media="screen">
textarea {
font-family: monospace;
}
<head>
<meta charset="utf-8" />
<title>RustPython Demo</title>
<style type="text/css" media="screen">
textarea {
font-family: monospace;
resize: vertical;
}
#code {
height: 35vh;
width: 95vw;
}
#code {
height: 35vh;
width: 95vw;
}
#console {
height: 35vh;
width: 95vw;
}
#console {
height: 35vh;
width: 95vw;
}
#run-btn {
width: 6em;
height: 2em;
font-size: 24px;
}
</style>
</head>
<body>
<h1>RustPython Demo</h1>
<p>RustPython is a Python interpreter writter in Rust. This demo is compiled from Rust to WebAssembly so it runs in the browser</p>
<p>Please input your python code below and click <kbd>Run</kbd>:</p>
<textarea id="code">n1 = 0
#run-btn {
width: 6em;
height: 2em;
font-size: 24px;
}
#error {
color: tomato;
margin-top: 10px;
font-family: monospace;
}
</style>
</head>
<body>
<h1>RustPython Demo</h1>
<p>
RustPython is a Python interpreter writter in Rust. This demo is
compiled from Rust to WebAssembly so it runs in the browser
</p>
<p>Please input your python code below and click <kbd>Run</kbd>:</p>
<p>
Alternatively, open up your browser's devtools and play with
<code>rp.eval_py('print("a")')</code>
</p>
<textarea id="code">
n1 = 0
n2 = 1
count = 0
until = 10
@@ -40,12 +55,35 @@ while count < until:
print(n1)
n1, n2 = n2, n1 + n2
count += 1
</textarea>
<button id="run-btn">Run &#9655;</button>
<script src="./bootstrap.js"></script>
<h3>Standard Output</h3>
<textarea id="console">Loading...</textarea>
<a href="https://github.com/RustPython/RustPython"><img style="position: absolute; top: 0; right: 0; border: 0;" src="https://s3.amazonaws.com/github/ribbons/forkme_right_green_007200.png" alt="Fork me on GitHub"></a>
</body>
</textarea>
<button id="run-btn">Run &#9655;</button>
<div id="error"></div>
<script src="./bootstrap.js"></script>
<h3>Standard Output</h3>
<textarea id="console">Loading...</textarea>
<p>Here's some info regarding the <code>rp.eval_py()</code> function</p>
<ul>
<li>
You can return variables from python and get them returned to
JS, with the only requirement being that they're serializable
with <code>json.dumps</code>.
</li>
<li>
You can pass an object as the second argument to the function,
and that will be available in python as the variable
<code>js_vars</code>. Again, only values that can be serialized
with <code>JSON.stringify()</code> will go through.
</li>
</ul>
<!-- "Fork me on GitHub" banner -->
<a href="https://github.com/RustPython/RustPython"
><img
style="position: absolute; top: 0; right: 0; border: 0;"
src="https://s3.amazonaws.com/github/ribbons/forkme_right_green_007200.png"
alt="Fork me on GitHub"
/></a>
</body>
</html>

View File

@@ -1,26 +1,27 @@
import * as rp from "rustpython_wasm";
import * as rp from 'rustpython_wasm';
// so people can play around with it
window.rp = rp;
function runCodeFromTextarea(_) {
const consoleElement = document.getElementById('console');
// Clean the console
consoleElement.value = '';
const consoleElement = document.getElementById('console');
const errorElement = document.getElementById('error');
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;
// 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);
}
rp.run_code(code);
} catch(e) {
consoleElement.value = 'Execution failed. Please check if your Python code has any syntax error.';
console.error(e);
}
}
document.getElementById('run-btn').addEventListener('click', runCodeFromTextarea);
document
.getElementById('run-btn')
.addEventListener('click', runCodeFromTextarea);
runCodeFromTextarea(); // Run once for demo

View File

@@ -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<F>(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 run_code(source: &str) -> () {
pub fn eval_py(source: &str, js_injections: Option<js_sys::Object>) -> Result<JsValue, JsValue> {
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_from_textbox(source: &str) -> Result<JsValue, JsValue> {
//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));
// 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 code_obj = compile::compile(&mut vm, &source.to_string(), compile::Mode::Exec, None);
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())
}
}
}

View File

@@ -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::<HtmlTextAreaElement>().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())
}