attribution 0.5.0

A declarative custom attribute parsing framework
Documentation
use crate::ParamVal;
use core::convert::TryFrom;
use core::convert::TryInto;
use syn::parse::Error as ParseError;
use syn::parse::Parse;
use syn::parse::ParseStream;
use syn::parse::Result as ParseResult;
use syn::Expr;
use syn::ExprArray;
use syn::ExprLit;
use syn::ExprUnary;
use syn::Lit;
use syn::UnOp;

impl Parse for ParamVal {
    fn parse(input: ParseStream) -> ParseResult<Self> {
        input.parse::<Expr>().and_then(TryInto::try_into)
    }
}

impl TryFrom<Expr> for ParamVal {
    type Error = ParseError;
    fn try_from(value: Expr) -> Result<Self, Self::Error> {
        match value {
            Expr::Array(expr_array) => expr_array.try_into(),
            Expr::Lit(expr_lit) => expr_lit.try_into(),
            Expr::Unary(expr_unary) => expr_unary.try_into(),
            other_expr => Err(ParseError::new_spanned(
                other_expr,
                "Unsupported expression",
            )),
        }
    }
}

impl TryFrom<ExprArray> for ParamVal {
    type Error = ParseError;
    fn try_from(expr_array: ExprArray) -> Result<Self, Self::Error> {
        expr_array
            .elems
            .into_iter()
            .map(|expr| ParamVal::try_from(expr))
            .collect::<Result<Vec<ParamVal>, _>>()
            .map(|vals| ParamVal::Array(vals))
    }
}

impl TryFrom<ExprLit> for ParamVal {
    type Error = ParseError;
    fn try_from(ExprLit { lit, .. }: ExprLit) -> Result<Self, Self::Error> {
        lit.try_into()
    }
}

impl TryFrom<ExprUnary> for ParamVal {
    type Error = ParseError;
    fn try_from(ExprUnary { expr, op, .. }: ExprUnary) -> Result<Self, Self::Error> {
        match op {
            UnOp::Neg(_) => match *expr {
                Expr::Lit(ExprLit { lit, .. }) => match lit {
                    Lit::Int(i) => i.base10_parse::<i64>().map(|int| ParamVal::Int(-int)),
                    Lit::Float(f) => f.base10_parse::<f64>().map(|float| ParamVal::Float(-float)),
                    other_neg_lit => Err(ParseError::new_spanned(
                        other_neg_lit,
                        "Non-negatable literal",
                    )),
                },
                other_neg_expr => Err(ParseError::new_spanned(
                    other_neg_expr,
                    "Non-negatable expression",
                )),
            },
            other_op => Err(ParseError::new_spanned(
                other_op,
                "Unsupported parameter value operator",
            )),
        }
    }
}

impl TryFrom<Lit> for ParamVal {
    type Error = ParseError;
    fn try_from(lit: Lit) -> Result<Self, Self::Error> {
        match lit {
            Lit::Bool(b) => Ok(ParamVal::Bool(b.value)),
            Lit::Int(i) => i.base10_parse::<i64>().map(|int| ParamVal::Int(int)),
            Lit::Float(f) => f.base10_parse::<f64>().map(|float| ParamVal::Float(float)),
            Lit::Str(s) => Ok(ParamVal::Str(s.value())),
            other_lit => Err(ParseError::new_spanned(other_lit, "Unrecognized literal")),
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use syn::parse_quote;

    #[test]
    fn parse_array() {
        let array_val: ParamVal = parse_quote!([1, 2, 3]);
        assert_eq!(array_val, ParamVal::Array(vec![ParamVal::Int(1), ParamVal::Int(2), ParamVal::Int(3)]))
    }

    #[test]
    fn parse_bool() {
        let bool_val: ParamVal = parse_quote!(true);
        assert_eq!(bool_val, ParamVal::Bool(true));
    }

    #[test]
    fn parse_unsigned_float() {
        let float_val: ParamVal = parse_quote!(1.0);
        assert_eq!(float_val, ParamVal::Float(1.0));
    }

    #[test]
    fn parse_signed_float() {
        let float_val: ParamVal = parse_quote!(-1.0);
        assert_eq!(float_val, ParamVal::Float(-1.0));
    }

    #[test]
    fn parse_unsigned_int() {
        let int_val: ParamVal = parse_quote!(1);
        assert_eq!(int_val, ParamVal::Int(1));
    }

    #[test]
    fn parse_signed_int() {
        let int_val: ParamVal = parse_quote!(-1);
        assert_eq!(int_val, ParamVal::Int(-1));
    }

    #[test]
    fn parse_string() {
        let str_val: ParamVal = parse_quote!("foo");
        assert_eq!(str_val, "foo".into())
    }
}