Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,4 @@ __pycache__
**/*.pytest_cache
.*sw*
.repl_history.txt
wasm-pack.log
2 changes: 2 additions & 0 deletions wasm/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
bin/
pkg/
2 changes: 2 additions & 0 deletions wasm/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions wasm/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Comment thread
coolreader18 marked this conversation as resolved.
Outdated

[dependencies.web-sys]
version = "0.3"
Expand Down
2 changes: 2 additions & 0 deletions wasm/app/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
dist/
node_modules/
4 changes: 4 additions & 0 deletions wasm/app/.prettierrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"singleQuote": true,
"tabWidth": 4
}
5 changes: 3 additions & 2 deletions wasm/app/bootstrap.js
Original file line number Diff line number Diff line change
@@ -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)
);
30 changes: 25 additions & 5 deletions wasm/app/index.html
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta charset="utf-8" />
<title>RustPython Demo</title>
<style type="text/css" media="screen">
textarea {
Expand All @@ -23,13 +23,26 @@
height: 2em;
font-size: 24px;
}

#error {
color: tomato;
margin-top: 10px;
}
</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>
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
<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
Expand All @@ -40,12 +53,19 @@ <h1>RustPython Demo</h1>
print(n1)
n1, n2 = n2, n1 + n2
count += 1
</textarea>

</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>

<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>
<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>
35 changes: 17 additions & 18 deletions wasm/app/index.js
Original file line number Diff line number Diff line change
@@ -1,26 +1,25 @@
import * as rp from "rustpython_wasm";
import * as rp from 'rustpython_wasm';

// so people can play around with it
window.rp = rp;
Comment thread
coolreader18 marked this conversation as resolved.

function runCodeFromTextarea(_) {
const consoleElement = document.getElementById('console');
// Clean the console
consoleElement.value = '';
const consoleElement = document.getElementById('console');
const errorElement = document.getElementById('error');
// 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;
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
124 changes: 112 additions & 12 deletions wasm/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,33 +1,133 @@
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 {
Comment thread
coolreader18 marked this conversation as resolved.
Outdated
use pyobject::PyObjectKind;
let py_obj = py_obj.borrow();
match py_obj.kind {
Comment thread
coolreader18 marked this conversation as resolved.
Outdated
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 {
// 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 vars = vm.context().new_scope(Some(builtins));

vm.run_code_obj(code_obj, vars)
}

#[wasm_bindgen]
pub fn eval_py(source: &str) -> Result<JsValue, JsValue> {
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_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));

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(
Comment thread
coolreader18 marked this conversation as resolved.
&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())
}
}
}
23 changes: 19 additions & 4 deletions wasm/src/wasm_builtins.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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::<HtmlTextAreaElement>().unwrap();
let value = textarea.value();
textarea.set_value(&format!("{}{}", value, text));
Expand All @@ -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 {
Comment thread
coolreader18 marked this conversation as resolved.
Outdated
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())
}