From b827a5c6406b115ffe698326294fbeac21c6a3a8 Mon Sep 17 00:00:00 2001 From: unspeaker Date: Tue, 6 May 2025 00:48:14 +0300 Subject: [PATCH] wip: proc: simplify expose macro --- proc/Cargo.toml | 5 +- proc/src/lib.rs | 23 ++- proc/src/proc_expose.rs | 376 ++++++++++++++++------------------------ proc/src/proc_view.rs | 137 ++++----------- 4 files changed, 201 insertions(+), 340 deletions(-) diff --git a/proc/Cargo.toml b/proc/Cargo.toml index aa2cd24..6001947 100644 --- a/proc/Cargo.toml +++ b/proc/Cargo.toml @@ -8,6 +8,7 @@ edition = { workspace = true } proc-macro = true [dependencies] -syn = { version = "2", features = ["full", "extra-traits"] } -quote = { version = "1" } +syn = { version = "2", features = ["full", "extra-traits"] } +quote = { version = "1" } proc-macro2 = { version = "1", features = ["span-locations"] } +heck = { version = "0.5" } diff --git a/proc/src/lib.rs b/proc/src/lib.rs index 52c8814..ac176da 100644 --- a/proc/src/lib.rs +++ b/proc/src/lib.rs @@ -1,6 +1,6 @@ extern crate proc_macro; -pub(crate) use std::collections::{HashMap, BTreeMap}; +pub(crate) use std::collections::{BTreeMap, BTreeSet}; pub(crate) use std::cmp::Ordering; pub(crate) use proc_macro::TokenStream; pub(crate) use proc_macro2::{ @@ -22,10 +22,27 @@ mod proc_expose; #[proc_macro_attribute] pub fn view (meta: TokenStream, item: TokenStream) -> TokenStream { - self::proc_view::view_impl(meta.into(), item.into()).into() + use self::proc_view::{ViewDefinition, ViewMeta, ViewImpl}; + write_macro(ViewDefinition( + parse_macro_input!(meta as ViewMeta), + parse_macro_input!(data as ViewImpl), + )) } #[proc_macro_attribute] pub fn expose (meta: TokenStream, item: TokenStream) -> TokenStream { - self::proc_expose::expose_impl(meta.into(), item.into()).into() + use self::proc_view::{ExposeDefinition, ExposeMeta, ExposeImpl}; + write_macro(ExposeDefinition( + parse_macro_input!(meta as ExposeMeta), + parse_macro_input!(data as ExposeImpl), + )) +} + +fn write_macro (t: T) -> TokenStream { + let mut out = TokenStream2::new(); + self::proc_expose::ExposeDefinition( + parse_macro_input!(meta as ExposeMeta), + parse_macro_input!(data as ExposeImpl), + ).to_tokens(&mut out); + out.into() } diff --git a/proc/src/proc_expose.rs b/proc/src/proc_expose.rs index 2c34b14..e153fa6 100644 --- a/proc/src/proc_expose.rs +++ b/proc/src/proc_expose.rs @@ -1,27 +1,59 @@ use crate::*; use syn::parse::discouraged::Speculative; -pub(crate) fn expose_impl (meta: TokenStream, data: TokenStream) -> TokenStream { - let mut out = TokenStream2::new(); - ExposeDefinition { - meta: parse_macro_input!(meta as ExposeMeta), - data: parse_macro_input!(data as ExposeImpl), - }.to_tokens(&mut out); - out.into() +#[derive(Debug, Clone)] +pub(crate) struct ExposeDefinition(pub(crate) ExposeMeta, pub(crate) ExposeImpl); + +#[derive(Debug, Clone)] +pub(crate) struct ExposeMeta; + +#[derive(Debug, Clone)] +pub(crate) struct ExposeImpl { + block: ItemImpl, + exposed: BTreeMap>, } #[derive(Debug, Clone)] -struct ExposeDefinition { - meta: ExposeMeta, - data: ExposeImpl, +struct ExposeArm { + key: ExposeSym, + value: Expr } -impl Parse for ExposeDefinition { +#[derive(Debug, Clone)] +struct ExposeSym(LitStr); + +#[derive(Debug, Clone)] +struct ExposeType(Box); + +impl Parse for ExposeMeta { fn parse (input: ParseStream) -> Result { - Ok(Self { - meta: input.parse::()?, - data: input.parse::()?, - }) + Ok(Self) + } +} + +impl Parse for ExposeImpl { + fn parse (input: ParseStream) -> Result { + let block = input.parse::()?; + let mut exposed: BTreeMap> = Default::default(); + for item in block.items.iter() { + if let ImplItem::Fn(ImplItemFn { sig: Signature { ident, output, .. }, .. }) = item { + if let ReturnType::Type(_, return_type) = output { + let return_type = ExposeType(return_type.clone()); + if !exposed.contains_key(return_type) { + exposed.insert(return_type.clone(), Default::default()) + } + let values = exposed.get_mut(&return_type).unwrap(); + let key = format!(":{}", AsKebabCase(&ident)); + if values.contains_key(key) { + return Err(input.error(format!("already defined: {key}"))) + } + values.insert(key, ident.clone()); + } else { + return Err(input.error("output type must be specified")) + } + } + } + Ok(Self { block, exposed }) } } @@ -34,62 +66,6 @@ impl ToTokens for ExposeDefinition { } } -#[derive(Debug, Clone)] -struct ExposeMeta {} - -impl Parse for ExposeMeta { - fn parse (input: ParseStream) -> Result { - Ok(Self {}) - } -} - -#[derive(Debug, Clone)] -struct ExposeImpl { - target: Ident, - items: Vec, - types: BTreeMap>, -} - -impl Parse for ExposeImpl { - fn parse (input: ParseStream) -> Result { - let _impl = input.parse::()?; - let target = input.parse::()?; - let group; - let brace = braced!(group in input); - let mut items = vec![]; - let mut types: BTreeMap> = Default::default(); - while !group.is_empty() { - let fork = group.fork(); - if let Ok(block) = fork.parse::() { - let t = block.type_.into(); - if let Some(values) = types.get_mut(&t) { - for (key, value) in block.values.into_iter() { - if values.contains_key(&key) { - return Err(input.error(format!("{key:?} ({t:?}): already exists"))) - } else { - values.insert(key, value); - } - } - } else { - types.insert(t, block.values); - } - group.advance_to(&fork); - continue - } - let fork = group.fork(); - if let Ok(item) = fork.parse::() { - items.push(item); - group.advance_to(&fork); - continue - } - return Err(input.error( - "expected either item or #[tengri::expose(type)] { \":key\" => value }" - )); - } - Ok(Self { target, items, types }) - } -} - impl ToTokens for ExposeImpl { fn to_tokens (&self, out: &mut TokenStream2) { let Self { target, items, types } = self; @@ -97,7 +73,7 @@ impl ToTokens for ExposeImpl { out.append(token); } for (t, variants) in types.iter() { - let predef = match format!("{}", quote! { #t }).as_str() { + let predefined = match format!("{}", quote! { #t }).as_str() { "bool" => vec![ quote! { ::tengri::dsl::Value::Sym(":true") => true }, quote! { ::tengri::dsl::Value::Sym(":false") => false }, @@ -113,7 +89,7 @@ impl ToTokens for ExposeImpl { impl ::tengri::dsl::Context<#t> for #target { fn get (&self, dsl: &::tengri::dsl::Value) -> Option<#t> { Some(match dsl { - #(#predef,)* + #(#predefined,)* #(#values,)* _ => return None }) @@ -127,58 +103,6 @@ impl ToTokens for ExposeImpl { } } -#[derive(Debug, Clone)] -struct ExposeBlock { - type_: Type, - values: BTreeMap, -} - -impl Parse for ExposeBlock { - fn parse (input: ParseStream) -> Result { - let _ = input.parse::()?; - - let group; - let bracket = bracketed!(group in input); - let path = group.parse::()?; - let type_ = if - path.segments.get(0).map(|x|x.ident.to_string()) == Some("tengri".to_string()) && - path.segments.get(1).map(|x|x.ident.to_string()) == Some("expose".to_string()) - { - let token; - let paren = parenthesized!(token in group); - token.parse::()? - } else { - return Err(input.error("expected #[tengri::expose(type)]")) - }; - - let group; - let brace = braced!(group in input); - let mut values = BTreeMap::new(); - while !group.is_empty() { - let arm = group.parse::()?; - values.insert(arm.key.clone(), arm); - let _ = group.parse::()?; - } - Ok(Self { type_, values }) - } -} - -#[derive(Debug, Clone)] -struct ExposeArm { - key: ExposeSym, - value: Expr -} - -impl Parse for ExposeArm { - fn parse (input: ParseStream) -> Result { - let key = input.parse::()?.into(); - let _ = input.parse::()?; - let _ = input.parse::]>()?; - let value = input.parse::()?; - Ok(Self { key, value }) - } -} - impl ToTokens for ExposeArm { fn to_tokens (&self, out: &mut TokenStream2) { let Self { key, value } = self; @@ -207,9 +131,6 @@ impl ToTokens for ExposeArm { } } -#[derive(Debug, Clone)] -struct ExposeSym(LitStr); - impl From for ExposeSym { fn from (this: LitStr) -> Self { Self(this) @@ -242,9 +163,6 @@ impl PartialEq for ExposeSym { impl Eq for ExposeSym {} -#[derive(Debug, Clone)] -struct ExposeType(Type); - impl From for ExposeType { fn from (this: Type) -> Self { Self(this) @@ -284,109 +202,107 @@ impl ToTokens for ExposeType { } #[cfg(test)] #[test] fn test_expose_definition () { - let parsed: ExposeImpl = pq! { - //#[tengri_proc::expose] - impl Something { - #[tengri::expose(bool)] { - ":bool1" => true || false, - } - fn something () {} - } - }; - // FIXME: - //assert_eq!( - //format!("{}", quote! { #parsed }), - //format!("{}", quote! { - //impl Something { - //fn something () {} - //} - //impl ::tengri::Context for Something { - //fn get (&self, dsl: &::tengri::Value) -> Option { - //Some(match dsl { - //::tengri::Value::Sym(":true") => true, - //::tengri::Value::Sym(":false") => false, - //::tengri::Value::Sym(":bool1") => true || false, - //_ => return None - //}) - //} - //} - //}) - //); + // TODO + //let parsed: ExposeImpl = pq! { + ////#[tengri_proc::expose] + //impl Something { + //fn something () -> bool {} + //} + //}; + //// FIXME: + ////assert_eq!( + ////format!("{}", quote! { #parsed }), + ////format!("{}", quote! { + ////impl Something { + ////fn something () {} + ////} + ////impl ::tengri::Context for Something { + ////fn get (&self, dsl: &::tengri::Value) -> Option { + ////Some(match dsl { + ////::tengri::Value::Sym(":true") => true, + ////::tengri::Value::Sym(":false") => false, + ////::tengri::Value::Sym(":bool1") => true || false, + ////_ => return None + ////}) + ////} + ////} + ////}) + ////); - let parsed: ExposeImpl = pq! { - //#[tengri_proc::expose] - impl Something { - #[tengri::expose(bool)] { - ":bool1" => true || false, - } - #[tengri::expose(u16)] { - ":u161" => 0 + 1, - } - #[tengri::expose(usize)] { - ":usize1" => 1 + 2, - } - #[tengri::expose(Arc)] { - ":arcstr1" => "foo".into(), - } - #[tengri::expose(Option>)] { - ":optarcstr1" => Some("bar".into()), - ":optarcstr2" => Some("baz".into()), - } - fn something () {} - } - }; - // FIXME: - //assert_eq!( - //format!("{}", quote! { #parsed }), - //format!("{}", quote! { - //impl Something { - //fn something () {} + //let parsed: ExposeImpl = pq! { + ////#[tengri_proc::expose] + //impl Something { + //#[tengri::expose(bool)] { + //":bool1" => true || false, //} - //impl ::tengri::Context> for Something { - //fn get (&self, dsl: &::tengri::Value) -> Option> { - //Some(match dsl { - //::tengri::Value::Sym(":arcstr1") => "foo".into(), - //_ => return None - //}) - //} + //#[tengri::expose(u16)] { + //":u161" => 0 + 1, //} - //impl ::tengri::Context>> for Something { - //fn get (&self, dsl: &::tengri::Value) -> Option>> { - //Some(match dsl { - //::tengri::Value::Sym(":optarcstr1") => Some("bar".into()), - //::tengri::Value::Sym(":optarcstr2") => Some("baz".into()), - //_ => return None - //}) - //} + //#[tengri::expose(usize)] { + //":usize1" => 1 + 2, //} - //impl ::tengri::Context for Something { - //fn get (&self, dsl: &::tengri::Value) -> Option { - //Some(match dsl { - //::tengri::Value::Sym(":true") => true, - //::tengri::Value::Sym(":false") => false, - //::tengri::Value::Sym(":bool1") => true || false, - //_ => return None - //}) - //} + //#[tengri::expose(Arc)] { + //":arcstr1" => "foo".into(), //} - //impl ::tengri::Context for Something { - //fn get (&self, dsl: &::tengri::Value) -> Option { - //Some(match dsl { - //::tengri::Value::Num(n) => *n as u16, - //::tengri::Value::Sym(":u161") => 0 + 1, - //_ => return None - //}) - //} + //#[tengri::expose(Option>)] { + //":optarcstr1" => Some("bar".into()), + //":optarcstr2" => Some("baz".into()), //} - //impl ::tengri::Context for Something { - //fn get (&self, dsl: &::tengri::Value) -> Option { - //Some(match dsl { - //::tengri::Value::Num(n) => *n as usize, - //::tengri::Value::Sym(":usize1") => 1 + 2, - //_ => return None - //}) - //} - //} - //}) - //) + //fn something () {} + //} + //}; + //// FIXME: + ////assert_eq!( + ////format!("{}", quote! { #parsed }), + ////format!("{}", quote! { + ////impl Something { + ////fn something () {} + ////} + ////impl ::tengri::Context> for Something { + ////fn get (&self, dsl: &::tengri::Value) -> Option> { + ////Some(match dsl { + ////::tengri::Value::Sym(":arcstr1") => "foo".into(), + ////_ => return None + ////}) + ////} + ////} + ////impl ::tengri::Context>> for Something { + ////fn get (&self, dsl: &::tengri::Value) -> Option>> { + ////Some(match dsl { + ////::tengri::Value::Sym(":optarcstr1") => Some("bar".into()), + ////::tengri::Value::Sym(":optarcstr2") => Some("baz".into()), + ////_ => return None + ////}) + ////} + ////} + ////impl ::tengri::Context for Something { + ////fn get (&self, dsl: &::tengri::Value) -> Option { + ////Some(match dsl { + ////::tengri::Value::Sym(":true") => true, + ////::tengri::Value::Sym(":false") => false, + ////::tengri::Value::Sym(":bool1") => true || false, + ////_ => return None + ////}) + ////} + ////} + ////impl ::tengri::Context for Something { + ////fn get (&self, dsl: &::tengri::Value) -> Option { + ////Some(match dsl { + ////::tengri::Value::Num(n) => *n as u16, + ////::tengri::Value::Sym(":u161") => 0 + 1, + ////_ => return None + ////}) + ////} + ////} + ////impl ::tengri::Context for Something { + ////fn get (&self, dsl: &::tengri::Value) -> Option { + ////Some(match dsl { + ////::tengri::Value::Num(n) => *n as usize, + ////::tengri::Value::Sym(":usize1") => 1 + 2, + ////_ => return None + ////}) + ////} + ////} + ////}) + ////) } diff --git a/proc/src/proc_view.rs b/proc/src/proc_view.rs index 2a32d27..bb805e9 100644 --- a/proc/src/proc_view.rs +++ b/proc/src/proc_view.rs @@ -1,51 +1,17 @@ use crate::*; -pub(crate) fn view_impl (meta: TokenStream, data: TokenStream) -> TokenStream { - let mut out = TokenStream2::new(); - ViewDefinition { - meta: parse_macro_input!(meta as ViewMeta), - data: parse_macro_input!(data as ViewImpl), - }.to_tokens(&mut out); - out.into() -} +#[derive(Debug, Clone)] +pub(crate) struct ViewDefinition(pub(crate) ViewMeta, pub(crate) ViewImpl); #[derive(Debug, Clone)] -struct ViewDefinition { - meta: ViewMeta, - data: ViewImpl, -} - -#[derive(Debug, Clone)] -struct ViewMeta { +pub(crate) struct ViewMeta { output: Ident, } #[derive(Debug, Clone)] -struct ViewImpl { - target: Ident, - items: Vec, - syms: Vec, -} - -#[derive(Debug, Clone)] -struct ViewItem { - item: ImplItem, - expose: Option, -} - -#[derive(Debug, Clone)] -struct ViewSym { - symbol: Literal, - name: Ident, -} - -impl Parse for ViewDefinition { - fn parse (input: ParseStream) -> Result { - Ok(Self { - meta: input.parse::()?, - data: input.parse::()?, - }) - } +pub(crate) struct ViewImpl { + block: ItemImpl, + exposed: BTreeSet, } impl Parse for ViewMeta { @@ -88,33 +54,32 @@ impl Parse for ViewImpl { } } - -impl Parse for ViewItem { - fn parse (input: ParseStream) -> Result { - let mut expose = None; - Ok(Self { - item: { - let mut item = input.parse::()?; - if let ImplItem::Fn(ref mut item) = item { - item.attrs = item.attrs.iter().filter(|attr| { - if let Attribute { - meta: Meta::List(MetaList { path, tokens, .. }), .. - } = attr - && path.segments.len() == 2 - && nth_segment_is(&path.segments, 0, "tengri") - && nth_segment_is(&path.segments, 1, "view") - && let Some(TokenTree::Literal(name)) = tokens.clone().into_iter().next() - { - expose = Some(name); - return false - } - true - }).map(|x|x.clone()).collect(); - }; - item - }, - expose, - }) +impl ToTokens for ViewDefinition { + fn to_tokens (&self, out: &mut TokenStream2) { + let Self(ViewMeta { output }, ViewImpl { target, syms, items }) = self; + for token in quote! { + /// Augmented by [tengri_proc]. + impl #target { + #(#items)* + } + /// Generated by [tengri_proc]. + impl ::tengri::output::Content<#output> for #target { + fn content (&self) -> impl Render<#output> { + self.size.of(::tengri::output::View(self, self.config.view)) + } + } + /// Generated by [tengri_proc]. + impl<'a> ::tengri::output::ViewContext<'a, #output> for #target { + fn get_content_sym (&'a self, value: &Value<'a>) -> Option> { + match value { + #(#syms)* + _ => panic!("expected Sym(content), got: {value:?}") + } + } + } + } { + out.append(token) + } } } @@ -155,44 +120,6 @@ impl ToTokens for ViewSym { } } -impl ToTokens for ViewItem { - fn to_tokens (&self, out: &mut TokenStream2) { - self.item.to_tokens(out) - } -} - -impl ToTokens for ViewDefinition { - fn to_tokens (&self, out: &mut TokenStream2) { - let Self { - meta: ViewMeta { output }, - data: ViewImpl { target, syms, items }, - } = self; - for token in quote! { - /// Augmented by [tengri_proc]. - impl #target { - #(#items)* - } - /// Generated by [tengri_proc]. - impl ::tengri::output::Content<#output> for #target { - fn content (&self) -> impl Render<#output> { - self.size.of(::tengri::output::View(self, self.config.view)) - } - } - /// Generated by [tengri_proc]. - impl<'a> ::tengri::output::ViewContext<'a, #output> for #target { - fn get_content_sym (&'a self, value: &Value<'a>) -> Option> { - match value { - #(#syms)* - _ => panic!("expected Sym(content), got: {value:?}") - } - } - } - } { - out.append(token) - } - } -} - fn nth_segment_is (segments: &Punctuated, n: usize, x: &str) -> bool { if let Some(PathSegment { arguments: PathArguments::None, ident, .. }) = segments.get(n) { if format!("{ident}") == x {