inline_python/
lib.rs

1//! Inline Python code directly in your Rust code.
2//!
3//! # Example
4//!
5//! ```
6//! use inline_python::python;
7//!
8//! let who = "world";
9//! let n = 5;
10//! python! {
11//!     for i in range('n):
12//!         print(i, "Hello", 'who)
13//!     print("Goodbye")
14//! }
15//! ```
16//!
17//! # How to use
18//!
19//! Use the `python!{..}` macro to write Python code directly in your Rust code.
20//!
21//! _NOTE:_ This crate uses the **unstable** [`proc_macro_span` feature](https://github.com/rust-lang/rust/issues/54725),
22//! so it will only compile on Rust **nightly**.
23//!
24//! ## Using Rust variables
25//!
26//! To reference Rust variables, use `'var`, as shown in the example above.
27//! `var` needs to implement [`pyo3::IntoPyObject`].
28//!
29//! ## Re-using a Python context
30//!
31//! It is possible to create a [`Context`] object ahead of time and use it for running the Python code.
32//! The context can be re-used for multiple invocations to share global variables across macro calls.
33//!
34//! ```
35//! # use inline_python::{Context, python};
36//! let c = Context::new();
37//!
38//! c.run(python! {
39//!   foo = 5
40//! });
41//!
42//! c.run(python! {
43//!   assert foo == 5
44//! });
45//! ```
46//!
47//! As a shortcut, you can assign a `python!{}` invocation directly to a
48//! variable of type `Context` to create a new context and run the Python code
49//! in it.
50//!
51//! ```
52//! # use inline_python::{Context, python};
53//! let c: Context = python! {
54//!   foo = 5
55//! };
56//!
57//! c.run(python! {
58//!   assert foo == 5
59//! });
60//! ```
61//!
62//! ## Getting information back
63//!
64//! A [`Context`] object could also be used to pass information back to Rust,
65//! as you can retrieve the global Python variables from the context through
66//! [`Context::get`].
67//!
68//! ```
69//! # use inline_python::{Context, python};
70//! let c: Context = python! {
71//!   foo = 5
72//! };
73//!
74//! assert_eq!(c.get::<i32>("foo"), 5);
75//! ```
76//!
77//! ## Syntax issues
78//!
79//! Since the Rust tokenizer will tokenize the Python code, some valid Python
80//! code is rejected. The main things to remember are:
81//!
82//! - Use double quoted strings (`""`) instead of single quoted strings (`''`).
83//!
84//!   (Single quoted strings only work if they contain a single character, since
85//!   in Rust, `'a'` is a character literal.)
86//!
87//! - Use `//`-comments instead of `#`-comments.
88//!
89//!   (If you use `#` comments, the Rust tokenizer will try to tokenize your
90//!   comment, and complain if your comment doesn't tokenize properly.)
91//!
92//! - Write `f ""` instead of `f""`.
93//!
94//!   (String literals with prefixes, like `f""`, are reserved in Rust for
95//!   future use. You can write `f ""` instead, which is automatically
96//!   converted back to to `f""`.)
97//!
98//! Other minor things that don't work are:
99//!
100//! - The `//` and `//=` operators are unusable, as they start a comment.
101//!
102//!   Workaround: you can write `##` instead, which is automatically converted
103//!   to `//`.
104//!
105//! - Certain escape codes in string literals.
106//!   (Specifically: `\a`, `\b`, `\f`, `\v`, `\N{..}`, `\123` (octal escape
107//!   codes), `\u`, and `\U`.)
108//!
109//!   These, however, are accepted just fine: `\\`, `\n`, `\t`, `\r`, `\xAB`
110//!   (hex escape codes), and `\0`.
111//!
112//! - Raw string literals with escaped double quotes. (E.g. `r"...\"..."`.)
113//!
114//! - Triple-quoted byte- and raw-strings with content that would not be valid
115//!   as a regular string. And the same for raw-byte and raw-format strings.
116//!   (E.g. `b"""\xFF"""`, `r"""\z"""`, `fr"\z"`, `br"\xFF"`.)
117//!
118//! Everything else should work fine.
119
120use pyo3::{Bound, Python, types::PyDict};
121
122mod context;
123mod run;
124
125pub use self::context::Context;
126pub use pyo3;
127
128/// A block of Python code within your Rust code.
129///
130/// This macro can be used in three different ways:
131///
132///  1. By itself as a statement.
133///     In this case, the Python code is executed directly.
134///
135///  2. By assigning it to a [`Context`].
136///     In this case, the Python code is executed directly, and the context
137///     (the global variables) are available for re-use by other Python code
138///     or inspection by Rust code.
139///
140///  3. By passing it as an argument to a function taking a `PythonBlock`, such
141///     as [`Context::run`].
142///
143/// See [the crate's module level documentation](index.html) for examples.
144pub use inline_python_macros::python;
145
146// `python!{..}` expands to `python_impl!{b"bytecode" var1 var2 …}`,
147// which then expands to a call to `FromInlinePython::from_python_macro`.
148#[macro_export]
149#[doc(hidden)]
150macro_rules! _python_block {
151    ($bytecode:literal $($var:ident)*) => {
152        $crate::FromInlinePython::from_python_macro(
153            // The compiled python bytecode:
154            $bytecode,
155            // The closure that puts all the captured variables in the 'globals' dictionary:
156            |globals| {
157                $(
158                    $crate::pyo3::prelude::PyDictMethods::set_item(
159                        globals, concat!("_RUST_", stringify!($var)), $var
160                    ).expect("python");
161                )*
162            },
163            // The closure that is used to throw panics with the right location:
164            |e| ::std::panic::panic_any(e),
165        )
166    }
167}
168
169#[doc(hidden)]
170pub trait FromInlinePython<F: FnOnce(&Bound<PyDict>)> {
171    /// The `python!{}` macro expands to a call to this function.
172    fn from_python_macro(bytecode: &'static [u8], set_vars: F, panic: fn(String) -> !) -> Self;
173}
174
175/// Converting a `python!{}` block to `()` will run the Python code.
176///
177/// This happens when `python!{}` is used as a statement by itself.
178impl<F: FnOnce(&Bound<PyDict>)> FromInlinePython<F> for () {
179    #[track_caller]
180    fn from_python_macro(bytecode: &'static [u8], set_vars: F, panic: fn(String) -> !) {
181        let _: Context = FromInlinePython::from_python_macro(bytecode, set_vars, panic);
182    }
183}
184
185/// Assigning a `python!{}` block to a `Context` will run the Python code and capture the resulting context.
186impl<F: FnOnce(&Bound<PyDict>)> FromInlinePython<F> for Context {
187    #[track_caller]
188    fn from_python_macro(bytecode: &'static [u8], set_vars: F, panic: fn(String) -> !) -> Self {
189        Python::with_gil(|py| {
190            let context = Context::new_with_gil(py);
191            context.run_with_gil(
192                py,
193                PythonBlock {
194                    bytecode,
195                    set_vars,
196                    panic,
197                },
198            );
199            context
200        })
201    }
202}
203
204/// Using a `python!{}` block as a `PythonBlock` object will not do anything yet.
205#[cfg(not(doc))]
206impl<F: FnOnce(&Bound<PyDict>)> FromInlinePython<F> for PythonBlock<F> {
207    fn from_python_macro(bytecode: &'static [u8], set_vars: F, panic: fn(String) -> !) -> Self {
208        Self {
209            bytecode,
210            set_vars,
211            panic,
212        }
213    }
214}
215
216/// Represents a `python!{}` block.
217#[cfg(not(doc))]
218pub struct PythonBlock<F> {
219    bytecode: &'static [u8],
220    set_vars: F,
221    panic: fn(String) -> !,
222}
223
224/// In the documentation, we just show `PythonBlock` in
225/// `Context::run`'s signature, without any generic arguments.
226#[cfg(doc)]
227#[doc(hidden)]
228pub struct PythonBlock;