inline_python_macros/
lib.rs

1//! Helper crate for `inline-python` and `ct-python`.
2
3#![feature(proc_macro_span)]
4
5extern crate proc_macro;
6
7use self::embed_python::EmbedPython;
8use proc_macro::{Span, TokenStream as TokenStream1};
9use proc_macro2::{Literal, TokenStream};
10use pyo3::{ffi, Py, PyObject, Python};
11use quote::quote;
12use std::ffi::CString;
13
14mod embed_python;
15mod error;
16mod run;
17
18fn python_impl(input: TokenStream) -> Result<TokenStream, TokenStream> {
19	let tokens = input.clone();
20
21	check_no_attribute(input.clone())?;
22
23	let filename = Span::call_site().source_file().path().to_string_lossy().into_owned();
24
25	let mut x = EmbedPython::new();
26
27	x.add(input)?;
28
29	let EmbedPython { python, variables, .. } = x;
30
31	let python = CString::new(python).unwrap();
32	let filename = CString::new(filename).unwrap();
33
34	let bytecode = unsafe {
35		let result: Result<Literal, TokenStream> = Python::with_gil(|py| {
36			let code = PyObject::from_owned_ptr_or_err(py, ffi::Py_CompileString(python.as_ptr(), filename.as_ptr(), ffi::Py_file_input))
37				.map_err(|err| error::compile_error_msg(py, err, tokens))?;
38
39			Ok(Literal::byte_string(
40				Py::from_owned_ptr_or_err(py, ffi::PyMarshal_WriteObjectToString(code.as_ptr(), pyo3::marshal::VERSION))
41					.map_err(|_e| quote!(compile_error! {"failed to generate python bytecode"}))?
42					.as_bytes(py),
43			))
44		});
45		result?
46	};
47
48	let varname = variables.keys();
49	let var = variables.values();
50
51	Ok(quote! {
52		::inline_python::FromInlinePython::from_python_macro(
53			#bytecode,
54			|globals| {
55				#(
56					::inline_python::pyo3::prelude::PyDictMethods::set_item(
57						globals,
58						#varname,
59						#var
60					).expect("Unable to convert variable to Python");
61				)*
62			},
63		)
64	})
65}
66
67fn ct_python_impl(input: TokenStream) -> Result<TokenStream, TokenStream> {
68	let tokens = input.clone();
69
70	let filename = Span::call_site().source_file().path().to_string_lossy().into_owned();
71
72	let mut x = EmbedPython::new();
73
74	x.compile_time = true;
75
76	x.add(input)?;
77
78	let EmbedPython { python, .. } = x;
79
80	let python = CString::new(python).unwrap();
81	let filename = CString::new(filename).unwrap();
82
83	Python::with_gil(|py| {
84		let code = unsafe {
85			PyObject::from_owned_ptr_or_err(py, ffi::Py_CompileString(python.as_ptr(), filename.as_ptr(), ffi::Py_file_input))
86				.map_err(|err| error::compile_error_msg(py, err, tokens.clone()))?
87		};
88
89		run::run_ct_python(py, code, tokens)
90	})
91}
92
93fn check_no_attribute(input: TokenStream) -> Result<(), TokenStream> {
94	let mut input = input.into_iter();
95	if let Some(token) = input.next() {
96		if token.to_string() == "#"
97			&& input.next().map_or(false, |t| t.to_string() == "!")
98			&& input.next().map_or(false, |t| t.to_string().starts_with('['))
99		{
100			return Err(quote!(compile_error! {
101				"Attributes in python!{} are no longer supported. \
102				Use context.run(python!{..}) to use a context.",
103			}));
104		}
105	}
106	Ok(())
107}
108
109#[doc(hidden)]
110#[proc_macro]
111pub fn python(input: TokenStream1) -> TokenStream1 {
112	TokenStream1::from(match python_impl(TokenStream::from(input)) {
113		Ok(tokens) => tokens,
114		Err(tokens) => tokens,
115	})
116}
117
118#[doc(hidden)]
119#[proc_macro]
120pub fn ct_python(input: TokenStream1) -> TokenStream1 {
121	TokenStream1::from(match ct_python_impl(TokenStream::from(input)) {
122		Ok(tokens) => tokens,
123		Err(tokens) => tokens,
124	})
125}