myd 0.1.1

An implementation of the rust module system
Documentation
//! Types for working with condition compilation

use std::collections::HashSet;

use crate::platforms::{Endian, SystemInfo};
use proc_macro2::Ident;
use syn::punctuated::Punctuated;
use syn::token::Paren;
use syn::{parenthesized, Meta};
use syn::{
    parse::{Parse, ParseStream},
    LitStr, Token,
};

/// A group of CfgAttr
///
/// This stores the cfg_attr, and any attributes it so enables
#[derive(Debug)]
pub struct CfgAttrGroup {
    /// The actuall predicate
    pub cfg: CfgPredicate,
    /// A comma
    pub comma: Token![,],
    /// The attributes which this enables
    pub attrs: Punctuated<Meta, Token![,]>,
}

impl Parse for CfgAttrGroup {
    fn parse(input: ParseStream) -> syn::Result<Self> {
        Ok(Self {
            cfg: input.parse()?,
            comma: input.parse()?,
            attrs: Punctuated::parse_terminated(input)?,
        })
    }
}

/// A trait to represent that something can be
/// evaluated depending on the current features
pub trait CfgEval {
    /// Does this cfg evaluate to true or false (should the code be generated)?
    fn eval(&self, features: &HashSet<String>, platform: &SystemInfo) -> bool;
}

/// A simple wrapper to allow every field of a struct to be parsed
macro_rules! parse_struct {
    ($s:ty, $($i:ident),*) => {
        impl Parse for $s {
            fn parse(input: ParseStream) -> syn::Result<Self> {
                let x = Ok(
                    Self {
                        $($i: Parse::parse(input)?),*
                    }
                );
                x
            }
        }
    }
}

/// A list of `CfgPredicate`
pub type CfgPredicateList = Punctuated<CfgPredicate, Token![,]>;

/// A type conditional compilation
///
/// See [The Rust Reference](https://doc.rust-lang.org/reference/conditional-compilation.html)
#[derive(Debug, PartialEq, Eq)]
pub enum CfgPredicate {
    /// An actual option (enabling or disabling a feature)
    Option(CfgOption),
    /// A [`CfgAll`]
    All(CfgAll),
    /// A [`CfgNot`]
    Not(CfgNot),
    /// A [`CfgAny`]
    Any(CfgAny),
}

impl CfgEval for CfgPredicate {
    fn eval(&self, features: &HashSet<String>, platform: &SystemInfo) -> bool {
        match self {
            Self::Option(x) => x.eval(features, platform),
            Self::All(x) => x.eval(features, platform),
            Self::Not(x) => x.eval(features, platform),
            Self::Any(x) => x.eval(features, platform),
        }
    }
}

impl Parse for CfgPredicate {
    fn parse(input: ParseStream) -> syn::Result<Self> {
        let lookahead = input.lookahead1();
        if lookahead.peek(kw::all) {
            input.parse().map(CfgPredicate::All)
        } else if lookahead.peek(kw::not) {
            input.parse().map(CfgPredicate::Not)
        } else if lookahead.peek(kw::any) {
            input.parse().map(CfgPredicate::Any)
        } else {
            input.parse().map(CfgPredicate::Option)
        }
    }
}

/// A configuration option
#[derive(Debug, PartialEq, Eq)]
pub struct CfgOption {
    /// The identifier of this, e.g. `feature`
    pub ident: Ident,
    /// The value which this equates if present
    pub inner: Option<CfgOptionInner>,
}

/// The value assigned to a [`CfgOption`]
#[derive(Debug, PartialEq, Eq)]
pub struct CfgOptionInner {
    /// An equals token
    pub eq: Token![=],
    /// The value, e.g. `foo` in `feature = foo`
    pub val: LitStr,
}

impl CfgEval for CfgOption {
    fn eval(&self, features: &HashSet<String>, platform: &SystemInfo) -> bool {
        let inner = self.inner.as_ref().map(|inner| inner.val.value());
        let ident = self.ident.to_string();
        match ident.as_str() {
            "feature" => match inner {
                Some(inner) => features.contains(&inner),
                None => true,
            },
            "target_arch" => match inner {
                Some(inner) => platform.target_arch == inner,
                None => false,
            },
            "target_feature" => match inner {
                Some(inner) => platform.target_features.contains(&inner),
                None => false,
            },
            "target_os" => match inner {
                Some(inner) => platform.target_os == inner,
                None => false,
            },
            "target_family" => match inner {
                Some(inner) => platform.target_family.contains(&inner),
                None => false,
            },
            "target_env" => match inner {
                Some(inner) => platform.target_env == inner,
                None => false,
            },
            "target_endian" => match inner {
                Some(inner) => inner
                    .parse::<Endian>()
                    .map(|x| x == platform.target_endian)
                    .unwrap_or(false),
                None => false,
            },
            "target_pointer_width" => match inner {
                Some(inner) => inner
                    .parse::<usize>()
                    .map(|x| x == platform.target_pointer_width)
                    .unwrap_or(false),
                None => false,
            },
            "target_vendor" => match inner {
                Some(inner) => platform.target_vendor == inner,
                None => false,
            },
            "test" => platform.test,
            "debug_assertions" => platform.debug_assertions,
            "proc_macro" => platform.proc_macro,
            "panic" => match inner {
                Some(inner) => platform.panic == inner,
                None => false,
            },
            _ => false,
        }
    }
}
parse_struct! { CfgOptionInner, eq, val }

