mirror of
https://github.com/RustPython/RustPython.git
synced 2026-10-28 10:44:36 +08:00
* Align compiler and AST behavior with CPython
Update the compiler pipeline, AST module support, and related VM compile/eval paths toward CPython 3.14 behavior.
Use upstream Ruff parser crates directly and keep RustPython-specific syntax preflight handling in the compile path.
Refresh opcode metadata, snapshots, and targeted tests for the aligned bytecode and AST behavior.
* fix
* Fix PR 8138 CI failures
* Fix match mapping wildcard scan complexity
* fixes
* Use Ruff runtime AST fields
* Minimize residual patch diff
* refactor error handling
* Remove _ast conversion context wrappers
* Clean up _ast conversion glue and compiler warning escalation
Rename the Node conversion parameter `ctx` to `vm` throughout the _ast
module.
Remove the unused compile_program, compile_program_single,
compile_block_expression, and compile_expression forwarders; compile_top
stays as the entry point.
Stash an escalated compiler SyntaxWarning so a non-SyntaxWarning category
propagates unchanged instead of being rewritten to SyntaxError, matching
PyErr_ExceptionMatches(SyntaxWarning) in compiler_warn. Drop the now-unused
CompileWarningError::into_codegen_error.
Replace the PositionalArguments two-variant enum with a struct holding a
shared range and a PositionalArgumentsKind enum.
Assisted-by: Claude
* Represent compiler flags as a CompilerFlags bitflags
Replace the loose PY_CF_*/CO_* i32 constants in compile_mode with a
CompilerFlags bitflags covering the PyCF_* flags and __future__
CO_FUTURE_* bits. The Python-visible ast.PyCF_* attribute values are
derived from it via bits(); parser input modes stay plain consts.
builtins::compile and the compile_string flag handling test flags
through CompilerFlags::contains instead of raw bit math.
Assisted-by: Claude
* Fix PR 8138 test regressions
* Fix PR 8138 CI failures
* Avoid shell interpolation in Linux deps action
* Fix review findings in compiler/AST changes
- frame.rs: compute the MATCH_CLASS class name lazily so a successful
match no longer takes the type name lock or allocates; the
__match_args__-not-a-tuple branch reuses it instead of recomputing.
- _ast/constant.rs: add the missing leading space to the with_recursion
label ("maximum recursion depth exceeded during compilation").
- sys.set_int_max_str_digits: restore the "maxdigits must be 0 or larger
than {n}" error text.
- codegen: set CO_MAXBLOCKS to 20 in both ir.rs and compile.rs.
- _ast/exception.rs: extract the duplicated excepthandler node guard
into ensure_excepthandler_node.
- codegen: correct the validate_keywords doc comment.
- Drop redundant "CPython" qualifiers from comments added in this branch.
Assisted-by: Claude
141 lines
4.6 KiB
Rust
141 lines
4.6 KiB
Rust
extern crate alloc;
|
|
|
|
pub mod browser_module;
|
|
pub mod convert;
|
|
pub mod js_module;
|
|
pub mod vm_class;
|
|
pub mod wasm_builtins;
|
|
|
|
#[macro_use]
|
|
extern crate rustpython_vm;
|
|
|
|
use js_sys::{Reflect, WebAssembly::RuntimeError};
|
|
use std::panic;
|
|
pub(crate) use vm_class::weak_vm;
|
|
use wasm_bindgen::prelude::*;
|
|
|
|
/// Sets error info on the window object, and prints the backtrace to console
|
|
pub fn panic_hook(info: &panic::PanicHookInfo<'_>) {
|
|
// If something errors, just ignore it; we don't want to panic in the panic hook
|
|
let try_set_info = || {
|
|
let msg = &info.to_string();
|
|
let window = match web_sys::window() {
|
|
Some(win) => win,
|
|
None => return,
|
|
};
|
|
let _ = Reflect::set(&window, &"__RUSTPYTHON_ERROR_MSG".into(), &msg.into());
|
|
let error = RuntimeError::new(msg);
|
|
let _ = Reflect::set(&window, &"__RUSTPYTHON_ERROR".into(), &error);
|
|
let stack = match Reflect::get(&error, &"stack".into()) {
|
|
Ok(stack) => stack,
|
|
Err(_) => return,
|
|
};
|
|
let _ = Reflect::set(&window, &"__RUSTPYTHON_ERROR_STACK".into(), &stack);
|
|
};
|
|
try_set_info();
|
|
console_error_panic_hook::hook(info);
|
|
}
|
|
|
|
#[doc(hidden)]
|
|
#[cfg(not(feature = "no-start-func"))]
|
|
#[wasm_bindgen(start)]
|
|
pub fn _setup_console_error() {
|
|
std::panic::set_hook(Box::new(panic_hook));
|
|
}
|
|
|
|
pub mod eval {
|
|
use crate::vm_class::VMStore;
|
|
use js_sys::{Object, Reflect, TypeError};
|
|
use rustpython_vm::compiler::Mode;
|
|
use wasm_bindgen::prelude::*;
|
|
|
|
const PY_EVAL_VM_ID: &str = "__py_eval_vm";
|
|
|
|
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_default();
|
|
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())?;
|
|
}
|
|
if matches!(mode, Mode::Single) {
|
|
vm.exec_single(source, None)
|
|
} else {
|
|
vm.run(source, mode, None)
|
|
}
|
|
}
|
|
|
|
/// 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.
|
|
#[wasm_bindgen(js_name = pyEval)]
|
|
pub fn eval_py(source: &str, options: Option<Object>) -> Result<JsValue, JsValue> {
|
|
run_py(source, options, Mode::Eval)
|
|
}
|
|
|
|
/// 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
|
|
#[wasm_bindgen(js_name = pyExec)]
|
|
pub fn exec_py(source: &str, options: Option<Object>) -> Result<(), JsValue> {
|
|
run_py(source, options, Mode::Exec).map(drop)
|
|
}
|
|
|
|
/// 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
|
|
#[wasm_bindgen(js_name = pyExecSingle)]
|
|
pub fn exec_single_py(source: &str, options: Option<Object>) -> Result<JsValue, JsValue> {
|
|
run_py(source, options, Mode::Single)
|
|
}
|
|
}
|
|
|
|
/// A module containing all the wasm-bindgen exports that rustpython_wasm has
|
|
/// Re-export as `pub use rustpython_wasm::exports::*;` in the root of your crate if you want your
|
|
/// wasm module to mimic rustpython_wasm's API
|
|
pub mod exports {
|
|
pub use crate::convert::PyError;
|
|
pub use crate::eval::{eval_py, exec_py, exec_single_py};
|
|
pub use crate::vm_class::{VMStore, WASMVirtualMachine};
|
|
}
|
|
|
|
#[doc(hidden)]
|
|
pub use exports::*;
|