From 1913f033be16b7a1779a013ac111727f810ce552 Mon Sep 17 00:00:00 2001 From: yanganto Date: Fri, 27 Dec 2019 16:05:35 +0800 Subject: [PATCH] Support eval and exec mode in wasm - Expose `pyEval`, `pyExec`, `pyExecSingle` in wasm module --- wasm/demo/src/main.js | 2 +- wasm/lib/src/lib.rs | 74 ++++++++++++++++++++++++++---------- wasm/lib/src/vm_class.rs | 2 +- wasm/tests/test_exec_mode.py | 46 ++++++++++++++++++++++ 4 files changed, 101 insertions(+), 23 deletions(-) create mode 100644 wasm/tests/test_exec_mode.py diff --git a/wasm/demo/src/main.js b/wasm/demo/src/main.js index 48587640b..2565d3ee2 100644 --- a/wasm/demo/src/main.js +++ b/wasm/demo/src/main.js @@ -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 === diff --git a/wasm/lib/src/lib.rs b/wasm/lib/src/lib.rs index b1f55d36b..d6cbaae8b 100644 --- a/wasm/lib/src/lib.rs +++ b/wasm/lib/src/lib.rs @@ -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::*; @@ -47,25 +49,8 @@ pub fn setup_console_error() { #[wasm_bindgen(typescript_custom_section)] const TS_CMT_START: &'static str = "/*"; -#[wasm_bindgen(js_name = pyEval)] -/// Evaluate Python code -/// -/// ```js -/// pyEval(code, options?); -/// ``` -/// -/// `code`: `string`: The Python code to run -/// -/// `options`: -/// -/// - `vars?`: `{ [key: string]: any }`: Variables passed to the VM that can be -/// accessed in Python with the variable `js_vars`. Functions do work, and -/// receive the Python kwargs as the `this` argument. -/// - `stdout?`: `"console" | ((out: string) => void) | null`: A function to replace the -/// native print native print function, and it will be `console.log` when giving -/// `undefined` or "console", and it will be a dumb function when giving null. - -pub fn eval_py(source: &str, options: Option) -> Result { +fn run_py(source: &str, options: Option, mode: Mode) -> Result { + 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())?; @@ -77,15 +62,62 @@ pub fn eval_py(source: &str, options: Option) -> Result void) | null`: A function to replace the +/// native print native print function, and it will be `console.log` when giving +/// `undefined` or "console", and it will be a dumb function when giving null. - vm.exec(source) +pub fn eval_py(source: &str, options: Option) -> Result { + run_py(source, options, Mode::Eval) +} + +#[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) { + let _ = run_py(source, options, Mode::Exec); +} + +#[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) -> Result { + run_py(source, options, Mode::Single) } #[wasm_bindgen(typescript_custom_section)] diff --git a/wasm/lib/src/vm_class.rs b/wasm/lib/src/vm_class.rs index 3f9a318fa..b9fa910f1 100644 --- a/wasm/lib/src/vm_class.rs +++ b/wasm/lib/src/vm_class.rs @@ -278,7 +278,7 @@ impl WASMVirtualMachine { })? } - fn run(&self, source: &str, mode: compile::Mode) -> Result { + pub(crate) fn run(&self, source: &str, mode: compile::Mode) -> Result { self.assert_valid()?; self.with_unchecked( |StoredVirtualMachine { diff --git a/wasm/tests/test_exec_mode.py b/wasm/tests/test_exec_mode.py new file mode 100644 index 000000000..1914f102e --- /dev/null +++ b/wasm/tests/test_exec_mode.py @@ -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']