mirror of
https://github.com/RustPython/RustPython.git
synced 2026-10-28 18:59:04 +08:00
Support eval and exec mode in wasm
- Expose `pyEval`, `pyExec`, `pyExecSingle` in wasm module
This commit is contained in:
1 parent
3aa09dda9f
commit
1913f033be
4 files changed
+101
-23
No files matched your search
@@ -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 ===
|
||||
|
||||
+53
-21
@@ -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<Object>) -> Result<JsValue, JsValue> {
|
||||
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())?;
|
||||
@@ -77,15 +62,62 @@ pub fn eval_py(source: &str, options: Option<Object>) -> Result<JsValue, JsValue
|
||||
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())?)?;
|
||||
|
||||
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
|
||||
/// var result = pyEval(code, options?);
|
||||
/// ```
|
||||
///
|
||||
/// `code`: `string`: The Python code to run in eval mode
|
||||
///
|
||||
/// `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.
|
||||
|
||||
vm.exec(source)
|
||||
pub fn eval_py(source: &str, options: Option<Object>) -> Result<JsValue, JsValue> {
|
||||
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<Object>) {
|
||||
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<Object>) -> Result<JsValue, JsValue> {
|
||||
run_py(source, options, Mode::Single)
|
||||
}
|
||||
|
||||
#[wasm_bindgen(typescript_custom_section)]
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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']
|
||||
Reference in new issue
Block a user