c3-lang-parser 0.0.4

Implementation of C3 Lang.
Documentation
use c3_lang_linearization::{Class, Fn, Var};
use quote::format_ident;
use syn::{
    parse::Parse, punctuated::Punctuated, Attribute, Expr, Field, Fields, ImplItem, ImplItemConst,
    ImplItemMethod, ItemImpl, ItemStruct, Token, Visibility,
};

#[derive(Debug, PartialEq)]
pub struct RustClassDef {
    pub item_struct: ItemStruct,
    pub item_impl: Option<ItemImpl>,
}

impl Parse for RustClassDef {
    fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
        let item_struct: ItemStruct = input.parse()?;
        let lookahead = input.lookahead1();
        let item_impl = if lookahead.peek(Token![impl]) || input.peek(Token![#]) {
            let item_impl: ItemImpl = input.parse()?;
            Some(item_impl)
        } else {
            None
        };
        // println!("asd");
        Ok(RustClassDef {
            item_struct,
            item_impl,
        })
    }
}

impl RustClassDef {
    pub fn class(&self) -> Class {
        Class::from(self.item_struct.ident.to_string())
    }

    pub fn is_public(&self) -> bool {
        matches!(self.item_struct.vis, Visibility::Public(_))
    }

    pub fn struct_attrs(&self) -> Vec<Attribute> {
        self.item_struct.attrs.clone()
    }

    pub fn impl_attrs(&self) -> Vec<Attribute> {
        match &self.item_impl {
            None => Vec::new(),
            Some(item_impl) => item_impl.attrs.clone(),
        }
    }

    pub fn parents(&self) -> Vec<Class> {
        if self.item_impl.is_none() {
            return vec![];
        }

        let item_impl = self.item_impl.clone().unwrap();
        let items: Vec<ImplItem> = item_impl.items;
        let mut parents: Vec<Class> = vec![];
        for item in items.iter() {
            if let ImplItem::Const(item_const) = item {
                let item_const: &ImplItemConst = item_const;
                if item_const.ident == format_ident!("PARENTS") {
                    let expr = &item_const.expr;
                    if let Expr::Reference(expr_reference) = expr {
                        let expr = *expr_reference.expr.clone();
                        if let Expr::Array(expr_list) = expr {
                            let exprs: Punctuated<Expr, Token![,]> = expr_list.elems;
                            for expr in exprs.iter() {
                                if let Expr::Path(expr) = expr {
                                    let path = expr.path.clone();
                                    let segments = path.segments;
                                    if segments[0].ident == format_ident!("ClassName") {
                                        parents.push(Class::from(segments[1].ident.to_string()));
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }
        parents
    }

    pub fn functions(&self) -> Vec<Fn> {
        self.function_impls().into_iter().map(|x| x.0).collect()
    }

    pub fn function_impls(&self) -> Vec<(Fn, ImplItemMethod)> {
        let item_impl = self.item_impl.clone().unwrap();
        let items: Vec<ImplItem> = item_impl.items;
        let mut functions: Vec<(Fn, ImplItemMethod)> = vec![];
        for item in items.iter() {
            if let ImplItem::Method(method) = item {
                let name = method.sig.ident.to_string();
                functions.push((Fn::from(name), method.clone()));
            }
        }
        functions
    }

    pub fn variables(&self) -> Vec<Var> {
        self.variables_impl().into_iter().map(|x| x.0).collect()
    }

    pub fn variables_impl(&self) -> Vec<(Var, Field)> {
        let mut variables: Vec<(Var, Field)> = vec![];
        if let Fields::Named(fields) = &self.item_struct.fields {
            for field in &fields.named {
                let var = Var::from(field.ident.clone().unwrap().to_string());
                variables.push((var, field.clone()));
            }
        };
        variables
    }
}

#[cfg(test)]
mod tests {
    use c3_lang_linearization::Class;
    use quote::quote;
    use syn::parse_quote;

    use super::RustClassDef;

    #[test]
    fn test_rust_class_def_without_impl() {
        let input = quote! {
            struct A {}
        };
        let result: RustClassDef = syn::parse2(input).unwrap();
        let target = RustClassDef {
            item_struct: parse_quote!(
                struct A {}
            ),
            item_impl: None,
        };
        assert_eq!(result, target);
    }

    #[test]
    fn test_rust_class_def_with_impl() {
        let input = quote! {
            struct A {}
            impl A for B {}
        };
        let result: RustClassDef = syn::parse2(input).unwrap();
        let target = RustClassDef {
            item_struct: parse_quote!(
                struct A {}
            ),
            item_impl: Some(parse_quote!( impl A for B {} )),
        };
        assert_eq!(result, target);
    }

    #[test]
    fn test_rust_class_def_getters() {
        let input = quote! {
            #[derive(Default)]
            pub struct A {
                x: u32
            }

            #[custom_macro]
            impl A {
                const PARENTS: &'static [ClassName; 2usize] = &[
                    ClassName::X,
                    ClassName::Y
                ];

                pub fn k(&self) -> u32 { 4 }
            }
        };
        let result: RustClassDef = syn::parse2(input).unwrap();
        assert!(result.is_public());
        assert_eq!(result.class(), Class::from("A"));
        assert_eq!(result.parents(), vec![Class::from("X"), Class::from("Y")]);
        assert_eq!(
            result.struct_attrs(),
            vec![parse_quote! { #[derive(Default)] }]
        );
        assert_eq!(result.impl_attrs(), vec![parse_quote! { #[custom_macro] }]);
    }
}