diff --git a/src/SUMMARY.md b/src/SUMMARY.md index e974727..7bbc211 100644 --- a/src/SUMMARY.md +++ b/src/SUMMARY.md @@ -1,4 +1,9 @@ # Summary -- [Introduction](./intro.md) -- [Writing Functions](./writing_functions.md) +- [Introduction](introduction/index.md) +- [Getting Started](getting_started/index.md) + - [Installation](getting_started/installation.md) + - [Initial Setup](getting_started/initial_setup.md) +- [Interop](interop/index.md) + - [Calling Rust from Python](interop/rust_from_python.md) + - [Calling Python from Rust](interop/python_from_rust.md) diff --git a/src/getting_started/index.md b/src/getting_started/index.md new file mode 100644 index 0000000..071944d --- /dev/null +++ b/src/getting_started/index.md @@ -0,0 +1,4 @@ +# Getting Started + +- [Installation](./installation.md) +- [Initial Setup](./initial_setup.md) \ No newline at end of file diff --git a/src/getting_started/initial_setup.md b/src/getting_started/initial_setup.md new file mode 100644 index 0000000..35c5818 --- /dev/null +++ b/src/getting_started/initial_setup.md @@ -0,0 +1,73 @@ +# Initial Setup + +First `rustpython_vm` needs to be imported. +If `rustpython` is installed, it can be imported as a re-export: +```rust +use rustpython::vm; +``` + +if `rustpython_vm` is installed, it can be imported just like any other module. + +```rust +use rustpython::vm; + +fn main() -> vm::PyResult<()> { + vm::Interpreter::without_stdlib(Default::default()).enter(|vm| { + let scope = vm.new_scope_with_builtins(); + let source = r#"print("Hello World!")"#; + let code_obj = vm + .compile(source, vm::compiler::Mode::Exec, "".to_owned()) + .map_err(|err| vm.new_syntax_error(&err, Some(source)))?; + + vm.run_code_obj(code_obj, scope)?; + + Ok(()) + }) +} +``` + +This will print `Hello World!` to the console. + +## Adding the standard library +If the `stdlib` feature is enabled, +the standard library can be added to the interpreter by calling `add_native_modules` +with the result of `rustpython_stdlib::get_module_inits()`. +```rust +use rustpython::vm as vm; +use std::process::ExitCode; +use vm::{Interpreter, builtins::PyStrRef}; + +fn py_main(interp: &Interpreter) -> vm::PyResult { + interp.enter(|vm| { + let scope = vm.new_scope_with_builtins(); + let source = r#"print("Hello World!")"#; + let code_obj = vm + .compile(source, vm::compiler::Mode::Exec, "".to_owned()) + .map_err(|err| vm.new_syntax_error(&err, Some(source)))?; + + vm.run_code_obj(code_obj, scope)?; + }) +} + +fn main() -> ExitCode { + // Add standard library path + let mut settings = vm::Settings::default(); + settings.path_list.push("Lib".to_owned()); + let interp = vm::Interpreter::with_init(settings, |vm| { + vm.add_native_modules(rustpython_stdlib::get_module_inits()); + }); + let result = py_main(&interp); + let result = result.map(|result| { + println!("name: {result}"); + }); + ExitCode::from(interp.run(|_vm| result)) +} +``` + +to import a module, the following code can be used: +```rust, no_run +// Add local library path +vm.insert_sys_path(vm.new_pyobj("")) + .expect("add examples to sys.path failed"); +let module = vm.import("", 0)?; +``` diff --git a/src/getting_started/installation.md b/src/getting_started/installation.md new file mode 100644 index 0000000..d7ae156 --- /dev/null +++ b/src/getting_started/installation.md @@ -0,0 +1,40 @@ +# Installation +## Requirements +RustPython requires Rust latest stable version to be installed + +## Stable +The latest stable version of the library can be installed using the following command: +```bash +cargo add rustpython +``` + +or by adding the following to your `Cargo.toml`: +```toml +[dependencies] +rustpython = "0.4" +``` + +## Nightly +Nightly releases are built weekly and are released on git. +```toml +[dependencies] +rustpython = { git = "https://github.com/RustPython/RustPython", tag = "2025-02-24-main-13" } +``` + +The tag should be pointed to the latest tag found at https://github.com/RustPython/RustPython/tags. + +## Features +By default `threading`, `stdlib`, and `importlib` are enabled. +### `bz2` +If you'd like to use the `bz2` module, you can enable the `bz2` feature. +### `stdlib` +`stdlib` is the default feature that enables the standard library. +### `sqlite` +If you'd like to use the `sqlite3` module, you can enable the `sqlite` feature. +### `ssl` +If you'd like to make https requests, you can enable the ssl feature, +which also lets you install the pip package manager. +Note that on Windows, you may need to install OpenSSL, or you can enable the ssl-vendor feature instead, +which compiles OpenSSL for you but requires a C compiler, perl, and make. +OpenSSL version 3 is expected and tested in CI. Older versions may not work. + diff --git a/src/interop/index.md b/src/interop/index.md new file mode 100644 index 0000000..d02aad6 --- /dev/null +++ b/src/interop/index.md @@ -0,0 +1,3 @@ +# Interpretation between Rust and Python +- [Calling Rust from Python](./rust_from_python.md) +- [Calling Python from Rust](./python_from_rust.md) diff --git a/src/interop/python_from_rust.md b/src/interop/python_from_rust.md new file mode 100644 index 0000000..dae5f91 --- /dev/null +++ b/src/interop/python_from_rust.md @@ -0,0 +1,2 @@ +# Calling Python from Rust +TODO. \ No newline at end of file diff --git a/src/interop/rust_from_python.md b/src/interop/rust_from_python.md new file mode 100644 index 0000000..4268b20 --- /dev/null +++ b/src/interop/rust_from_python.md @@ -0,0 +1,171 @@ +# Calling Rust from Python +## Structure +```rust, ignore +use rustpython::vm::pymodule; +#[pymodule] +mod test_module { + #[pyattr] + pub const THE_ANSWER: i32 = 42; + + #[pyfunction] + pub fn add(a: i32, b: i32) -> i32 { + a + b + } + + #[pyattr] + #[pyclass] + pub struct TestClass { + pub value: i32, + } + + #[pyclass] + impl TestClass { + #[pygetset] + pub fn value(&self) -> i32 { + self.value + } + + #[pymethod] + pub fn get_info(&self) -> i32 { + self.value * 2 + } + } +} +``` +This code defines a Python module named `test_module` with two items: +a constant named `THE_ANSWER` and a function named `add`. +The `#[pymodule]` attribute is used to mark the module, +and the `#[pyattr]` and `#[pyfunction]` attributes are used to mark the constant and function, respectively. + +RustPython allows for 3 types of items in a module: +- Variables: Defined using the `#[pyattr]` attribute. +- Functions: Defined using the `#[pyfunction]` attribute. +- Classes: Defined using the `#[pyclass]` attribute. + +## General Configuration +Most attributes have a `name` parameter that can be used to specify the name of the item in Python. +If the `name` parameter is not provided, the Rust identifier is used as the name in Python. + +## Variables +Variables are defined using the `#[pyattr]` attribute. +A variable can either be a constant or a function. +Note that classes are treated as attributes in RustPython +and are annotated with `#[pyattr]` as well, but that can be done away with if needed. +```rust, no_run +#[pyattr] +const THE_ANSWER: i32 = 42; +// ... or +#[pyattr] +fn the_answer() -> i32 { + 42 +} +// ... or +// this will cache the result of the function +// and return the same value every time it is called +#[pyattr(once)] +fn cached_answer() -> i32 { + 42 +} +``` + +## Valid Arguments/Return Types +Every input and return value must be convertible to `PyResult`. This is defined as `IntoPyResult` trait. So any return value of them must implement `IntoPyResult`. It will be `PyResult`, `PyObjectRef` and any `PyResult` when T implements `IntoPyObject`. Practically we can list them like: +- Any `T` when `PyResult` is possible +- `PyObjectRef` +- `PyResult<()>` and `()` as `None` +- `PyRef` like `PyIntRef`, `PyStrRef` +- `T: PyValue` like `PyInt`, `PyStr` +- Numbers like `usize` or `f64` for `PyInt` and `PyFloat` +- `String` for `PyStr` +- And more types implementing `IntoPyObject`. + +The `vm` paramter is optional. We add it as the last parameter unless we don't use `vm` at all - very rare case. It takes an object `obj` as `PyObjectRef`, which is a general python object. It returns `PyResult`, which will turn into `PyResult` the same representation of `PyResult`. The `vm` parameter does not need to be passed in by the python code. + +If needed a seperate struct can be used for arguments using the `FromArgs` trait like so: + +```rust +#[derive(FromArgs)] +struct BisectArgs { + a: PyObjectRef, + x: PyObjectRef + #[pyarg(any, optional)] + lo: OptionalArg, + #[pyarg(any, optional)] + hi: OptionalArg, + #[pyarg(named, default)] + key: Option, +} + +#[pyfunction] +fn bisect_left( + BisectArgs { a, x, lo, hi, key }: BisectArgs, + vm: &VirtualMachine, +) -> PyResult { + // ... +} + +// or ... + +#[pyfunction] +fn bisect_left( + args: BisectArgs, + vm: &VirtualMachine, +) -> PyResult { + // ... +} +``` + +## Errors + +Returning a PyResult is the supported error handling strategy. Builtin python errors are created with `vm.new_xxx_error` methods. + +### Custom Errors + +``` +#[pyattr(once)] +fn error(vm: &VirtualMachine) -> PyTypeRef { + vm.ctx.new_exception_type( + "", + "", + Some(vec![vm.ctx.exceptions.exception_type.to_owned()]), + ) +} + +// convenience function +fn new_error(message: &str, vm: &VirtualMachine) -> PyBaseExceptionRef { + vm.new_exception_msg(vm.class("", ""), message.to_owned()) +} +``` + +## Functions +Functions are defined using the `#[pyfunction]` attribute. +```rust, no_run +#[pyfunction] +fn add(a: i32, b: i32) -> i32 { + a + b +} +``` + +## Classes +Classes are defined using the `#[pyclass]` attribute. +```rust, no_run +#[pyclass] +pub struct TestClass { + pub value: i32, +} +#[pyclass] +impl TestClass { +} +``` +### Associated Data +TODO. +### Methods +TODO. +### Getters and Setters +TODO. +### Class Methods +TODO. +### Static Methods +TODO. +### Inheritance +TODO. diff --git a/src/introduction/index.md b/src/introduction/index.md new file mode 100644 index 0000000..5ff8c66 --- /dev/null +++ b/src/introduction/index.md @@ -0,0 +1,3 @@ +# Introduction + +RustPython is a Python interpreter written in Rust. diff --git a/src/writing_functions.md b/src/writing_functions.md deleted file mode 100644 index 16d92c8..0000000 --- a/src/writing_functions.md +++ /dev/null @@ -1,45 +0,0 @@ -# Writing Python functions in Rust - -In RustPython, it's possible to write functions in Rust and import them into and call them from -Python. Here's an example: - -```rust -fn rustmod_funct( - obj: PyObjectRef, - s: PyStringRef, - maybe_thing: OptionalArg, - vm: &VirtualMachine, -) -> PyResult<(String, Vec)> { ... } -``` - -## Parameters - -You can use any type that implements [`FromArgs`] as a parameter to your function, which includes -types that implements [`TryFromObject`]. In our example, we use a standard `PyObjectRef`, a -`PyStringRef` (which is really just a `PyRef`, and `PyRef` implements `TryFromObject`), -and an `OptionalArg`, where [`OptionalArg`] gets an optional positional argument. In -addition, [`TryFromObject`] is implemented for every primitive number type, so you can use those as -args too. -[Here is a list of all of the types that implement `TryFromObject`](https://docs.rs/rustpython-vm/0.1.1/rustpython_vm/pyobject/trait.TryFromObject.html#foreign-impls). - -## VirtualMachine parameter - -You can optionally put a `vm: &VirtualMachine` parameter at the end of the parameter list in order to -get access to the Python VM. If you're doing anything more than a very simple function, you'll likely -want this, as it is necessary for creating exception objects; but, if it turns out you didn't use it -in the function, always remember that you can just remove it. - -## Return type - -You can put any type that implements [`IntoPyObject`] as the return type of your function, which includes -many simple Rust types that you'd expect to map cleanly to Python, e.g. `String` -> `str`, `Vec` -> `bytes`, -integral primitives -> `int`, `f32,f64` -> float. If you need to return an error from the function, you can -put any [`IntoPyObject`] type as the `T` in [`PyResult`], and return an `Err(exc)`. (You can create the -exception using one of the `new_*_error` methods on [`VirtualMachine`]) - -[`FromArgs`]: https://docs.rs/rustpython-vm/*/rustpython_vm/function/trait.FromArgs.html -[`TryFromObject`]: https://docs.rs/rustpython-vm/*/rustpython_vm/pyobject/trait.TryFromObject.html -[`OptionalArg`]: https://docs.rs/rustpython-vm/*/rustpython_vm/function/enum.OptionalArg.html -[`IntoPyObject`]: https://docs.rs/rustpython-vm/*/rustpython_vm/pyobject/trait.IntoPyObject.html -[`PyResult`]: https://docs.rs/rustpython-vm/*/rustpython_vm/pyobject/type.PyResult.html -[`VirtualMachine`]: https://docs.rs/rustpython-vm/*/rustpython_vm/struct.VirtualMachine.html