diman_unit_system 0.2.0

Internal procedural macros for diman.
Documentation
use syn::{
    parse::{Parse, ParseStream},
    token::Paren,
    *,
};

use crate::expression::Expr;
use crate::expression::Factor;

impl<T: Parse> Parse for Factor<T> {
    fn parse(input: ParseStream) -> Result<Self> {
        let lookahead = input.lookahead1();
        if lookahead.peek(Paren) {
            let content;
            let _: token::Paren = parenthesized!(content in input);
            Ok(Self::ParenExpr(Box::new(content.parse()?)))
        } else {
            Ok(Self::Value(input.parse()?))
        }
    }
}

impl<T: Parse> Parse for Expr<T> {
    fn parse(input: ParseStream) -> Result<Self> {
        let first_factor: Factor<T> = input.parse()?;
        let lookahead = input.lookahead1();
        if input.is_empty() || lookahead.peek(Token![,]) {
            Ok(Self::Value(first_factor))
        } else if lookahead.peek(Token![*]) {
            let _: Token![*] = input.parse().unwrap();
            let second_expr: Expr<T> = input.parse()?;
            Ok(Self::Times(first_factor, Box::new(second_expr)))
        } else if lookahead.peek(Token![/]) {
            let _: Token![/] = input.parse().unwrap();
            let second_expr: Expr<T> = input.parse()?;
            Ok(Self::Over(first_factor, Box::new(second_expr)))
        } else {
            Err(lookahead.error())
        }
    }
}

#[cfg(test)]
pub mod tests {
    use proc_macro2::TokenStream;
    use quote::quote;

    use super::Expr;
    use syn::{
        parse::{self, Parse},
        Lit, Result,
    };

    #[derive(Debug, PartialEq, Eq, Clone)]
    pub struct MyInt(pub isize);

    impl Parse for MyInt {
        fn parse(input: parse::ParseStream) -> Result<Self> {
            let val: Lit = input.parse()?;
            match val {
                Lit::Int(x) => Ok(MyInt(x.base10_parse().unwrap())),
                _ => panic!(),
            }
        }
    }

    pub fn parse_expr(input: TokenStream) -> Expr<MyInt> {
        syn::parse2(input).unwrap()
    }

    #[test]
    fn parse_exprs() {
        use super::Expr::{Over, Times};
        use super::Factor::*;
        let x = parse_expr(quote! { 1 });
        assert_eq!(x, Expr::Value(Value(MyInt(1))));
        let x = parse_expr(quote! { 1 * 2 });
        assert_eq!(
            x,
            Times(Value(MyInt(1)), Box::new(Expr::Value(Value(MyInt(2)))))
        );
        let x = parse_expr(quote! { 1 / 2 });
        assert_eq!(
            x,
            Over(Value(MyInt(1)), Box::new(Expr::Value(Value(MyInt(2)))))
        );
        let x = parse_expr(quote! { 1 / (2 * 3) });
        assert_eq!(
            x,
            Over(
                Value(MyInt(1)),
                Box::new(Expr::Value(ParenExpr(Box::new(Times(
                    Value(MyInt(2)),
                    Box::new(Expr::Value(Value(MyInt(3)))),
                )))))
            )
        );
    }

    #[test]
    fn parse_expr_with_multiple_factors() {
        use super::Expr::Times;
        use super::Factor::*;
        let x = parse_expr(quote! { 1 * 2 * 3 });
        assert_eq!(
            x,
            Times(
                Value(MyInt(1)),
                Box::new(Times(
                    Value(MyInt(2)),
                    Box::new(Expr::Value(Value(MyInt(3))))
                ))
            )
        );
    }
}