impl Parse for CfgOption {
    fn parse(input: ParseStream) -> syn::Result<Self> {
        Ok(Self {
            ident: input.parse()?,
            inner: {
                let lookahead = input.lookahead1();
                if lookahead.peek(Token!(=)) {
                    Some(input.parse()?)
                } else {
                    None
                }
            },
        })
    }
}

/// A cfg predicate where all options must be true
#[derive(Debug, PartialEq, Eq)]
pub struct CfgAll {
    /// The keyword
    pub all: kw::all,
    /// The parenthesis
    pub paren_token: Paren,
    /// The list of predicates that must be true
    pub elem: CfgPredicateList,
}

impl CfgEval for CfgAll {
    fn eval(&self, features: &HashSet<String>, platform: &SystemInfo) -> bool {
        let mut x = true;
        for elem in &self.elem {
            x &= elem.eval(features, platform);
        }
        x
    }
}

/// A cfg predicate which logically nots its `elem`
#[derive(Debug, PartialEq, Eq)]
pub struct CfgNot {
    /// The keyword
    pub not: kw::not,
    /// The parentheses
    pub paren_token: Paren,
    /// The internal element which is notted
    pub elem: Box<CfgPredicate>,
}

/// A cfg predicate which acts as an `or`
#[derive(Debug, PartialEq, Eq)]
pub struct CfgAny {
    /// The keyword
    pub any: kw::any,
    /// The parentheses
    pub paren_token: Paren,
    /// The elements
    ///
    /// To evaluate to `true` one of
    /// these must evaluate to `true`
    pub elem: CfgPredicateList,
}

impl CfgEval for CfgNot {
    fn eval(&self, features: &HashSet<String>, platform: &SystemInfo) -> bool {
        !self.elem.eval(features, platform)
    }
}

impl CfgEval for CfgAny {
    fn eval(&self, features: &HashSet<String>, platform: &SystemInfo) -> bool {
        let mut x = false;
        for elem in &self.elem {
            x |= elem.eval(features, platform);
        }
        x
    }
}
impl Parse for CfgAll {
    fn parse(input: ParseStream) -> syn::Result<Self> {
        let content;
        Ok(Self {
            all: input.parse()?,
            paren_token: parenthesized!(content in input),
            elem: CfgPredicateList::parse_terminated(&content)?,
        })
    }
}

impl Parse for CfgAny {
    fn parse(input: ParseStream) -> syn::Result<Self> {
        let content;
        Ok(Self {
            any: input.parse()?,
            paren_token: parenthesized!(content in input),
            elem: CfgPredicateList::parse_terminated(&content)?,
        })
    }
}

impl Parse for CfgNot {
    fn parse(input: ParseStream) -> syn::Result<Self> {
        let content;
        Ok(Self {
            not: input.parse()?,
            paren_token: parenthesized!(content in input),
            elem: content.parse()?,
        })
    }
}

/// Creates some custom syn keywords
mod kw {
    syn::custom_keyword!(cfg);
    syn::custom_keyword!(all);
    syn::custom_keyword!(not);
    syn::custom_keyword!(any);
}

#[cfg(test)]
mod test {
    use proc_macro2::Span;

    use super::*;

    #[test]
    fn test_cfg_option() {
        let res: CfgOption = syn::parse_quote! {
            feature = "true"
        };

        assert_eq!(res.ident.to_string(), "feature");
        assert_eq!(res.inner.unwrap().val.value(), "true");

        let res: CfgOption = syn::parse_quote! {
            test
        };

        assert_eq!(res.ident.to_string(), "test");
        assert!(res.inner.is_none());
    }

    #[test]
    fn test_not() {
        let res: CfgNot = syn::parse_quote! {
            not(feature = "true")
        };

        if let CfgPredicate::Option(res) = *res.elem {
            assert_eq!(res.ident.to_string(), "feature");
            assert_eq!(res.inner.unwrap().val.value(), "true");
        } else {
            panic!("Should be an option");
        }
    }

    #[test]
    fn test_any() {
        let res: CfgAny = syn::parse_quote! {
            any(feature = "f1", feature = "f2")
        };

        assert_eq!(
            res,
            CfgAny {
                any: kw::any(Span::call_site()),
                paren_token: Paren::default(),
                elem: {
                    let mut p = Punctuated::new();
                    p.push(CfgPredicate::Option(CfgOption {
                        ident: Ident::new("feature", Span::call_site()),
                        inner: Some(CfgOptionInner {
                            eq: Token!(=)(Span::call_site()),
                            val: LitStr::new("f1", Span::call_site()),
                        }),
                    }));
                    p.push(CfgPredicate::Option(CfgOption {
                        ident: Ident::new("feature", Span::call_site()),
                        inner: Some(CfgOptionInner {
                            eq: Token!(=)(Span::call_site()),
                            val: LitStr::new("f2", Span::call_site()),
                        }),
                    }));
                    p
                }
            }
        );
    }
}