use crate::pattern::{
ComparisonOp, Pattern, PatternClosure, PatternComparison, PatternEnum, PatternMap,
PatternRange, PatternSet, PatternSimple, PatternSlice, PatternString, PatternStruct,
PatternTuple, PatternWildcard, TupleElement,
};
#[cfg(feature = "regex")]
use crate::pattern::{PatternLike, PatternRegex};
use proc_macro2::{Ident, Span, TokenStream};
use quote::quote;
pub(super) fn expand_pattern_node_ident(node_id: usize) -> Ident {
Ident::new(&format!("__PATTERN_NODE_{}", node_id), Span::call_site())
}
pub(super) fn generate_pattern_nodes(
pattern: &Pattern,
node_defs: &mut Vec<(usize, TokenStream)>,
parent_ident: Option<&Ident>,
) -> TokenStream {
let node_id = match pattern {
Pattern::Simple(PatternSimple { node_id, .. })
| Pattern::String(PatternString { node_id, .. })
| Pattern::Struct(PatternStruct { node_id, .. })
| Pattern::Enum(PatternEnum { node_id, .. })
| Pattern::Tuple(PatternTuple { node_id, .. })
| Pattern::Slice(PatternSlice { node_id, .. })
| Pattern::Comparison(PatternComparison { node_id, .. })
| Pattern::Range(PatternRange { node_id, .. })
| Pattern::Wildcard(PatternWildcard { node_id })
| Pattern::Closure(PatternClosure { node_id, .. })
| Pattern::Map(PatternMap { node_id, .. })
| Pattern::Set(PatternSet { node_id, .. }) => *node_id,
#[cfg(feature = "regex")]
Pattern::Regex(PatternRegex { node_id, .. })
| Pattern::Like(PatternLike { node_id, .. }) => *node_id,
};
if node_id == usize::MAX {
return quote! {};
}
let node_ident = Ident::new(&format!("__PATTERN_NODE_{}", node_id), Span::call_site());
let (line_start, col_start, line_end, col_end) = pattern.location();
let parent_ref = if let Some(parent) = parent_ident {
quote! { Some(&#parent) }
} else {
quote! { None }
};
let node_def = match pattern {
Pattern::Simple(PatternSimple { expr, .. }) => {
let value_str = quote! { #expr }.to_string();
quote! {
::assert_struct::__macro_support::PatternNode {
kind: ::assert_struct::__macro_support::NodeKind::Simple {
value: #value_str,
},
parent: #parent_ref,
line_start: #line_start,
col_start: #col_start,
line_end: #line_end,
col_end: #col_end,
}
}
}
Pattern::String(PatternString { lit, .. }) => {
let value_str = format!("\"{}\"", lit.value());
quote! {
::assert_struct::__macro_support::PatternNode {
kind: ::assert_struct::__macro_support::NodeKind::Simple {
value: #value_str,
},
parent: #parent_ref,
line_start: #line_start,
col_start: #col_start,
line_end: #line_end,
col_end: #col_end,
}
}
}
Pattern::Comparison(PatternComparison { op, expr, .. }) => {
let op_variant = match op {
ComparisonOp::Less(_) => {
quote!(::assert_struct::__macro_support::ComparisonOp::Less)
}
ComparisonOp::LessEqual(_) => {
quote!(::assert_struct::__macro_support::ComparisonOp::LessEqual)
}
ComparisonOp::Greater(_) => {
quote!(::assert_struct::__macro_support::ComparisonOp::Greater)
}
ComparisonOp::GreaterEqual(_) => {
quote!(::assert_struct::__macro_support::ComparisonOp::GreaterEqual)
}
ComparisonOp::Equal(_) => {
quote!(::assert_struct::__macro_support::ComparisonOp::Equal)
}
ComparisonOp::NotEqual(_) => {
quote!(::assert_struct::__macro_support::ComparisonOp::NotEqual)
}
};
let value_str = quote! { #expr }.to_string();
quote! {
::assert_struct::__macro_support::PatternNode {
kind: ::assert_struct::__macro_support::NodeKind::Comparison {
op: #op_variant,
value: #value_str,
},
parent: #parent_ref,
line_start: #line_start,
col_start: #col_start,
line_end: #line_end,
col_end: #col_end,
}
}
}
Pattern::Range(PatternRange { expr, .. }) => {
let pattern_str = quote! { #expr }.to_string();
quote! {
::assert_struct::__macro_support::PatternNode {
kind: ::assert_struct::__macro_support::NodeKind::Range {
pattern: #pattern_str,
},
parent: #parent_ref,
line_start: #line_start,
col_start: #col_start,
line_end: #line_end,
col_end: #col_end,
}
}
}
#[cfg(feature = "regex")]
Pattern::Regex(PatternRegex { pattern, .. }) => {
let pattern_str = format!("r\"{}\"", pattern);
quote! {
::assert_struct::__macro_support::PatternNode {
kind: ::assert_struct::__macro_support::NodeKind::Regex {
pattern: #pattern_str,
},
parent: #parent_ref,
line_start: #line_start,
col_start: #col_start,
line_end: #line_end,
col_end: #col_end,
}
}
}
#[cfg(feature = "regex")]
Pattern::Like(PatternLike { expr, .. }) => {
let expr_str = quote! { #expr }.to_string();
quote! {
::assert_struct::__macro_support::PatternNode {
kind: ::assert_struct::__macro_support::NodeKind::Like {
expr: #expr_str,
},
parent: #parent_ref,
line_start: #line_start,
col_start: #col_start,
line_end: #line_end,
col_end: #col_end,
}
}
}
Pattern::Wildcard(PatternWildcard { .. }) => {
quote! {
::assert_struct::__macro_support::PatternNode {
kind: ::assert_struct::__macro_support::NodeKind::Wildcard,
parent: #parent_ref,
line_start: #line_start,
col_start: #col_start,
line_end: #line_end,
col_end: #col_end,
}
}
}
Pattern::Closure(PatternClosure { closure, .. }) => {
let closure_str = quote! { #closure }.to_string();
quote! {
::assert_struct::__macro_support::PatternNode {
kind: ::assert_struct::__macro_support::NodeKind::Closure {
closure: #closure_str,
},
parent: #parent_ref,
line_start: #line_start,
col_start: #col_start,
line_end: #line_end,
col_end: #col_end,
}
}
}
Pattern::Enum(PatternEnum { path, elements, .. }) => {
let child_refs: Vec<TokenStream> = elements
.iter()
.map(|elem| {
let pattern = match elem {
TupleElement::Positional(pattern) => pattern,
TupleElement::Indexed(boxed_elem) => &boxed_elem.pattern,
};
generate_pattern_nodes(pattern, node_defs, Some(&node_ident))
})
.collect();
let path_str = quote!(#path).to_string().replace(" :: ", "::");
let args = if elements.is_empty() {
quote!(None)
} else {
quote!(Some(&[#(&#child_refs),*]))
};
quote! {
::assert_struct::__macro_support::PatternNode {
kind: ::assert_struct::__macro_support::NodeKind::EnumVariant {
path: #path_str,
args: #args,
},
parent: #parent_ref,
line_start: #line_start,
col_start: #col_start,
line_end: #line_end,
col_end: #col_end,
}
}
}
Pattern::Tuple(PatternTuple { elements, .. }) => {
let child_refs: Vec<TokenStream> = elements
.iter()
.map(|elem| {
let pattern = match elem {
TupleElement::Positional(pattern) => pattern,
TupleElement::Indexed(boxed_elem) => &boxed_elem.pattern,
};
generate_pattern_nodes(pattern, node_defs, Some(&node_ident))
})
.collect();
quote! {
::assert_struct::__macro_support::PatternNode {
kind: ::assert_struct::__macro_support::NodeKind::Tuple {
items: &[#(&#child_refs),*],
},
parent: #parent_ref,
line_start: #line_start,
col_start: #col_start,
line_end: #line_end,
col_end: #col_end,
}
}
}
Pattern::Slice(PatternSlice { elements, .. }) => {
let rest = elements
.iter()
.any(|e| matches!(e, Pattern::Range(PatternRange { node_id, .. }) if *node_id == usize::MAX));
let child_refs: Vec<TokenStream> = elements
.iter()
.filter(|e| !matches!(e, Pattern::Range(PatternRange { node_id, .. }) if *node_id == usize::MAX))
.map(|elem| generate_pattern_nodes(elem, node_defs, Some(&node_ident)))
.collect();
quote! {
::assert_struct::__macro_support::PatternNode {
kind: ::assert_struct::__macro_support::NodeKind::Slice {
items: &[#(&#child_refs),*],
rest: #rest,
},
parent: #parent_ref,
line_start: #line_start,
col_start: #col_start,
line_end: #line_end,
col_end: #col_end,
}
}
}
Pattern::Struct(PatternStruct {
path, fields, rest, ..
}) => {
let name_str = if let Some(p) = path {
quote! { #p }.to_string().replace(" :: ", "::")
} else {
"_".to_string() };
let field_entries: Vec<TokenStream> = fields
.iter()
.map(|field| {
let field_name = field.operations.root_field_name().to_string();
let child_ref =
generate_pattern_nodes(&field.pattern, node_defs, Some(&node_ident));
quote! {
(#field_name, &#child_ref)
}
})
.collect();
quote! {
::assert_struct::__macro_support::PatternNode {
kind: ::assert_struct::__macro_support::NodeKind::Struct {
name: #name_str,
fields: &[#(#field_entries),*],
rest: #rest,
},
parent: #parent_ref,
line_start: #line_start,
col_start: #col_start,
line_end: #line_end,
col_end: #col_end,
}
}
}
Pattern::Set(PatternSet { elements, rest, .. }) => {
let child_refs: Vec<TokenStream> = elements
.iter()
.map(|elem| generate_pattern_nodes(elem, node_defs, Some(&node_ident)))
.collect();
quote! {
::assert_struct::__macro_support::PatternNode {
kind: ::assert_struct::__macro_support::NodeKind::Set {
items: &[#(&#child_refs),*],
rest: #rest,
},
parent: #parent_ref,
line_start: #line_start,
col_start: #col_start,
line_end: #line_end,
col_end: #col_end,
}
}
}
Pattern::Map(PatternMap { entries, rest, .. }) => {
let entry_refs: Vec<TokenStream> = entries
.iter()
.map(|(key, value)| {
let key_str = quote! { #key }.to_string();
let value_ref = generate_pattern_nodes(value, node_defs, Some(&node_ident));
quote! {
(#key_str, &#value_ref)
}
})
.collect();
quote! {
::assert_struct::__macro_support::PatternNode {
kind: ::assert_struct::__macro_support::NodeKind::Map {
entries: &[#(#entry_refs),*],
rest: #rest,
},
parent: #parent_ref,
line_start: #line_start,
col_start: #col_start,
line_end: #line_end,
col_end: #col_end,
}
}
}
};
node_defs.push((node_id, node_def));
quote! { #node_ident }
}