c3-lang-parser 0.0.4

Implementation of C3 Lang.
Documentation
use c3_lang_linearization::Class;
use proc_macro2::{Ident, TokenStream};
use quote::{format_ident, quote, ToTokens, TokenStreamExt};
use syn::{Attribute, FnArg};

use super::c3_ast::{ClassDef, ClassFnImpl, ClassNameDef, FnDef, PackageDef, VarDef};

impl ToTokens for PackageDef {
    fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
        tokens.append_all(&self.other_code);
        tokens.extend(stack_definition());
        tokens.extend(self.class_name.to_token_stream());
        tokens.append_all(&self.classes);
    }
}

impl ToTokens for ClassNameDef {
    fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
        let classes = &self.classes;
        tokens.extend(quote! {
            #[derive(Clone)]
            enum ClassName {
                #(#classes),*
            }
        })
    }
}

impl ToTokens for ClassDef {
    fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
        let class_ident = &self.class;
        let path: Vec<Class> = self.path.clone().into_iter().rev().collect();
        let path_len = path.len();
        let variables = &self.variables;
        let functions = &self.functions;
        let struct_attrs = attributes_to_token_stream(&self.struct_attrs);
        let impl_attrs = attributes_to_token_stream(&self.impl_attrs);
        tokens.extend(quote! {
            #struct_attrs
            pub struct #class_ident {
                __stack: PathStack,
                #(#variables),*
            }

            #impl_attrs
            impl #class_ident {
                const PATH: &'static [ClassName; #path_len] = &[
                    #(ClassName::#path),*
                ];

                #(#functions)*
            }
        })
    }
}

impl ToTokens for VarDef {
    fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
        let ident = &self.ident;
        let ty = &self.ty;
        tokens.extend(quote! {
            #ident: #ty
        });
    }
}

impl ToTokens for FnDef {
    fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
        let fn_ident = &self.name;
        let fn_super_ident = format_ident!("super_{}", fn_ident.to_string());
        let args = &self.args;
        let ret = &self.ret;
        let implementations = &self.implementations;
        let args_as_params = args_to_params(args);
        let attrs = attributes_to_token_stream(&self.attrs);
        tokens.extend(quote! {
            #attrs
            pub fn #fn_ident(#(#args),*) #ret {
                self.__stack.push_path_on_stack(Self::PATH);
                let result = self.#fn_super_ident(#(#args_as_params),*);
                self.__stack.drop_one_from_stack();
                result
            }

            pub fn #fn_super_ident(#(#args),*) #ret {
                let __class = self.__stack.pop_from_top_path();
                match __class {
                    #(#implementations),*
                    #[allow(unreachable_patterns)]
                    _ => self.#fn_super_ident(#(#args_as_params),*),
                }
            }
        });
    }
}

impl ToTokens for ClassFnImpl {
    fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
        let class = &self.class;
        let implementation = &self.implementation;
        tokens.extend(quote! {
            ClassName::#class => #implementation
        });
    }
}

fn args_to_params(args: &[FnArg]) -> Vec<Ident> {
    args.iter()
        .skip(1)
        .map(|arg| {
            if let FnArg::Typed(arg) = arg {
                format_ident!("{}", arg.pat.to_token_stream().to_string())
            } else {
                panic!("Unsupported arg");
            }
        })
        .collect()
}

fn attributes_to_token_stream(attrs: &[Attribute]) -> proc_macro2::TokenStream {
    let mut result = proc_macro2::TokenStream::new();
    for attr in attrs {
        result.extend(quote! {
            #attr
        });
    }
    result
}

fn stack_definition() -> TokenStream {
    quote! {
        struct PathStack {
            stack: std::sync::Arc<std::sync::Mutex<Vec<Vec<ClassName>>>>
        }

        impl PathStack {
            pub fn new() -> Self {
                PathStack {
                    stack: std::sync::Arc::new(std::sync::Mutex::new(Vec::new()))
                }
            }

            pub fn push_path_on_stack(&self, path: &[ClassName]) {
                let mut stack = self.stack.lock().unwrap();
                stack.push(path.to_vec());
            }

            pub fn drop_one_from_stack(&self) {
                let mut stack = self.stack.lock().unwrap();
                stack.pop().unwrap();
            }

            pub fn pop_from_top_path(&self) -> ClassName {
                let mut stack = self.stack.lock().unwrap();
                let mut path = stack.pop().unwrap();
                let class = path.pop().unwrap();
                stack.push(path);
                class
            }
        }

        impl Default for PathStack {
            fn default() -> PathStack {
                PathStack::new()
            }
        }
    }
}

#[cfg(test)]
mod tests {
    use quote::{quote, ToTokens};

    use crate::{c3_ast_builder::tests::test_c3_ast, test_utils::test_code};

    use super::stack_definition;

    #[test]
    fn test_package_def_printing() {
        let input = test_c3_ast();
        let stack = stack_definition();
        let target = quote! {
            pub type Num = u32;

            #stack

            #[derive(Clone)]
            enum ClassName {
                A,
                B,
            }

            #[derive(Debug)]
            pub struct B {
                __stack: PathStack,
                x: u32,
            }

            #[cfg(target_os = "linux")]
            impl B {
                const PATH: &'static [ClassName; 2usize] = &[ClassName::A, ClassName::B];

                pub fn bar(&self, counter: Num) -> String {
                    self.__stack.push_path_on_stack(Self::PATH);
                    let result = self.super_bar(counter);
                    self.__stack.drop_one_from_stack();
                    result
                }
                pub fn super_bar(&self, counter: Num) -> String {
                    let __class = self.__stack.pop_from_top_path();
                    match __class {
                        ClassName::A => {
                            let label = format!("A::bar({})", counter);
                            if counter == 0 {
                                label
                            } else {
                                format!("{} {}", label, self.foo(counter - 1))
                            }
                        }
                        ClassName::B => {
                            let label = format!("B::bar({})", counter);
                            if counter == 0 {
                                label
                            } else {
                                format!("{} {}", label, self.super_bar(counter - 1))
                            }
                        }
                        #[allow(unreachable_patterns)]
                        _ => self.super_bar(counter),
                    }
                }

                #[test]
                pub fn foo(&self, counter: Num) -> String {
                    self.__stack.push_path_on_stack(Self::PATH);
                    let result = self.super_foo(counter);
                    self.__stack.drop_one_from_stack();
                    result
                }
                pub fn super_foo(&self, counter: Num) -> String {
                    let __class = self.__stack.pop_from_top_path();
                    match __class {
                        ClassName::A => {
                            let label = format!("A::foo({})", counter);
                            if counter == 0 {
                                label
                            } else {
                                format!("{} {}", label, self.bar(counter - 1))
                            }
                        }
                        #[allow(unreachable_patterns)]
                        _ => self.super_foo(counter),
                    }
                }
            }

        };
        test_code(input.to_token_stream(), target)
    }
}