extern crate proc_macro;
extern crate proc_macro2;
extern crate quote;
extern crate syn;
use std::iter::IntoIterator;
use proc_macro::TokenStream;
use proc_macro2::Span;
use quote::quote;
use syn::{parse_quote, parse_macro_input, parenthesized};
use syn::parse::{Parse, ParseStream, Result, Error};
use syn::fold::Fold;
use syn::spanned::Spanned;
use syn::{Token, LitFloat, Ident, Item, Field, Variant, Attribute};
const FEATURES_ENV: &'static str = env!("INKWELL_FEATURES");
fn get_feature_versions() -> Vec<&'static str> {
FEATURES_ENV
.split(',')
.collect()
}
fn get_latest_feature_index(features: &[&str]) -> usize {
features.len() - 1
}
fn get_feature_index(features: &[&str], feature: String, span: Span) -> Result<usize> {
let feat = feature.as_str();
match features.iter().position(|&s| s == feat) {
None => Err(Error::new(span, format!("Invalid feature version: {}, not defined", feature))),
Some(index) => Ok(index)
}
}
fn get_features(vt: VersionType) -> Result<Vec<&'static str>> {
let features = get_feature_versions();
let latest = get_latest_feature_index(&features);
match vt {
VersionType::Specific(version, span) => {
let feature = f64_to_feature_string(version);
let index = get_feature_index(&features, feature, span.clone())?;
Ok(features[index..=index].to_vec())
}
VersionType::InclusiveRangeToLatest(version, span) => {
let feature = f64_to_feature_string(version);
let index = get_feature_index(&features, feature, span.clone())?;
Ok(features[index..=latest].to_vec())
}
VersionType::InclusiveRange((start, start_span), (end, end_span)) => {
let start_feature = f64_to_feature_string(start);
let end_feature = f64_to_feature_string(end);
let start_index = get_feature_index(&features, start_feature, start_span.clone())?;
let end_index = get_feature_index(&features, end_feature, end_span.clone())?;
if end_index < start_index {
let message = format!("Invalid version range: {} must be greater than or equal to {}", start, end);
Err(Error::new(end_span, message))
} else {
Ok(features[start_index..=end_index].to_vec())
}
}
VersionType::ExclusiveRangeToLatest(version, span) => {
let feature = f64_to_feature_string(version);
let index = get_feature_index(&features, feature, span.clone())?;
if latest == index {
let message = format!("Invalid version range: {}..latest produces an empty feature set", version);
Err(Error::new(span, message))
} else {
Ok(features[index..latest].to_vec())
}
}
VersionType::ExclusiveRange((start, start_span), (end, end_span)) => {
let start_feature = f64_to_feature_string(start);
let end_feature = f64_to_feature_string(end);
let start_index = get_feature_index(&features, start_feature, start_span.clone())?;
let end_index = get_feature_index(&features, end_feature, end_span.clone())?;
if end_index == start_index {
let message = format!("Invalid version range: {}..{} produces an empty feature set", start, end);
Err(Error::new(start_span, message))
} else if end_index < start_index {
let message = format!("Invalid version range: {} must be greater than {}", start, end);
Err(Error::new(end_span, message))
} else {
Ok(features[start_index..end_index].to_vec())
}
}
}
}
fn f64_to_feature_string(float: f64) -> String {
let int = float as u64;
format!("llvm{}-{}", int, (float * 10.) % 10.)
}
#[derive(Debug)]
enum VersionType {
Specific(f64, Span),
InclusiveRange((f64, Span), (f64, Span)),
InclusiveRangeToLatest(f64, Span),
ExclusiveRange((f64, Span), (f64, Span)),
ExclusiveRangeToLatest(f64, Span),
}
impl Parse for VersionType {
fn parse(input: ParseStream) -> Result<Self> {
let lookahead = input.lookahead1();
if lookahead.peek(LitFloat) {
let from = input.parse::<LitFloat>().unwrap();
let from_val = from.value();
if input.is_empty() {
return Ok(VersionType::Specific(from_val, from.span()));
}
let lookahead = input.lookahead1();
if lookahead.peek(Token![..=]) {
let _: Token![..=] = input.parse().unwrap();
let lookahead = input.lookahead1();
if lookahead.peek(Ident) {
let to = input.parse::<Ident>().unwrap();
if to.to_string() == "latest" {
Ok(VersionType::InclusiveRangeToLatest(from_val, from.span()))
} else {
Err(Error::new(to.span(), "expected `latest` or `X.Y`"))
}
} else if lookahead.peek(LitFloat) {
let to = input.parse::<LitFloat>().unwrap();
let to_val = to.value();
Ok(VersionType::InclusiveRange((from_val, from.span()), (to_val, to.span())))
} else {
Err(lookahead.error())
}
} else if lookahead.peek(Token![..]) {
let _: Token![..] = input.parse().unwrap();
let lookahead = input.lookahead1();
if lookahead.peek(Ident) {
let to = input.parse::<Ident>().unwrap();
if to.to_string() == "latest" {
Ok(VersionType::ExclusiveRangeToLatest(from_val, from.span()))
} else {
Err(Error::new(to.span(), "expected `latest` or `X.Y`"))
}
} else if lookahead.peek(LitFloat) {
let to = input.parse::<LitFloat>().unwrap();
let to_val = to.value();
Ok(VersionType::ExclusiveRange((from_val, from.span()), (to_val, to.span())))
} else {
Err(lookahead.error())
}
} else {
Err(lookahead.error())
}
} else {
Err(lookahead.error())
}
}
}
#[derive(Debug)]
struct ParenthesizedFeatureSet(FeatureSet);
impl Parse for ParenthesizedFeatureSet {
fn parse(input: ParseStream) -> Result<Self> {
let content;
let _ = parenthesized!(content in input);
let features = content.parse::<FeatureSet>()?;
Ok(Self(features))
}
}
#[derive(Debug)]
struct FeatureSet(Vec<&'static str>, Option<Error>);
impl Default for FeatureSet {
fn default() -> Self {
Self(get_feature_versions(), None)
}
}
impl Parse for FeatureSet {
fn parse(input: ParseStream) -> Result<Self> {
let version_type = input.parse::<VersionType>()?;
let features = get_features(version_type)?;
Ok(Self(features, None))
}
}
impl IntoIterator for FeatureSet {
type Item = &'static str;
type IntoIter = std::vec::IntoIter<&'static str>;
fn into_iter(self) -> Self::IntoIter {
self.0.into_iter()
}
}
impl FeatureSet {
#[inline]
fn has_error(&self) -> bool {
self.1.is_some()
}
#[inline]
fn set_error(&mut self, err: Error) {
self.1 = Some(err);
}
fn to_error(self) -> Error {
self.1.unwrap()
}
fn to_compile_error(self) -> TokenStream {
TokenStream::from(self.1.unwrap().to_compile_error())
}
fn expand_llvm_versions_attr(&mut self, attr: &Attribute) -> Attribute {
if self.has_error() {
return attr.clone();
}
if !attr.path.is_ident("llvm_versions") {
return attr.clone();
}
match syn::parse2::<ParenthesizedFeatureSet>(attr.tts.clone()) {
Ok(ParenthesizedFeatureSet(features)) => {
parse_quote! {
#[cfg(any(#(feature = #features),*))]
}
}
Err(err) => {
self.set_error(err);
attr.clone()
}
}
}
}
impl Fold for FeatureSet {
fn fold_variant(&mut self, mut variant: Variant) -> Variant {
if self.has_error() {
return variant;
}
let attrs = variant.attrs
.iter()
.map(|attr| self.expand_llvm_versions_attr(attr))
.collect::<Vec<_>>();
variant.attrs = attrs;
variant
}
fn fold_field(&mut self, mut field: Field) -> Field {
if self.has_error() {
return field;
}
let attrs = field.attrs
.iter()
.map(|attr| self.expand_llvm_versions_attr(attr))
.collect::<Vec<_>>();
field.attrs = attrs;
field
}
}
#[proc_macro_attribute]
pub fn llvm_versions(attribute_args: TokenStream, attributee: TokenStream) -> TokenStream {
let mut features = parse_macro_input!(attribute_args as FeatureSet);
let attributee = parse_macro_input!(attributee as Item);
let folded = features.fold_item(attributee);
if features.has_error() {
return features.to_compile_error();
}
let q = quote! {
#[cfg(any(#(feature = #features),*))]
#folded
};
q.into()
}
#[proc_macro_attribute]
pub fn llvm_versioned_item(_attribute_args: TokenStream, attributee: TokenStream) -> TokenStream {
let attributee = parse_macro_input!(attributee as Item);
let mut features = FeatureSet::default();
let folded = features.fold_item(attributee);
if features.has_error() {
return features.to_compile_error();
}
quote!(#folded).into()
}
struct EnumVariant {
llvm_variant: Ident,
rust_variant: Ident,
attrs: Vec<Attribute>,
}
impl EnumVariant {
fn new(variant: &Variant) -> Self {
let rust_variant = variant.ident.clone();
let llvm_variant = Ident::new(&format!("LLVM{}", rust_variant.to_string()), variant.span());
let mut attrs = variant.attrs.clone();
attrs.retain(|attr| !attr.path.is_ident("llvm_variant"));
Self {
llvm_variant,
rust_variant,
attrs,
}
}
fn with_name(variant: &Variant, mut llvm_variant: Ident) -> Self {
let rust_variant = variant.ident.clone();
llvm_variant.set_span(rust_variant.span());
let mut attrs = variant.attrs.clone();
attrs.retain(|attr| !attr.path.is_ident("llvm_variant"));
Self {
llvm_variant,
rust_variant,
attrs,
}
}
}
#[derive(Default)]
struct EnumVariants {
variants: Vec<EnumVariant>,
error: Option<Error>,
}
impl EnumVariants {
#[inline]
fn len(&self) -> usize {
self.variants.len()
}
#[inline]
fn iter(&self) -> core::slice::Iter<'_, EnumVariant> {
self.variants.iter()
}
#[inline]
fn has_error(&self) -> bool {
self.error.is_some()
}
#[inline]
fn set_error(&mut self, err: &str, span: Span) {
self.error = Some(Error::new(span, err));
}
fn to_error(self) -> Error {
self.error.unwrap()
}
}
impl Fold for EnumVariants {
fn fold_variant(&mut self, mut variant: Variant) -> Variant {
use syn::{Meta, NestedMeta};
if self.has_error() {
return variant;
}
if let Some(attr) = variant.attrs.iter().find(|attr| attr.path.is_ident("llvm_variant")) {
if let Ok(Meta::List(meta)) = attr.parse_meta() {
if meta.nested.len() == 1 {
let variant_meta = meta.nested.first().unwrap();
if let NestedMeta::Meta(Meta::Word(name)) = variant_meta.value().clone() {
self.variants.push(EnumVariant::with_name(&variant, name.clone()));
variant.attrs.retain(|attr| !attr.path.is_ident("llvm_variant"));
return variant;
}
}
}
self.set_error("expected #[llvm_variant(VARIANT_NAME)]", attr.span());
return variant;
}
self.variants.push(EnumVariant::new(&variant));
variant
}
}
struct LLVMEnumType {
name: Ident,
decl: syn::ItemEnum,
variants: EnumVariants,
}
impl Parse for LLVMEnumType {
fn parse(input: ParseStream) -> Result<Self> {
let decl = input.parse::<syn::ItemEnum>()?;
let name = decl.ident.clone();
let mut features = FeatureSet::default();
let decl = features.fold_item_enum(decl);
if features.has_error() {
return Err(features.to_error());
}
let mut variants = EnumVariants::default();
let decl = variants.fold_item_enum(decl);
if variants.has_error() {
return Err(variants.to_error());
}
Ok(Self {
name,
decl,
variants,
})
}
}
#[proc_macro_attribute]
pub fn llvm_enum(attribute_args: TokenStream, attributee: TokenStream) -> TokenStream {
use syn::{Path, PatPath, Arm};
let llvm_ty = parse_macro_input!(attribute_args as Path);
let llvm_enum_type = parse_macro_input!(attributee as LLVMEnumType);
let mut from_arms = Vec::with_capacity(llvm_enum_type.variants.len());
for variant in llvm_enum_type.variants.iter() {
let src_variant = variant.llvm_variant.clone();
let src_attrs = variant.attrs.clone();
let src_ty = llvm_ty.clone();
let dst_variant = variant.rust_variant.clone();
let dst_ty = llvm_enum_type.name.clone();
let pat = PatPath {
qself: None,
path: parse_quote!(#src_ty::#src_variant),
};
let arm: Arm = parse_quote! {
#(#src_attrs)*
#pat => { #dst_ty::#dst_variant }
};
from_arms.push(arm);
}
let mut to_arms = Vec::with_capacity(llvm_enum_type.variants.len());
for variant in llvm_enum_type.variants.iter() {
let src_variant = variant.rust_variant.clone();
let src_attrs = variant.attrs.clone();
let src_ty = llvm_enum_type.name.clone();
let dst_variant = variant.llvm_variant.clone();
let dst_ty = llvm_ty.clone();
let pat = PatPath {
qself: None,
path: parse_quote!(#src_ty::#src_variant),
};
let arm: Arm = parse_quote! {
#(#src_attrs)*
#pat => { #dst_ty::#dst_variant }
};
to_arms.push(arm);
}
let enum_ty = llvm_enum_type.name.clone();
let enum_decl = llvm_enum_type.decl.clone();
let q = quote! {
#enum_decl
impl #enum_ty {
fn new(src: #llvm_ty) -> Self {
match src {
#(#from_arms)*
}
}
}
impl From<#llvm_ty> for #enum_ty {
fn from(src: #llvm_ty) -> Self {
Self::new(src)
}
}
impl Into<#llvm_ty> for #enum_ty {
fn into(self) -> #llvm_ty {
match self {
#(#to_arms),*
}
}
}
};
q.into()
}