use proc_macro::TokenStream;
use quote::quote;
use syn::{
Expr, Ident, Token, braced,
parse::{Parse, ParseStream},
token::Brace,
};
trait Completable {
fn completions(&self) -> Vec<proc_macro2::TokenStream>;
}
enum RootItem {
FlowDirection(syn::Expr), Child(Child), Incomplete(syn::Expr), }
impl Parse for RootItem {
fn parse(input: ParseStream) -> syn::Result<Self> {
if input.peek(Ident) && input.peek2(Brace) {
return Ok(Self::Child(input.parse()?));
}
let expr = input.parse::<syn::Expr>()?;
if let Some(enum_name) = is_expr_enum(&expr) {
match enum_name.as_str() {
"Flow" => return Ok(Self::FlowDirection(expr)),
_ => {}
}
}
Ok(Self::Incomplete(expr))
}
}
impl Completable for RootItem {
fn completions(&self) -> Vec<proc_macro2::TokenStream> {
match self {
Self::Child(child) => child.completions(),
Self::Incomplete(expr) => vec![quote! { #expr; }],
_ => vec![],
}
}
}
enum ChildItem {
Measure(syn::Expr), Incomplete(syn::Expr), }
impl Parse for ChildItem {
fn parse(input: ParseStream) -> syn::Result<Self> {
let expr = input.parse::<syn::Expr>()?;
if let Some(enum_name) = is_expr_enum(&expr) {
match enum_name.as_str() {
"Measure" => return Ok(Self::Measure(expr)),
_ => {}
}
}
Ok(Self::Incomplete(expr))
}
}
impl Completable for ChildItem {
fn completions(&self) -> Vec<proc_macro2::TokenStream> {
match self {
Self::Incomplete(expr) => vec![quote! { #expr; }],
_ => vec![],
}
}
}
struct Child {
name: syn::Path,
items: Vec<ChildItem>,
}
impl Parse for Child {
fn parse(input: ParseStream) -> syn::Result<Self> {
let name = input.parse::<syn::Path>()?;
let content;
braced!(content in input);
let mut items = Vec::new();
while !content.is_empty() {
items.push(content.parse::<ChildItem>()?);
if content.peek(Token![,]) {
content.parse::<Token![,]>()?;
}
}
Ok(Self { name, items })
}
}
impl Completable for Child {
fn completions(&self) -> Vec<proc_macro2::TokenStream> {
let mut comps = Vec::new();
for item in &self.items {
comps.extend(item.completions());
}
comps
}
}
impl Child {
fn measure(&self) -> proc_macro2::TokenStream {
for item in &self.items {
if let ChildItem::Measure(expr) = item {
return quote! { #expr };
}
}
quote! { Measure::default() }
}
}
struct VtuiMacro {
items: Vec<RootItem>,
}
impl Parse for VtuiMacro {
fn parse(input: ParseStream) -> syn::Result<Self> {
let mut items = Vec::new();
while !input.is_empty() {
items.push(input.parse::<RootItem>()?);
if input.peek(Token![,]) {
input.parse::<Token![,]>()?;
}
}
Ok(Self { items })
}
}
impl VtuiMacro {
pub fn expand(self) -> proc_macro2::TokenStream {
let completions: Vec<_> = self
.items
.iter()
.flat_map(|item| item.completions())
.collect();
let flows = self.items.iter().filter_map(|item| {
if let RootItem::FlowDirection(expr) = item {
Some(quote! { .set_flow(#expr) })
} else {
None
}
});
let children = self.items.iter().filter_map(|item| {
if let RootItem::Child(child) = item {
let factory = &child.name;
let measure = child.measure();
Some(quote! {
.child(#measure, #factory, ())
})
} else {
None
}
});
quote! {
mod completions__ {
fn ignore() {
#(#completions)*
}
}
Node::from(c)
#(#flows)*
#(#children)*
}
}
}
pub(crate) fn transform_vtui(input: TokenStream) -> TokenStream {
match syn::parse::<VtuiMacro>(input) {
Ok(vtui) => vtui.expand().into(),
Err(err) => {
let error = err.to_compile_error();
let tokens = quote! {
#error
Node::from(c)
};
tokens.into()
}
}
}
pub fn is_expr_enum(expr: &Expr) -> Option<String> {
let path = match expr {
Expr::Path(p) => &p.path,
Expr::Call(c) => match &*c.func {
Expr::Path(p) => &p.path,
_ => return None,
},
_ => return None,
};
Some(path.segments.first()?.ident.to_string())
}