mirror of
https://github.com/RustPython/RustPython.git
synced 2026-06-09 22:49:57 +09:00
Merge pull request #230 from coolreader18/master
Improve wasm demo website
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -6,3 +6,4 @@ __pycache__
|
||||
**/*.pytest_cache
|
||||
.*sw*
|
||||
.repl_history.txt
|
||||
wasm-pack.log
|
||||
|
||||
2
wasm/.gitignore
vendored
Normal file
2
wasm/.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
bin/
|
||||
pkg/
|
||||
1
wasm/Cargo.lock
generated
1
wasm/Cargo.lock
generated
@@ -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)",
|
||||
|
||||
@@ -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
2
wasm/app/.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
dist/
|
||||
node_modules/
|
||||
4
wasm/app/.prettierrc
Normal file
4
wasm/app/.prettierrc
Normal file
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"singleQuote": true,
|
||||
"tabWidth": 4
|
||||
}
|
||||
5
wasm/app/bootstrap.js
vendored
5
wasm/app/bootstrap.js
vendored
@@ -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)
|
||||
);
|
||||
|
||||
@@ -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 ▷</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 ▷</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>
|
||||
|
||||
@@ -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
|
||||
|
||||
134
wasm/src/lib.rs
134
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<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())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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())
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user