inline_python_macros/
lib.rs

1//! Helper crate for `inline-python`.
2
3#![feature(proc_macro_span)]
4
5extern crate proc_macro;
6
7use proc_macro::{Delimiter, Group, Literal, Span, TokenStream, TokenTree};
8use pyo3::{Py, Python};
9use std::{
10	collections::BTreeMap,
11	ffi::{CStr, CString},
12};
13
14mod shared;
15use shared::*;
16
17#[doc(hidden)]
18#[proc_macro]
19pub fn python(input: TokenStream) -> TokenStream {
20	python_impl(input).unwrap_or_else(|e| e)
21}
22
23#[rustfmt::skip]
24fn python_impl(input: TokenStream) -> Result<TokenStream, TokenStream> {
25	let mut variables = BTreeMap::new();
26	let python = CString::new(python_from_macro(input.clone(), Some(&mut variables))?).unwrap();
27	let filename = CString::new(Span::call_site().file()).unwrap();
28	let bytecode = compile_to_bytecode(&python, &filename, input)?;
29	Ok(TokenStream::from_iter([
30		punct(':'), punct(':'), ident("inline_python"),
31		punct(':'), punct(':'), ident("FromInlinePython"),
32		punct(':'), punct(':'), ident("from_python_macro"),
33		parens([
34			TokenTree::Literal(bytecode), punct(','),
35			punct('|'), ident("globals"), punct('|'),
36			braces(variables.into_iter().flat_map(|(key, value)| [
37				punct(':'), punct(':'), ident("inline_python"),
38				punct(':'), punct(':'), ident("pyo3"),
39				punct(':'), punct(':'), ident("prelude"),
40				punct(':'), punct(':'), ident("PyDictMethods"),
41				punct(':'), punct(':'), ident("set_item"),
42				parens([
43					ident("globals"), punct(','),
44					string(&key), punct(','),
45					TokenTree::Ident(value)
46				]),
47				punct('.'), ident("expect"), parens([string("python")]),
48				punct(';'),
49			])),
50			punct(','),
51			punct('|'), ident("e"), punct('|'),
52			punct(':'), punct(':'), ident("std"),
53			punct(':'), punct(':'), ident("panic"),
54			punct(':'), punct(':'), ident("panic_any"),
55			parens([ident("e")]),
56		]),
57	]))
58}
59
60fn parens(t: impl IntoIterator<Item = TokenTree>) -> TokenTree {
61	TokenTree::Group(Group::new(Delimiter::Parenthesis, TokenStream::from_iter(t)))
62}
63
64fn compile_to_bytecode(python: &CStr, filename: &CStr, tokens: TokenStream) -> Result<Literal, TokenStream> {
65	Python::with_gil(|py| {
66		let compiled = compile_python(py, python, filename, tokens)?;
67		let bytes = unsafe {
68			let ptr = pyo3::ffi::PyMarshal_WriteObjectToString(compiled.as_ptr(), pyo3::marshal::VERSION);
69			Py::from_owned_ptr(py, ptr)
70		};
71		Ok(Literal::byte_string(bytes.as_bytes(py)))
72	})
73}