Genericize the wasm lib to not be specifically for the demo

This included changing RustObjectKind::RustFunction.function to be
a `Box<Fn()>` instead of a `fn()` to support closures.
This commit is contained in:
coolreader18 committed 2018-12-24 15:28:49 -06:00
1 parent 4c32693c7b
commit ca30ebcf5d
22 files changed
+10523 -91

No files matched your search

+1 -1
View File
@@ -4,7 +4,7 @@ version = "0.0.1"
authors = ["Windel Bouwman", "Shing Lyu <shing.lyu@gmail.com>"]
[workspace]
members = [".", "vm", "wasm", "parser"]
members = [".", "vm", "wasm/lib", "parser"]
[dependencies]
log="0.4.1"
+1 -1
View File
@@ -559,7 +559,7 @@ impl Frame {
bytecode::Instruction::LoadBuildClass => {
let rustfunc = PyObject::new(
PyObjectKind::RustFunction {
function: builtins::builtin_build_class_,
function: Box::new(builtins::builtin_build_class_),
},
vm.ctx.type_type(),
);
+26 -7
View File
@@ -452,9 +452,24 @@ impl PyContext {
)
}
pub fn new_rustfunc(&self, function: RustPyFunc) -> PyObjectRef {
pub fn new_rustfunc<F: 'static + Fn(&mut VirtualMachine, PyFuncArgs) -> PyResult>(
&self,
function: F,
) -> PyObjectRef {
PyObject::new(
PyObjectKind::RustFunction { function: function },
PyObjectKind::RustFunction {
function: Box::new(function),
},
self.function_type(),
)
}
pub fn new_rustfunc_from_box(
&self,
function: Box<(Fn(&mut VirtualMachine, PyFuncArgs) -> PyResult)>,
) -> PyObjectRef {
PyObject::new(
PyObjectKind::RustFunction { function },
self.function_type(),
)
}
@@ -463,7 +478,10 @@ impl PyContext {
PyObject::new(PyObjectKind::Frame { frame: frame }, self.frame_type())
}
pub fn new_property(&self, function: RustPyFunc) -> PyObjectRef {
pub fn new_property<F: 'static + Fn(&mut VirtualMachine, PyFuncArgs) -> PyResult>(
&self,
function: F,
) -> PyObjectRef {
let fget = self.new_rustfunc(function);
let py_obj = PyObject::new(
PyObjectKind::Instance {
@@ -505,7 +523,10 @@ impl PyContext {
)
}
pub fn new_member_descriptor(&self, function: RustPyFunc) -> PyObjectRef {
pub fn new_member_descriptor<F: 'static + Fn(&mut VirtualMachine, PyFuncArgs) -> PyResult>(
&self,
function: F,
) -> PyObjectRef {
let dict = self.new_dict();
self.set_item(&dict, "function", self.new_rustfunc(function));
self.new_instance(dict, self.member_descriptor_type())
@@ -762,8 +783,6 @@ impl PyFuncArgs {
}
}
type RustPyFunc = fn(vm: &mut VirtualMachine, PyFuncArgs) -> PyResult;
/// Rather than determining the type of a python object, this enum is more
/// a holder for the rust payload of a python object. It is more a carrier
/// of rust data for a particular python object. Determine the python type
@@ -840,7 +859,7 @@ pub enum PyObjectKind {
dict: PyObjectRef,
},
RustFunction {
function: RustPyFunc,
function: Box<(Fn(&mut VirtualMachine, PyFuncArgs) -> PyResult)>,
},
}
+1 -1
View File
@@ -229,7 +229,7 @@ impl VirtualMachine {
pub fn invoke(&mut self, func_ref: PyObjectRef, args: PyFuncArgs) -> PyResult {
trace!("Invoke: {:?} {:?}", func_ref, args);
match func_ref.borrow().kind {
PyObjectKind::RustFunction { function } => function(self, args),
PyObjectKind::RustFunction { ref function } => function(self, args),
PyObjectKind::Function {
ref code,
ref scope,
+3 -7
View File
@@ -1,7 +1,3 @@
wasm-pack build --debug && \
cd pkg && \
npm link && \
cd ../app && \
npm install && \
npm link rustpython_wasm && \
node_modules/.bin/webpack-dev-server
set -e
(cd lib; wasm-pack build --debug)
(cd demo; npm install && node_modules/.bin/webpack-dev-server)
File renamed without changes.
File renamed without changes.
+6032
View File
File diff suppressed because it is too large. Load diff
File renamed without changes.
File renamed without changes.
@@ -34,6 +34,21 @@ while count < until:
<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>
<a href="https://github.com/RustPython/RustPython">
<img
style="position: absolute; top: 0; right: 0; border: 0;"
@@ -1,4 +1,4 @@
import * as rp from '../../pkg';
import * as rp from '../../lib/pkg';
import CodeMirror from 'codemirror';
import 'codemirror/mode/python/python';
import 'codemirror/addon/comment/comment';
@@ -14,6 +14,7 @@ const editor = CodeMirror.fromTextArea(document.getElementById('code'), {
'Ctrl-/': 'toggleComment',
'Cmd-/': 'toggleComment'
},
lineNumbers: true,
mode: 'text/x-python',
indentUnit: 4,
autofocus: true
@@ -29,7 +30,9 @@ function runCodeFromTextarea() {
const code = editor.getValue();
try {
rp.run_from_textbox(code);
rp.eval_py(code, {
stdout: '#console'
});
} catch (e) {
errorElement.textContent = e;
console.error(e);
File renamed without changes.
File renamed without changes.
+4345
View File
File diff suppressed because it is too large. Load diff
View File
File renamed without changes.
+2 -2
View File
@@ -7,8 +7,8 @@ authors = ["Ryan Liddle <ryan@rmliddle.com>"]
crate-type = ["cdylib", "rlib"]
[dependencies]
rustpython_parser = {path = "../parser"}
rustpython_vm = {path = "../vm"}
rustpython_parser = {path = "../../parser"}
rustpython_vm = {path = "../../vm"}
cfg-if = "0.1.2"
wasm-bindgen = "0.2"
js-sys = "0.3"
+70 -45
View File
@@ -5,12 +5,11 @@ extern crate rustpython_vm;
extern crate wasm_bindgen;
extern crate web_sys;
use js_sys::Reflect;
use js_sys::{Array, Object, Reflect, TypeError};
use rustpython_vm::compile;
use rustpython_vm::pyobject::{self, PyObjectRef, PyResult};
use rustpython_vm::pyobject::{self, PyFuncArgs, 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)
@@ -76,21 +75,84 @@ where
}
#[wasm_bindgen]
pub fn eval_py(source: &str, js_injections: Option<js_sys::Object>) -> Result<JsValue, JsValue> {
pub fn eval_py(source: &str, options: Option<Object>) -> Result<JsValue, JsValue> {
let options = options.unwrap_or_else(|| Object::new());
let js_vars = {
let prop = Reflect::get(&options, &"vars".into())?;
let prop = Object::from(prop);
if prop.is_undefined() {
None
} else if prop.is_object() {
Some(prop)
} else {
return Err(TypeError::new("vars must be an object").into());
}
};
let stdout = {
let prop = Reflect::get(&options, &"stdout".into())?;
if prop.is_undefined() {
None
} else {
Some(prop)
}
};
let string_stdout = {
let prop = Reflect::get(&options, &"stdoutString".into())?;
let prop = Object::from(prop);
if prop.is_undefined() {
true
} else if let Some(prop) = prop.as_bool() {
prop
} else {
return Err(TypeError::new("stdoutString must be a boolean").into());
}
};
let mut vm = VirtualMachine::new();
let print_fn: Box<(Fn(&mut VirtualMachine, PyFuncArgs) -> PyResult)> = match stdout {
Some(val) => {
if let Some(selector) = val.as_string() {
Box::new(
move |vm: &mut VirtualMachine, args: PyFuncArgs| -> PyResult {
wasm_builtins::builtin_print_html(vm, args, &selector)
},
)
} else if val.is_function() {
let func = js_sys::Function::from(val);
Box::new(
move |vm: &mut VirtualMachine, args: PyFuncArgs| -> PyResult {
let arr = Array::new();
for arg in args.args {
let arg = if string_stdout {
vm.to_pystr(&arg)?.into()
} else {
py_to_js(vm, arg)
};
arr.push(&arg);
}
func.apply(&JsValue::UNDEFINED, &arr)
.map_err(|err| js_to_py(vm, err))?;
Ok(vm.get_none())
},
)
} else {
return Err(TypeError::new("stdout must be a function or a css selector").into());
}
}
None => Box::new(wasm_builtins::builtin_print_console),
};
vm.ctx.set_attr(
&vm.builtins,
"print",
vm.context()
.new_rustfunc(wasm_builtins::builtin_print_console),
vm.ctx.new_rustfunc_from_box(print_fn),
);
let res = eval(&mut vm, source, |vm, vars| {
let injections = vm.new_dict();
if let Some(js_injections) = js_injections.clone() {
for pair in js_sys::try_iter(&js_sys::Object::entries(&js_injections))
if let Some(js_vars) = js_vars.clone() {
for pair in js_sys::try_iter(&Object::entries(&js_vars))
.unwrap()
.unwrap()
{
@@ -112,40 +174,3 @@ pub fn eval_py(source: &str, js_injections: Option<js_sys::Object>) -> Result<Js
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: 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),
);
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())
}
}
}
@@ -8,47 +8,48 @@ extern crate js_sys;
extern crate wasm_bindgen;
extern crate web_sys;
use crate::js_to_py;
use js_sys::Array;
use rustpython_vm::obj::objstr;
use rustpython_vm::pyobject::{PyFuncArgs, PyResult};
use rustpython_vm::VirtualMachine;
use wasm_bindgen::JsCast;
use wasm_bindgen::{JsCast, JsValue};
use web_sys::{console, window, HtmlTextAreaElement};
// The HTML id of the textarea element that act as our STDOUT
const CONSOLE_ELEMENT_ID: &str = "console";
pub fn print_to_html(text: &str) {
pub fn print_to_html(text: &str, selector: &str) -> Result<(), JsValue> {
let document = window().unwrap().document().unwrap();
let element = document
.get_element_by_id(CONSOLE_ELEMENT_ID)
.expect("Can't find the console textarea");
let textarea = element.dyn_ref::<HtmlTextAreaElement>().unwrap();
.query_selector(selector)?
.ok_or_else(|| js_sys::TypeError::new("Couldn't get element"))?;
let textarea = element
.dyn_ref::<HtmlTextAreaElement>()
.ok_or_else(|| js_sys::TypeError::new("Element must be a textarea"))?;
let value = textarea.value();
textarea.set_value(&format!("{}{}", value, text));
Ok(())
}
pub fn builtin_print_html(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult {
pub fn builtin_print_html(vm: &mut VirtualMachine, args: PyFuncArgs, selector: &str) -> PyResult {
let mut output = String::new();
let mut first = true;
for a in args.args {
if first {
first = false;
} else {
print_to_html(&" ")
output.push_str(" ");
}
let v = vm.to_str(&a)?;
let s = objstr::get_value(&v);
print_to_html(&format!("{}\n", s))
output.push_str(&vm.to_pystr(&a)?);
output.push('\n');
}
print_to_html(&output, selector).map_err(|err| js_to_py(vm, err))?;
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());
for arg in args.args {
arr.push(&vm.to_pystr(&arg)?.into());
}
console::log(&arr);
Ok(vm.get_none())
+1
View File
@@ -0,0 +1 @@
../../target
+4 -8
View File
@@ -1,8 +1,4 @@
wasm-pack build && \
cd pkg && \
npm link && \
cd ../app && \
npm install && \
npm link rustpython_wasm && \
node_modules/.bin/webpack --mode production && \
echo "Output saved to app/dist"
set -e
(cd lib; wasm-pack build)
(cd demo; npm install && node_modules/.bin/webpack --mode production)
echo "Output saved to demo/dist"
-1
View File
@@ -1 +0,0 @@
../target/