#![warn(missing_debug_implementations)]
#![warn(missing_docs)]
use std::{
collections::{HashMap, HashSet},
fmt::Debug,
ops::{RangeFrom, RangeTo},
str,
};
use nom::{
branch::alt,
combinator::{all_consuming, map, opt},
multi::fold_many0,
sequence::{preceded, terminated, tuple},
AsChar, Compare, FindSubstring, FindToken, IResult, InputIter, InputLength, InputTake, InputTakeAtPosition, Offset,
ParseTo, Slice,
};
use proc_macro2::{Ident, Span, TokenStream};
use quote::{quote, TokenStreamExt};
pub mod ast;
pub use ast::*;
mod expand;
pub use expand::expand;
mod error;
pub use error::*;
mod macro_body;
pub use macro_body::*;
mod context;
pub use context::*;
#[derive(Debug, Clone)]
pub struct VarMacro {
pub name: String,
pub value: Expr,
}
impl VarMacro {
pub fn parse<I, C>(name: I, value: &[I]) -> Result<Self, crate::Error>
where
I: Debug
+ InputTake
+ InputLength
+ InputIter<Item = C>
+ InputTakeAtPosition<Item = C>
+ Slice<RangeFrom<usize>>
+ Slice<RangeTo<usize>>
+ Compare<&'static str>
+ FindSubstring<&'static str>
+ ParseTo<f64>
+ ParseTo<f32>
+ Offset
+ Clone,
C: AsChar + Copy,
&'static str: FindToken<<I as InputIter>::Item>,
{
let name = if let Ok((_, name)) = identifier(&[name]) { name } else { return Err(crate::Error::ParserError) };
let ctx = ParseContext::var_macro(&name);
let body = match MacroBody::parse(value, &ctx) {
Ok((_, body)) => body,
Err(_) => return Err(crate::Error::ParserError),
};
let value = match body {
MacroBody::Statement(_) => return Err(crate::Error::ParserError),
MacroBody::Expr(expr) => expr,
};
Ok(Self { name, value })
}
pub fn generate<C>(&mut self, cx: C) -> Result<(TokenStream, Option<TokenStream>), crate::Error>
where
C: CodegenContext,
{
let mut ctx = LocalContext::new(&self.name, &cx);
let mut tokens = TokenStream::new();
let ty = self.value.finish(&mut ctx)?;
self.value.to_tokens(&mut ctx, &mut tokens);
let ty = if let Expr::Literal(Lit::String(_)) = self.value {
let ffi_prefix = ctx.trait_prefix().map(|trait_prefix| quote! { #trait_prefix ffi:: });
Some(quote! { & #ffi_prefix CStr })
} else {
ty.map(|ty| ty.to_token_stream(&mut ctx))
};
Ok((tokens, ty))
}
}
#[derive(Debug, Clone)]
pub struct FnMacro {
pub name: String,
pub args: Vec<String>,
pub body: MacroBody,
}
impl FnMacro {
fn parse_args<I>(input: &[I]) -> IResult<&[I], Vec<String>>
where
I: Debug
+ InputTake
+ InputLength
+ InputIter
+ Slice<RangeFrom<usize>>
+ Compare<&'static str>
+ FindSubstring<&'static str>
+ Clone,
<I as InputIter>::Item: AsChar,
{
all_consuming(terminated(
alt((
map(preceded(meta, token("...")), |var_arg| vec![var_arg.to_owned()]),
map(
tuple((
fold_many0(preceded(meta, identifier), Vec::new, |mut acc, arg| {
acc.push(arg);
acc
}),
preceded(meta, opt(map(token("..."), |var_arg| var_arg.to_owned()))),
)),
|(arguments, var_arg)| {
let mut arguments = arguments.to_vec();
if let Some(var_arg) = var_arg {
arguments.push(var_arg);
}
arguments
},
),
)),
meta,
))(input)
}
pub fn parse<I, C>(name: I, args: &[I], body: &[I]) -> Result<Self, crate::Error>
where
I: Debug
+ InputTake
+ InputLength
+ InputIter<Item = C>
+ InputTakeAtPosition<Item = C>
+ Slice<RangeFrom<usize>>
+ Slice<RangeTo<usize>>
+ Compare<&'static str>
+ FindSubstring<&'static str>
+ ParseTo<f64>
+ ParseTo<f32>
+ Offset
+ Clone,
C: AsChar + Copy,
&'static str: FindToken<<I as InputIter>::Item>,
{
let (_, name) = identifier(&[name]).map_err(|_| crate::Error::ParserError)?;
let (_, args) = Self::parse_args(args).map_err(|_| crate::Error::ParserError)?;
let ctx_args = args.iter().map(|a| a.as_str()).collect::<Vec<_>>();
let ctx = ParseContext::fn_macro(&name, &ctx_args);
let (_, body) = MacroBody::parse(body, &ctx).map_err(|_| crate::Error::ParserError)?;
Ok(Self { name, args, body })
}
pub(crate) fn call<C>(
mut self,
root_name: &str,
names: &HashSet<String>,
args: &[Expr],
ctx: &LocalContext<C>,
) -> Result<MacroBody, crate::Error>
where
C: CodegenContext,
{
if ctx.names.contains(&self.name) {
return Err(crate::Error::RecursiveDefinition(self.name))
}
let mut names = names.clone();
names.insert(self.name.clone());
let arg_values = self.args.into_iter().zip(args.iter()).collect();
let mut ctx = LocalContext::new_with_args(root_name, arg_values, ctx.global_context);
self.body.finish(&mut ctx)?;
Ok(self.body)
}
pub fn generate<C>(&mut self, cx: C) -> Result<TokenStream, crate::Error>
where
C: CodegenContext,
{
let mut tokens = TokenStream::new();
let arg_types = self
.args
.iter()
.map(|arg| {
let ty = if let Some(arg_ty) = cx.macro_arg_ty(&self.name, arg) {
let arg_ty = syn::parse_str::<syn::Type>(&arg_ty).unwrap();
MacroArgType::Known(Type::try_from(arg_ty)?)
} else {
MacroArgType::Unknown
};
Ok((arg.to_owned(), ty))
})
.collect::<Result<_, _>>()?;
let mut ctx = LocalContext::new(&self.name, &cx);
ctx.arg_types = arg_types;
let ret_ty = self.body.finish(&mut ctx)?;
ctx.export_as_macro = ctx.export_as_macro
|| (ctx.function(&self.name).is_some() && ctx.function_macro(&self.name).is_some())
|| ctx.is_variadic()
|| !ctx.arg_types.iter().all(|(_, ty)| matches!(*ty, MacroArgType::Known(_)))
|| ret_ty.is_none();
let name = Ident::new(&self.name, Span::call_site());
let mut body = TokenStream::new();
match &self.body {
MacroBody::Statement(stmt) => stmt.to_tokens(&mut ctx, &mut body),
MacroBody::Expr(expr) => expr.to_tokens(&mut ctx, &mut body),
}
if ctx.export_as_macro {
let args = self
.args
.iter()
.map(|arg| {
if arg == "..." {
quote! { $($__VA_ARGS__:expr),* }
} else {
let id = Ident::new(arg, Span::call_site());
let ty = ctx.arg_type(arg).unwrap();
if matches!(ty, MacroArgType::Ident) {
quote! { $#id:ident }
} else {
quote! { $#id:expr }
}
}
})
.collect::<Vec<_>>();
let macro_id = Ident::new(&format!("__cmacro__{}", self.name), Span::call_site());
tokens.append_all(quote! {
#[doc(hidden)]
#[macro_export]
macro_rules! #macro_id {
(#(#args),*) => {
#body
};
}
pub use #macro_id as #name;
})
} else {
let func_args = self
.args
.iter()
.map(|arg| {
if let Some(MacroArgType::Known(ty)) = ctx.arg_types.remove(arg) {
let id = Ident::new(arg, Span::call_site());
let ty = ty.to_token_stream(&mut ctx);
quote! { #id: #ty }
} else {
unreachable!()
}
})
.collect::<Vec<_>>();
let return_type = ret_ty.and_then(|ty| {
if ty.is_void() {
return None
}
let ty = ty.to_token_stream(&mut ctx);
Some(quote! { -> #ty })
});
let semicolon = if return_type.is_none() { Some(quote! { ; }) } else { None };
tokens.append_all(quote! {
#[allow(non_snake_case, unused_mut, unsafe_code)]
#[inline(always)]
pub unsafe extern "C" fn #name(#(mut #func_args),*) #return_type {
#body
#semicolon
}
})
}
Ok(tokens)
}
}