1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
//! Helper crate for `inline-python` and `ct-python`.

#![feature(proc_macro_span)]

extern crate proc_macro;

use self::embed_python::EmbedPython;
use proc_macro::{Span, TokenStream as TokenStream1};
use proc_macro2::{Literal, TokenStream};
use pyo3::{ffi, types::PyBytes, AsPyPointer, FromPyPointer, PyObject, Python};
use quote::quote;
use std::ffi::CString;

mod embed_python;
mod error;
mod run;

fn python_impl(input: TokenStream) -> Result<TokenStream, TokenStream> {
	let tokens = input.clone();

	check_no_attribute(input.clone())?;

	let filename = Span::call_site().source_file().path().to_string_lossy().into_owned();

	let mut x = EmbedPython::new();

	x.add(input)?;

	let EmbedPython { python, variables, .. } = x;

	let python = CString::new(python).unwrap();
	let filename = CString::new(filename).unwrap();

	let bytecode = unsafe {
		let gil = Python::acquire_gil();
		let py = gil.python();

		let code = PyObject::from_owned_ptr_or_err(py, ffi::Py_CompileString(python.as_ptr(), filename.as_ptr(), ffi::Py_file_input))
			.map_err(|err| error::compile_error_msg(py, err, tokens))?;

		Literal::byte_string(
			PyBytes::from_owned_ptr_or_err(py, ffi::PyMarshal_WriteObjectToString(code.as_ptr(), pyo3::marshal::VERSION))
				.map_err(|_e| quote!(compile_error! {"failed to generate python bytecode"}))?
				.as_bytes(),
		)
	};

	let varname = variables.keys();
	let var = variables.values();

	Ok(quote! {
		::inline_python::FromInlinePython::from_python_macro(
			#bytecode,
			|globals| {
				#(
					globals
						.set_item(#varname, #var)
						.expect("Unable to convert variable to Python");
				)*
			},
		)
	})
}

fn ct_python_impl(input: TokenStream) -> Result<TokenStream, TokenStream> {
	let tokens = input.clone();

	let filename = Span::call_site().source_file().path().to_string_lossy().into_owned();

	let mut x = EmbedPython::new();

	x.compile_time = true;

	x.add(input)?;

	let EmbedPython { python, .. } = x;

	let python = CString::new(python).unwrap();
	let filename = CString::new(filename).unwrap();

	let gil = Python::acquire_gil();
	let py = gil.python();

	let code = unsafe {
		PyObject::from_owned_ptr_or_err(py, ffi::Py_CompileString(python.as_ptr(), filename.as_ptr(), ffi::Py_file_input))
			.map_err(|err| error::compile_error_msg(py, err, tokens.clone()))?
	};

	run::run_ct_python(py, code, tokens)
}

fn check_no_attribute(input: TokenStream) -> Result<(), TokenStream> {
	let mut input = input.into_iter();
	if let Some(token) = input.next() {
		if token.to_string() == "#"
			&& input.next().map_or(false, |t| t.to_string() == "!")
			&& input.next().map_or(false, |t| t.to_string().starts_with('['))
		{
			return Err(quote!(compile_error! {
				"Attributes in python!{} are no longer supported. \
				Use context.run(python!{..}) to use a context.",
			}));
		}
	}
	Ok(())
}

#[doc(hidden)]
#[proc_macro]
pub fn python(input: TokenStream1) -> TokenStream1 {
	TokenStream1::from(match python_impl(TokenStream::from(input)) {
		Ok(tokens) => tokens,
		Err(tokens) => tokens,
	})
}

#[doc(hidden)]
#[proc_macro]
pub fn ct_python(input: TokenStream1) -> TokenStream1 {
	TokenStream1::from(match ct_python_impl(TokenStream::from(input)) {
		Ok(tokens) => tokens,
		Err(tokens) => tokens,
	})
}