use {
super::parse::Ast,
crate::{args::Args, cst::parse::FieldType},
proc_macro2::{self, TokenStream},
quote::quote,
};
pub(crate) fn codegen(
args: Args,
ast: Ast,
) -> Result<TokenStream, crate::error::Error> {
let Ast { name, fields } = ast;
if !fields
.iter()
.any(|f| f.name == "span" && f.ty == FieldType::Span && !f.is_option)
{
return Err(crate::error::Error::MissingRequiredSpanField(
proc_macro2::Span::call_site(),
));
}
let field_data = &fields
.iter()
.map(|f| -> Result<TokenStream, crate::error::Error> {
let (name, ty) = match f.ty {
| FieldType::Unknown(ref ty) => (&f.name, ty.as_str()),
| FieldType::Literal(ty_name) => (&f.name, ty_name),
| FieldType::NodeId => (&f.name, "crate::CstNodeId"),
| FieldType::Span => (&f.name, "laburnum::Span"),
| FieldType::NodeList => (&f.name, "crate::node::NodeList"),
| FieldType::ControlSpan => {
if !f.name.ends_with("_token") {
return Err(crate::error::Error::InvalidFieldNamingConvention(
proc_macro2::Span::call_site(),
f.name.clone(),
"_token".to_string(),
));
}
(&f.name, "crate::lexer::control::ControlSpan")
},
| FieldType::KeywordSpan => {
if !f.name.ends_with("_keyword") {
return Err(crate::error::Error::InvalidFieldNamingConvention(
proc_macro2::Span::call_site(),
f.name.clone(),
"_keyword".to_string(),
));
}
(&f.name, "crate::lexer::keyword::KeywordSpan")
},
| FieldType::ScalarSpan => {
if !f.name.ends_with("_scalar") {
return Err(crate::error::Error::InvalidFieldNamingConvention(
proc_macro2::Span::call_site(),
f.name.clone(),
"_scalar".to_string(),
));
}
(&f.name, "crate::lexer::scalar::ScalarSpan")
},
| FieldType::LiteralSpan => {
if !f.name.ends_with("_literal") {
return Err(crate::error::Error::InvalidFieldNamingConvention(
proc_macro2::Span::call_site(),
f.name.clone(),
"_literal".to_string(),
));
}
(&f.name, "crate::primitive::literal::LiteralSpan")
},
| FieldType::TriviaSpan => {
if !f.name.ends_with("_trivia") {
return Err(crate::error::Error::InvalidFieldNamingConvention(
proc_macro2::Span::call_site(),
f.name.clone(),
"_trivia".to_string(),
));
}
(&f.name, "crate::errata::TriviaSpan")
},
| FieldType::StringSpan => (&f.name, "laburnum::Span"),
| FieldType::Ident => (&f.name, "laburnum::Ident"),
};
let ty = {
if f.is_option {
format!("Option<{ty}>")
} else {
ty.to_string()
}
};
let field_name: syn::Ident = syn::parse_str(name).unwrap();
let path: syn::Path = syn::parse_str(&ty).unwrap();
Ok(quote::quote!(pub #field_name: #path))
})
.collect::<Result<Vec<TokenStream>, _>>()?;
let struct_name =
proc_macro2::Ident::new(&name, proc_macro2::Span::call_site());
let ts_struct = quote! {
#[derive(Clone, PartialEq)]
pub struct #struct_name {
#(#field_data),*
}
impl #struct_name {
pub fn span(&self) -> laburnum::Span {
self.span
}
}
};
let mod_name = quote! {
{
let __mp = module_path!();
let __stripped = __mp.find("::").map(|i| &__mp[i+2..]).unwrap_or(__mp);
if let Some(__last_sep) = __stripped.rfind("::") {
let __last_seg = &__stripped[__last_sep + 2..];
let __name = stringify!(#struct_name);
let __seg_norm: std::string::String = __last_seg.chars().filter(|c| *c != '_').collect();
let __name_norm: std::string::String = __name.chars().filter(|c| *c != '_').collect();
if __seg_norm.eq_ignore_ascii_case(&__name_norm) {
&__stripped[..__last_sep]
} else {
__stripped
}
} else {
__stripped
}
}
};
let ts_impl_display = quote! {
#[allow(unused)]
impl std::fmt::Display for #struct_name {
fn fmt(&self,f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct(&format!("{}::{}", #mod_name, stringify!(#struct_name)))
.field("span", &self.span)
.finish()
}
}
};
let ts_impl_debug = quote! {
#[allow(unused)]
impl std::fmt::Debug for #struct_name {
fn fmt(&self,f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct(&format!("{}::{}", #mod_name, stringify!(#struct_name)))
.field("span", &self.span)
.finish()
}
}
};
let bgname = {
if args.is_error_node() {
quote! {
&format!("{}::{}", #mod_name, stringify!(#struct_name).red().bold().to_string())
}
} else {
quote! {
&format!("{}::{}", #mod_name, stringify!(#struct_name))
}
}
};
let ts_impl_bluegum = quote! {
#[allow(unused)]
impl bluegum::Bluegum for #struct_name {
fn node(&self,b: &mut bluegum::Builder){
use owo_colors::OwoColorize;
b.name(#bgname)
.debug("span",self.span);
}
}
};
let ts_impl_bluegum_with_state = {
let mut fields = fields
.iter()
.filter(|f| f.name != "span")
.map(|f| {
let field_name: syn::Ident = syn::parse_str(&f.name).unwrap();
let field = match f.ty {
| FieldType::NodeId => {
quote! {
b.add_node_with_state(state, stringify!(#field_name), #field_name);
}
},
| FieldType::NodeList => {
quote! {
b.add_nodes_with_state::<
crate::Printer<'_>,
>(state, stringify!(#field_name), #field_name.as_slice());
}
},
| FieldType::ControlSpan => {
quote! {
let mut t = bluegum::Builder::new();
t.name("ControlSpan").debug("span", #field_name.span());
let s = format!("{}", #field_name.value());
if s.width() > 80 {
t.alt(format!("{:.80}...", s).yellow());
} else {
t.alt(format!("{:.80}", s).yellow());
};
b.add_named_builder(stringify!(#field_name), t);
}
},
| FieldType::ScalarSpan => {
quote! {
let mut t = bluegum::Builder::new();
t.name("ScalarSpan").debug("span", #field_name.span());
let s = format!("{}", #field_name.value());
if s.width() > 80 {
t.alt(format!("{:.80}...", s).bright_green());
} else {
t.alt(format!("{:.80}", s).bright_green());
};
b.add_named_builder(stringify!(#field_name), t);
}
},
| FieldType::LiteralSpan => {
quote! {
b.child(stringify!(#field_name), #field_name) ;
}
},
| FieldType::KeywordSpan => {
quote! {
let mut t = bluegum::Builder::new();
t.name("KeywordSpan").debug("span", #field_name.span());
let s = format!("{}", #field_name.value());
if s.width() > 80 {
t.alt(format!("{:.80}...", s).blue().italic());
} else {
t.alt(format!("{:.80}", s).blue().italic());
};
b.add_named_builder(stringify!(#field_name), t);
}
},
| FieldType::StringSpan => {
quote! {
use {owo_colors::OwoColorize, unicode_width::UnicodeWidthStr};
let mut t = bluegum::Builder::new();
t.name("StringSpan");
let val = state.span_text(#field_name);
if val.width() > 80 {
t.alt(format!("{:.80}...", val).bright_white());
} else {
t.alt(format!("{:.80}", val).bright_white());
};
b.add_named_builder(stringify!(#field_name), t);
}
},
| FieldType::Ident => {
quote! {
use {owo_colors::OwoColorize, unicode_width::UnicodeWidthStr};
let mut t = bluegum::Builder::new();
t.name("Ident");
let val = state.span_text(&self.span);
if val.width() > 80 {
t.alt(format!("{:.80}...", val).bright_white());
} else {
t.alt(format!("{:.80}", val).bright_white());
};
b.add_named_builder(stringify!(#field_name), t);
}
},
| FieldType::Literal(_) => {
quote! {
let mut t = bluegum::Builder::new();
t.name("Literal");
let s = format!("{}", #field_name);
if s.width() > 80 {
t.alt(format!("{:.80}...", s).bright_green());
} else {
t.alt(format!("{:.80}", s).bright_green());
};
b.add_named_builder(stringify!(#field_name), t);
}
},
| FieldType::Unknown(_) => {
quote! {
let mut t = bluegum::Builder::new();
t.name("Literal");
let s = format!("{}", #field_name);
if s.width() > 80 {
t.alt(format!("{:.80}...", s).bright_green());
} else {
t.alt(format!("{:.80}", s).bright_green());
};
b.add_named_builder(stringify!(#field_name), t);
}
},
| _ => {
quote! {}
},
};
if f.is_option {
quote! {
if let Some(ref #field_name) = self.#field_name {
#field
}
}
} else {
quote! {
let #field_name = &self.#field_name;
#field
}
}
})
.collect::<Vec<TokenStream>>();
if args.is_error_node() {
fields.push(quote! {
b.alt(format!("ERROR").red());
})
}
quote! {
#[allow(unused)]
impl bluegum::BluegumWithState<crate::Printer<'_>>for #struct_name {
fn node_with_state(
&self,
b: &mut bluegum::Builder,
state: &crate::Printer<'_>,
){
use owo_colors::OwoColorize;
use unicode_width::UnicodeWidthStr;
b.name(#bgname)
.debug("span", self.span);
{ #(#fields)* };
}
}
}
};
let ts = quote! {
#ts_struct
#ts_impl_display
#ts_impl_debug
#ts_impl_bluegum
#ts_impl_bluegum_with_state
};
Ok(ts)
}