use crate::deserialize::util::{make_build_fn_name, make_build_pair}; use crate::spec::struct_spec::{ MemberChildBuild, NodeMemberBuild, StructChild, StructSpec, VecChild, VecChildBuild, }; use crate::spec::{SpecialChild, SpecialChildKind}; use proc_macro2::{Ident, TokenStream}; use quote::{format_ident, quote}; fn make_preamble(spec: &StructSpec, pair_ident: &Ident) -> TokenStream { if spec.children().any(StructChild::is_special) { quote! { let as_span = #pair_ident.as_span(); } } else { quote! {} } } fn make_special_children(spec: &StructSpec) -> Vec { spec.children() .map(StructChild::unwrap_special) .filter(Option::is_some) .map(Option::unwrap) .map(|special_child| { let child_ident = format_ident!("{}", special_child.name()); match special_child.kind() { SpecialChildKind::FileId => { quote! { let #child_ident = file_id } } SpecialChildKind::Range => { quote! { let #child_ident = Range { start: as_span.start(), end: as_span.end() } } } } }) .collect::>() } fn make_vec_child_holder(vec_child: &VecChild) -> TokenStream { let child_ident = format_ident!("{}", vec_child.name()); match vec_child.build() { VecChildBuild::String(_) => { quote! { let mut #child_ident: Vec = vec![] } } VecChildBuild::Node(node_build) => { let child_type_ident = format_ident!("{}", node_build.kind()); quote! { let mut #child_ident: Vec> = vec![] } } } } fn make_node_child_holder(name: &str, node_child: &NodeMemberBuild) -> TokenStream { let child_ident = format_ident!("{}", name); let child_type_ident = format_ident!("{}", node_child.kind()); quote! { let mut #child_ident: Option> = None } } fn make_boolean_child_holder(name: &str) -> TokenStream { let child_ident = format_ident!("{}", name); quote! { let mut #child_ident: bool = false } } fn make_child_holder(child_spec: &StructChild) -> Option { match child_spec { StructChild::SkipChild(_) => None, StructChild::VecChild(vec_child) => Some(make_vec_child_holder(vec_child)), StructChild::MemberChild(member_child) => match member_child.build() { MemberChildBuild::Node(node_child) => { Some(make_node_child_holder(member_child.name(), node_child)) } MemberChildBuild::Boolean(_) => Some(make_boolean_child_holder(member_child.name())), }, StructChild::Special(_) => None, } } fn make_vec_child_match_action(vec_child: &VecChild) -> TokenStream { let child_name_ident = format_ident!("{}", vec_child.name()); match vec_child.build() { VecChildBuild::String(string_build) => { let build_fn_ident = format_ident!("{}", string_build.with()); quote! { #child_name_ident.push(#build_fn_ident(file_id, inner_pair)) } } VecChildBuild::Node(node_build) => { let build_fn_ident = format_ident!("{}", node_build.with()); quote! { #child_name_ident.push(Box::new(#build_fn_ident(file_id, inner_pair))) } } } } fn make_node_member_child_match_action(name: &str, node_child: &NodeMemberBuild) -> TokenStream { let child_name_ident = format_ident!("{}", name); let build_fn_ident = format_ident!("{}", node_child.with()); quote! { #child_name_ident = Some(Box::new(#build_fn_ident(file_id, inner_pair))) } } fn make_boolean_member_child_match_action(name: &str) -> TokenStream { let child_name_ident = format_ident!("{}", name); quote! { #child_name_ident = true } } fn make_rule_matcher(child_spec: &StructChild) -> Option { match child_spec { StructChild::SkipChild(skip_child) => { let rule_ident = format_ident!("{}", skip_child.rule()); Some(quote! { Rule::#rule_ident => {} }) } StructChild::VecChild(vec_child) => { let rule_ident = format_ident!("{}", vec_child.rule()); let action = make_vec_child_match_action(vec_child); Some(quote! { Rule::#rule_ident => { #action } }) } StructChild::MemberChild(member_child) => match member_child.build() { MemberChildBuild::Node(node_member_build) => { let rule_ident = format_ident!("{}", member_child.rule()); let action = make_node_member_child_match_action(member_child.name(), node_member_build); Some(quote! { Rule::#rule_ident => { #action } }) } MemberChildBuild::Boolean(_) => { let rule_ident = format_ident!("{}", member_child.rule()); let action = make_boolean_member_child_match_action(member_child.name()); Some(quote! { Rule::#rule_ident => { #action } }) } }, StructChild::Special(_) => None, } } fn make_vec_child_arg(vec_child: &VecChild) -> TokenStream { let child_ident = format_ident!("{}", vec_child.name()); quote! { #child_ident } } fn make_node_member_child_arg( name: &str, optional: bool, node_child: &NodeMemberBuild, ) -> TokenStream { let child_ident = format_ident!("{}", name); if optional { quote! { #child_ident } } else if let Some(or_else) = node_child.or_else() { let child_type_ident = format_ident!("{}", node_child.kind()); let or_else_ident = format_ident!("{}", or_else); quote! { #child_ident.unwrap_or_else(|| Box::new(#child_type_ident::#or_else_ident())) } } else { quote! { #child_ident.unwrap() } } } fn make_boolean_member_child_arg(name: &str) -> TokenStream { let child_ident = format_ident!("{}", name); quote! { #child_ident } } fn make_special_child_arg(special_child: &SpecialChild) -> TokenStream { let child_ident = format_ident!("{}", special_child.name()); quote! { #child_ident } } fn make_child_arg(child_spec: &StructChild, pair_ident: &Ident) -> Option { match child_spec { StructChild::SkipChild(_) => None, StructChild::VecChild(vec_child) => Some(make_vec_child_arg(vec_child)), StructChild::MemberChild(member_child) => match member_child.build() { MemberChildBuild::Node(node_member_build) => Some(make_node_member_child_arg( member_child.name(), member_child.optional(), node_member_build, )), MemberChildBuild::Boolean(_) => { Some(make_boolean_member_child_arg(member_child.name())) } }, StructChild::Special(special_child) => Some(make_special_child_arg(special_child)), } } fn make_return_value_stream(build_spec: &StructSpec, pair_ident: &Ident) -> TokenStream { let type_ident = format_ident!("{}", build_spec.build()); let child_args = build_spec .children() .map(|child| make_child_arg(child, pair_ident)) .filter(|child_arg| child_arg.is_some()) .map(|child_arg| child_arg.unwrap()) .collect::>(); quote! { #type_ident::new( #(#child_args,)* ) } } pub fn make_struct_build_fn(build_spec: &StructSpec) -> TokenStream { let build_fn_ident = format_ident!("{}", make_build_fn_name(build_spec.build())); let pair_ident = format_ident!("{}", make_build_pair(build_spec.build())); let return_type_ident = format_ident!("{}", build_spec.build()); let preamble = make_preamble(build_spec, &pair_ident); let special_children = make_special_children(build_spec); let child_holders = build_spec .children() .map(|child_spec| make_child_holder(child_spec)) .filter(|child_holder| child_holder.is_some()) .map(|child_holder| child_holder.unwrap()) .collect::>(); let rule_matchers = build_spec .children() .map(|child_spec| make_rule_matcher(child_spec)) .collect::>(); let iter_stream = quote! { for inner_pair in #pair_ident.into_inner() { match inner_pair.as_rule() { #(#rule_matchers)* _ => panic!("Unexpected rule: {:?}", inner_pair.as_rule()) } } }; let new_stream = make_return_value_stream(build_spec, &pair_ident); quote! { fn #build_fn_ident(file_id: usize, #pair_ident: Pair) -> #return_type_ident { #preamble #(#special_children;)* #(#child_holders;)* #iter_stream #new_stream } } }