Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
2 changes: 1 addition & 1 deletion wasm/demo/src/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ function runCodeFromTextarea() {

const code = editor.getValue();
try {
rp.pyEval(code, {
rp.pyExec(code, {
stdout: output => {
const shouldScroll =
consoleElement.scrollHeight - consoleElement.scrollTop ===
Expand Down
72 changes: 52 additions & 20 deletions wasm/lib/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,13 @@ extern crate futures;
extern crate js_sys;
#[macro_use]
extern crate rustpython_vm;
extern crate rustpython_compiler;
extern crate wasm_bindgen;
extern crate wasm_bindgen_futures;
extern crate web_sys;

use js_sys::{Object, Reflect, TypeError};
use rustpython_compiler::compile::Mode;
use std::panic;
use wasm_bindgen::prelude::*;

Expand Down Expand Up @@ -47,14 +49,35 @@ pub fn setup_console_error() {
#[wasm_bindgen(typescript_custom_section)]
const TS_CMT_START: &'static str = "/*";

fn run_py(source: &str, options: Option<Object>, mode: Mode) -> Result<JsValue, JsValue> {
let vm = VMStore::init(PY_EVAL_VM_ID.into(), Some(true));
let options = options.unwrap_or_else(Object::new);
let js_vars = {
let prop = Reflect::get(&options, &"vars".into())?;
if prop.is_undefined() {
None
} else if prop.is_object() {
Some(Object::from(prop))
} else {
return Err(TypeError::new("vars must be an object").into());
}
};

vm.set_stdout(Reflect::get(&options, &"stdout".into())?)?;

if let Some(js_vars) = js_vars {
vm.add_to_scope("js_vars".into(), js_vars.into())?;
}
vm.run(source, mode)
}
#[wasm_bindgen(js_name = pyEval)]
/// Evaluate Python code
///
/// ```js
/// pyEval(code, options?);
/// var result = pyEval(code, options?);
/// ```
///
/// `code`: `string`: The Python code to run
/// `code`: `string`: The Python code to run in eval mode
///
/// `options`:
///
Expand All @@ -66,26 +89,35 @@ const TS_CMT_START: &'static str = "/*";
/// `undefined` or "console", and it will be a dumb function when giving null.

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())?;
if prop.is_undefined() {
None
} else if prop.is_object() {
Some(Object::from(prop))
} else {
return Err(TypeError::new("vars must be an object").into());
}
};
let vm = VMStore::init(PY_EVAL_VM_ID.into(), Some(true));

vm.set_stdout(Reflect::get(&options, &"stdout".into())?)?;
run_py(source, options, Mode::Eval)
}

if let Some(js_vars) = js_vars {
vm.add_to_scope("js_vars".into(), js_vars.into())?;
}
#[wasm_bindgen(js_name = pyExec)]
/// Evaluate Python code
///
/// ```js
/// pyExec(code, options?);
/// ```
///
/// `code`: `string`: The Python code to run in exec mode
///
/// `options`: The options are the same as eval mode
pub fn exec_py(source: &str, options: Option<Object>) {
let _ = run_py(source, options, Mode::Exec);
}

vm.exec(source)
#[wasm_bindgen(js_name = pyExecSingle)]
/// Evaluate Python code
///
/// ```js
/// var result = pyExecSingle(code, options?);
/// ```
///
/// `code`: `string`: The Python code to run in exec single mode
///
/// `options`: The options are the same as eval mode
pub fn exec_single_py(source: &str, options: Option<Object>) -> Result<JsValue, JsValue> {
run_py(source, options, Mode::Single)
}

#[wasm_bindgen(typescript_custom_section)]
Expand Down
2 changes: 1 addition & 1 deletion wasm/lib/src/vm_class.rs
Original file line number Diff line number Diff line change
Expand Up @@ -278,7 +278,7 @@ impl WASMVirtualMachine {
})?
}

fn run(&self, source: &str, mode: compile::Mode) -> Result<JsValue, JsValue> {
pub(crate) fn run(&self, source: &str, mode: compile::Mode) -> Result<JsValue, JsValue> {
self.assert_valid()?;
self.with_unchecked(
|StoredVirtualMachine {
Expand Down
46 changes: 46 additions & 0 deletions wasm/tests/test_exec_mode.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import time
import sys

from selenium import webdriver
from selenium.webdriver.firefox.options import Options
import pytest

def print_stack(driver):
stack = driver.execute_script(
"return window.__RUSTPYTHON_ERROR_MSG + '\\n' + window.__RUSTPYTHON_ERROR_STACK"
)
print(f"RustPython error stack:\n{stack}", file=sys.stderr)


@pytest.fixture(scope="module")
def driver(request):
options = Options()
options.add_argument('-headless')
driver = webdriver.Firefox(options=options)
try:
driver.get("http://localhost:8080")
except Exception as e:
print_stack(driver)
raise
time.sleep(5)
yield driver
driver.close()


def test_eval_mode(driver):
assert driver.execute_script("return window.rp.pyEval('1+1')") == 2

def test_exec_mode(driver):
assert driver.execute_script("return window.rp.pyExec('1+1')") is None

def test_exec_single_mode(driver):
assert driver.execute_script("return window.rp.pyExecSingle('1+1')") == 2
assert driver.execute_script(
"""
var output = [];
save_output = function(text) {{
output.push(text)
}};
window.rp.pyExecSingle('1+1\\n2+2',{stdout: save_output});
return output;
""") == ['2\n', '4\n']