use super::*;
struct FieldMeta {
name: syn::Ident,
node_name: syn::TypePath,
value: syn::TypePath,
}
pub(super) fn map_valid_field_type(
f: &crate::ast::parse::Field,
) -> Result<(TokenStream, Option<syn::Ident>), crate::error::Error> {
let field_name_str = f.name.to_upper_camel_case();
let field_name: syn::Ident = match syn::parse_str(&field_name_str) {
| Ok(ident) => ident,
| Err(_) => {
return Err(crate::error::Error::ReservedKeywordFieldName(
proc_macro2::Span::call_site(),
f.name.clone(),
field_name_str,
));
},
};
if f.is_field {
return Ok((quote!(), None));
}
match &f.ty {
| FieldType::MultipleNode(items) => {
let (field_meta, field_impls): (Vec<FieldMeta>, Vec<TokenStream>) = items
.iter()
.map(|opt| {
let field_option_value: syn::TypePath =
syn::parse_str(&opt.clone()).unwrap();
let name = opt
.replace("crate::", "")
.replace("::", "_")
.to_snake_case()
.to_upper_camel_case();
let field_option_name: syn::Ident = syn::parse_str(&name).unwrap();
let node_name: syn::TypePath =
syn::parse_str(&format!("crate::Node::{name}")).unwrap();
(
FieldMeta {
name: field_option_name.clone(),
node_name: node_name.clone(),
value: field_option_value.clone(),
},
quote!(
impl<'ast> From<&'ast #field_option_value> for #field_name<'ast> {
fn from(v: &'ast #field_option_value) -> Self {
Self::#field_option_name(v)
}
}
),
)
})
.unzip();
let field_options = field_meta
.iter()
.map(|f| {
let field_option_name = &f.name;
let field_option_value = &f.value;
quote!(#field_option_name(&'ast #field_option_value))
})
.collect::<Vec<TokenStream>>();
let field_option_impls = field_meta
.iter()
.map(|f| {
let field_option_name = &f.name;
let field_node_name = &f.node_name;
quote!( #field_node_name(val) => { #field_name::#field_option_name(val) })
})
.collect::<Vec<TokenStream>>();
Ok((
quote! {
#[derive(Debug)]
pub enum #field_name<'ast> {
#(#field_options),*,
Error
}
impl<'ast> #field_name<'ast> {
pub fn validate(
&self,
ast: &'ast crate::AST,
) -> crate::Result<()> {
match self {
Self::Error => Err(crate::invalid_node_error()),
_ => Ok(()),
}
}
pub fn from_node(node: &'ast crate::Node) -> Option<#field_name<'ast>> {
Some(Self::from(node))
}
}
impl<'ast> From<&'ast crate::Node> for #field_name<'ast> {
fn from(n: &'ast crate::Node) -> Self {
match n {
#(#field_option_impls),*,
_ => #field_name::Error,
}
}
}
#(#field_impls)*
},
Some(field_name),
))
},
| _ => {
Ok((quote!(), None))
},
}
}
pub(super) fn map_impl_walk(
f: &crate::ast::parse::Field,
maybe_field_ty: Option<TokenStream>,
) -> (TokenStream, syn::Ident) {
let field_name: syn::Ident = syn::parse_str(&f.name).unwrap();
let field_method_name: syn::Ident =
syn::parse_str(&format!("get_{}", f.name)).unwrap();
let field_doc_comment: syn::LitStr = syn::LitStr::new(
&format!("Returns the `{field_name}` field"),
proc_macro2::Span::call_site(),
);
let return_ty = {
if f.is_field {
let ret_ty = match &f.ty {
| FieldType::Path(s) => {
let field_ty: syn::TypePath = syn::parse_str(s).unwrap();
quote!(#field_ty)
},
| FieldType::Enum(s) => {
let ty: syn::Path = syn::parse_str(s).unwrap();
quote!(&#ty)
},
| FieldType::Literal(s) => {
let ty: syn::Path = syn::parse_str(s).unwrap();
quote!(#ty)
},
| FieldType::String => quote!(std::string::String),
| FieldType::Span => quote!(laburnum::Span),
| FieldType::Ident => quote!(laburnum::Ident),
| FieldType::SpannedIdent => quote!(laburnum::Spanned<laburnum::Ident>),
| _ => quote!(&'ast crate::Node),
};
match f.is_optional {
| true => {
quote! {
Option<#ret_ty>
}
},
| false => ret_ty,
}
} else {
match &f.ty {
| FieldType::Literal(s) => {
let ty: syn::Path = syn::parse_str(s).unwrap();
quote!(#ty)
},
| FieldType::String => quote!(std::string::String),
| FieldType::Span => quote!(laburnum::Span),
| FieldType::Ident => quote!(laburnum::Ident),
| FieldType::SpannedIdent => quote!(laburnum::Spanned<laburnum::Ident>),
| FieldType::Enum(s) => {
let ty: syn::Path = syn::parse_str(s).unwrap();
quote!(&#ty)
},
| _ => {
let ret = match &f.ty {
| FieldType::Path(s) => {
let field_ty: syn::TypePath = syn::parse_str(s).unwrap();
quote!(#field_ty)
},
| FieldType::SingleNode(s) => {
let field_ty: syn::TypePath = syn::parse_str(s).unwrap();
quote!(&'ast #field_ty)
},
| FieldType::EnumNodeId(s) => {
let field_ty: syn::TypePath = syn::parse_str(s).unwrap();
quote!(#field_ty<'ast>)
},
| FieldType::MultipleNode(_items) => maybe_field_ty
.clone()
.map(|ty| quote!(#ty<'ast>))
.unwrap_or_else(|| quote!(&'ast crate::Node)),
| FieldType::Literal(s) => {
let ty: syn::Path = syn::parse_str(s).unwrap();
quote!(#ty)
},
| _ => quote!(()),
};
if f.is_vec {
quote! {Vec<#ret>}
} else {
quote! {Option<#ret>}
}
},
}
}
};
let body = {
let field = if f.is_field {
let body = match &f.ty {
| FieldType::Path(_) => {
quote! {
#field_name
}
},
| FieldType::Literal(_s) => {
quote!(*#field_name)
},
| FieldType::String => {
quote!(ast.intern.resolve(#field_name).to_string())
},
| FieldType::Span => {
quote!(*#field_name)
},
| FieldType::Ident => {
quote!(*#field_name)
},
| FieldType::SpannedIdent => {
quote!(*#field_name)
},
| FieldType::Enum(_) => {
quote!(#field_name)
},
| _ => quote!(None),
};
match f.is_optional {
| true => {
quote! {
Some(#body)
}
},
| false => body,
}
} else {
match &f.ty {
| FieldType::Path(_) => {
quote! {
ast.node(*#field_name)
}
},
| FieldType::SingleNode(s) => {
let field_ty: syn::TypePath = syn::parse_str(s).unwrap();
if f.is_vec {
quote! {
#field_name
.iter()
.filter_map(|n: &crate::NodeId| {
ast.node(*n).and_then(|node: &crate::Node| {
#field_ty::from_node(node)
})
})
.collect::<Vec<_>>()
}
} else {
quote! {
ast.node(*#field_name).and_then(|node| #field_ty::from_node(node))
}
}
},
| FieldType::EnumNodeId(s) => {
let field_ty: syn::TypePath = syn::parse_str(s).unwrap();
if f.is_vec {
quote! {
#field_name
.iter()
.filter_map(|n: &crate::NodeId| {
ast.node(*n).and_then(|node: &crate::Node| {
#field_ty::from_node(node)
})
})
.collect::<Vec<_>>()
}
} else {
quote! {
ast.node(*#field_name).and_then(|node| #field_ty::from_node(node))
}
}
},
| FieldType::MultipleNode(_) => match maybe_field_ty {
| Some(field_ty) => {
if f.is_vec {
quote! {
#field_name
.iter()
.filter_map(|n| ast.node(*n).and_then(|node| #field_ty::from_node(node)))
.collect::<Vec<_>>()
}
} else {
quote! { ast.node(*#field_name).and_then(|node| #field_ty::from_node(node)) }
}
},
| None => {
quote! { ast.node(*#field_name) }
},
},
| FieldType::Literal(_s) => {
quote!(*#field_name)
},
| FieldType::String => {
quote!(ast.intern.resolve(#field_name).to_string())
},
| FieldType::Span => {
quote!(*#field_name)
},
| FieldType::Ident => {
quote!(*#field_name)
},
| FieldType::SpannedIdent => {
quote!(*#field_name)
},
| FieldType::Enum(_) => {
quote!(#field_name)
},
| _ => quote!(None),
}
};
if f.is_optional {
quote! {
{
if let Some(ref #field_name) = self.#field_name {
#field
} else {
None
}
}
}
} else {
quote! {
{
let #field_name = &self.#field_name;
#field
}
}
}
};
let (body_ids, return_ty_ids, should_generate_ids) = if !f.is_field {
match &f.ty {
| FieldType::Path(_)
| FieldType::EnumNodeId(_)
| FieldType::SingleNode(_) => {
let body = if f.is_vec {
if f.is_optional {
quote! {
{
if let Some(ref #field_name) = self.#field_name {
#field_name.clone()
} else {
Vec::new()
}
}
}
} else {
quote! {
{
let #field_name = &self.#field_name;
#field_name.clone()
}
}
}
} else if f.is_optional {
quote! {
{
if let Some(ref #field_name) = self.#field_name {
Some(*#field_name)
} else {
None
}
}
}
} else {
quote! {
{
let #field_name = &self.#field_name;
*#field_name
}
}
};
let ret_ty = if f.is_vec {
quote!(Vec<crate::NodeId>)
} else if f.is_optional {
quote!(Option<crate::NodeId>)
} else {
quote!(crate::NodeId)
};
(Some(body), Some(ret_ty), true)
},
| _ => (None, None, false),
}
} else {
(None, None, false)
};
let extra = {
if f.ty == FieldType::String {
let field_method_name: syn::Ident =
syn::parse_str(&format!("get_{}_spur", f.name)).unwrap();
let field_doc_comment: syn::LitStr = syn::LitStr::new(
&format!("Returns the `laburnum::Span` for the `{field_name}` field"),
proc_macro2::Span::call_site(),
);
quote! {
#[doc = #field_doc_comment]
pub fn #field_method_name<'ast>(
&self,
ast: &'ast crate::AST,
) -> laburnum::Span {
self.#field_name
}
}
} else {
quote! {}
}
};
let ids_method = if should_generate_ids {
let method_suffix = if f.is_vec { "ids" } else { "id" };
let field_method_name: syn::Ident =
syn::parse_str(&format!("get_{}_{}", f.name, method_suffix)).unwrap();
let doc_text = if f.is_vec {
format!("Returns the NodeIds for the `{field_name}` field")
} else {
format!("Returns the NodeId for the `{field_name}` field")
};
let field_doc_comment_ids: syn::LitStr =
syn::LitStr::new(&doc_text, proc_macro2::Span::call_site());
match (body_ids, return_ty_ids) {
| (Some(body_ids), Some(return_ty_ids)) => {
quote! {
#[doc = #field_doc_comment_ids]
pub fn #field_method_name<'ast>(
&self,
ast: &'ast crate::AST,
) -> #return_ty_ids {
#body_ids
}
}
},
| _ => quote! {},
}
} else {
quote! {}
};
(
quote!(
#extra
#[doc = #field_doc_comment]
pub fn #field_method_name<'ast>(
&self,
ast: &'ast crate::AST,
) -> #return_ty {
#body
}
#ids_method
),
field_method_name,
)
}