parsable-macro 0.1.1

Macro to generate the `parsable` trait on enums or structs.
Documentation
use syn::{*, parse::{Parse, ParseStream}};
use quote::quote;
use crate::{field_attributes::FieldAttributes, output::Output, root_attributes::RootAttributes, utils::{is_type}};

struct Wrapper {
    field: Field
}

impl Parse for Wrapper {
    fn parse(input: ParseStream) -> Result<Self> {
        let field = Field::parse_named(input)?;

        Ok(Self { field })
    }
}

pub fn create_location_field(field_name: &str) -> Field {
    let string = format!("pub {}: parsable::ItemLocation", field_name);
    let result : Result<Wrapper> = syn::parse_str(&string);

    result.unwrap().field
}

pub fn process_struct(data_struct: &mut DataStruct, root_attributes: &mut RootAttributes, output: &mut Output) {
    output.get_location = quote! {
        fn location(&self) -> &parsable::ItemLocation {
            &self.location
        }
    };

    let (root_markers_on_start, root_markers_on_exit, root_markers_on_fail) = root_attributes.get_push_pop_markers();
    let mut markers_on_fail = vec![root_markers_on_fail];

    match &mut data_struct.fields {
        Fields::Named(named_fields) => {
            let field_count = named_fields.named.len();
            let mut field_names = vec![];
            let mut lines = vec![];

            for (i, field) in named_fields.named.iter_mut().enumerate() {
                let attributes = FieldAttributes::from_field_attributes(&mut field.attrs);
                let (field_markers_on_start, field_markers_on_exit, field_markers_on_fail) = attributes.get_push_pop_markers(i);
                let is_vec = is_type(&field.ty, "Vec");
                let is_option = is_type(&field.ty, "Option");

                let field_name = field.ident.as_ref().unwrap();
                let field_type = &field.ty;

                field_names.push(quote! { #field_name });
                markers_on_fail.insert(0, field_markers_on_fail);

                let optional = is_option || attributes.optional.unwrap_or(false);
                let participate_in_cascade = root_attributes.cascade && attributes.cascade.unwrap_or(true);
                let consume_spaces = match attributes.consume_spaces {
                    Some(false) => quote! {},
                    _ => quote! { reader__.eat_spaces(); }
                };
                let mut handle_failure = quote! {};
                let mut on_fail = quote ! {
                    reader__.set_index(start_index__);
                    #(#markers_on_fail)*
                    #root_markers_on_exit
                    return None;
                };

                if optional {
                    let set_option_failed = match participate_in_cascade {
                        true => quote! { option_failed__ = true },
                        false => quote! {},
                    };

                    on_fail = quote! {
                        field_failed__ = true;
                        #set_option_failed;
                        reader__.set_index(field_index__);
                        <#field_type as Default>::default()
                    };

                    if attributes.suffix.is_some() {
                        handle_failure = quote! {
                            if field_failed__ {
                                #field_name = <#field_type as Default>::default();
                            }
                        }
                    }
                }

                let mut check = vec![];
                let has_prefix = attributes.prefix.is_some();
                let has_suffix = attributes.suffix.is_some();

                let mut pre_parsing_check = quote! {};

                if optional && participate_in_cascade {
                    pre_parsing_check = quote! {
                        if option_failed__ {
                            field_failed__ = true;
                        }
                    };
                }

                let prefix_parsing = match attributes.prefix {
                    Some(prefix) => {
                        let prefix_consume_spaces = match attributes.consume_spaces_after_prefix {
                            Some(false) => quote! { {} },
                            _ => quote! { reader__.eat_spaces() },
                        };

                        quote! {
                            if !field_failed__ {
                                match reader__.read_string(#prefix) {
                                    Some(_) => #prefix_consume_spaces,
                                    None => {
                                        reader__.set_expected_string(#prefix);
                                        prefix_ok__ = false;
                                        field_failed__ = true;
                                        #on_fail;
                                    }
                                };
                            }
                        }
                    },
                    None => quote! {}
                };
                let suffix_parsing = match attributes.suffix {
                    Some(suffix) => {
                        let suffix_consume_spaces = match attributes.consume_spaces_after_suffix {
                            Some(false) => quote! { {} },
                            _ => quote! { reader__.eat_spaces() },
                        };

                        quote! {
                            if !field_failed__ {
                                match reader__.read_string(#suffix) {
                                    Some(_) => #suffix_consume_spaces,
                                    None => {
                                        reader__.set_expected_string(#suffix);
                                        #on_fail;
                                    }
                                };
                            }
                        }
                    },
                    None => quote! {}
                };

                let mut exclude_parsing = quote! {};

                if let Some(exclude) = &attributes.exclude {
                    exclude_parsing = quote! {
                        if !field_failed__ && reader__.peek_regex(#exclude) {
                            #on_fail;
                        }
                    };
                }

                let mut followed_by_parsing = quote! {};

                if let Some(followed_by) = &attributes.followed_by {
                    followed_by_parsing = quote! {
                        if !field_failed__ && !reader__.peek_regex(#followed_by) {
                            reader__.set_expected_regex(#followed_by);
                            #on_fail;
                        }
                    };
                } else if let Some(not_followed_by) = &attributes.not_followed_by {
                    followed_by_parsing = quote! {
                        if !field_failed__ && reader__.peek_regex(#not_followed_by) {
                            // reader__.set_expected_regex(#not_followed_by);
                            #on_fail;
                        }
                    };
                }

                let mut parse_method = quote! { parse_item(reader__) };

                if is_vec {
                    if let Some(separator) = attributes.separator {
                        parse_method = quote! { parse_item_with_separator(reader__, #separator) };
                    } else if let Some(false) = attributes.consume_spaces_between_items {
                        parse_method = quote! { parse_item_without_consuming_spaces(reader__) };
                    }
                }

                let mut assignment = quote! {
                    let mut #field_name = match <#field_type as parsable::Parsable>::#parse_method {
                        Some(value) => value,
                        None => {
                            reader__.set_expected_item::<#field_type>();
                            #on_fail
                        }
                    };
                };

                if (has_prefix || participate_in_cascade) && optional {
                    assignment = quote! {
                        let mut #field_name = match prefix_ok__ && !option_failed__ {
                            true => match <#field_type as parsable::Parsable>::#parse_method {
                                Some(value) => value,
                                None => {
                                    reader__.set_expected_item::<#field_type>();
                                    #on_fail
                                }
                            },
                            false => <#field_type as Default>::default()
                        };
                    };

                    // assignment = quote! {
                    //     let #field_name = <#field_type as Default>::default();
                    // };
                }

                let make_field_from_string = match is_option {
                    true => quote! { Some(value) },
                    false => quote! { value },
                };

                if let Some(pattern) = attributes.regex {
                    assignment = quote! {
                        let #field_name = match reader__.read_regex(#pattern) {
                            Some(value) => match <String as std::str::FromStr>::from_str(value) {
                                Ok(value) => #make_field_from_string,
                                Err(_) => { #on_fail }
                            },
                            None => { #on_fail }
                        };
                    };
                } else if let Some(literal) = attributes.value {
                    assignment = quote! {
                        let #field_name = match reader__.read_string(#literal) {
                            Some(value) => match <String as std::str::FromStr>::from_str(value) {
                                Ok(value) => #make_field_from_string,
                                Err(_) => { #on_fail }
                            },
                            None => { #on_fail }
                        };
                    };

                    if field_count == 1 && root_attributes.token.is_none() {
                        root_attributes.token = Some(literal.clone());
                    }
                }

                if let Some(min) = attributes.min {
                    check.push(quote! {
                        if !field_failed__ && #field_name.len() < #min {
                            reader__.set_expected_item::<#field_type>();
                            #on_fail;
                        }
                    });
                }

                if is_option && has_prefix {
                    check.push(quote! {
                        if #field_name.is_none() {
                            #on_fail;
                        }
                    });
                }

                if is_vec && has_prefix && !has_suffix {
                    check.push(quote! {
                        if #field_name.is_empty() && prefix_ok__ {
                            reader__.set_expected_item::<#field_type>();
                            #on_fail;
                        }
                    });
                }

                if attributes.ignore {
                    lines.push(quote! {
                        let #field_name = <#field_type as Default>::default();
                    });
                } else {
                    lines.push(quote! {
                        #field_markers_on_start
                        field_failed__ = false;
                        prefix_ok__ = true;
                        field_index__ = reader__.get_index();
                        #pre_parsing_check
                        #prefix_parsing
                        #exclude_parsing
                        #assignment
                        #(#check)*
                        #consume_spaces
                        #suffix_parsing
                        #followed_by_parsing
                        #handle_failure
                        #field_markers_on_exit
                    });
                }
            }

            let mut set_location = quote! {};

            if root_attributes.located {
                field_names.push(quote! { location });
                named_fields.named.insert(0, create_location_field("location"));
                set_location = quote! { let location = reader__.get_item_location(start_index__); };
            }

            output.parse_item = quote! {
                fn parse_item(reader__: &mut parsable::StringReader) -> Option<Self> {
                    let start_index__ = reader__.get_index();
                    let mut field_index__ : usize = 0;
                    let mut field_failed__ = false;
                    let mut prefix_ok__ = true;
                    let mut option_failed__ = false;
                    #root_markers_on_start
                    #(#lines)*
                    #root_markers_on_exit
                    #set_location
                    Some(Self { #(#field_names),* })
                }
            };

        },
        Fields::Unnamed(_) => unreachable!(),
        Fields::Unit => unreachable!()
    }
}