From cba23a005cc991e642c834947da87ff6b31638e2 Mon Sep 17 00:00:00 2001 From: unspeaker Date: Sun, 4 May 2025 19:59:41 +0300 Subject: [PATCH] proc: expose macro implementation --- proc/src/lib.rs | 41 +++-- proc/src/proc_expose.rs | 392 ++++++++++++++++++++++++++++++++++++++++ proc/src/proc_view.rs | 18 +- 3 files changed, 417 insertions(+), 34 deletions(-) create mode 100644 proc/src/proc_expose.rs diff --git a/proc/src/lib.rs b/proc/src/lib.rs index 0709e4b..52c8814 100644 --- a/proc/src/lib.rs +++ b/proc/src/lib.rs @@ -1,26 +1,31 @@ extern crate proc_macro; -use proc_macro::TokenStream; -use proc_macro2::{TokenStream as TokenStream2}; + +pub(crate) use std::collections::{HashMap, BTreeMap}; +pub(crate) use std::cmp::Ordering; +pub(crate) use proc_macro::TokenStream; +pub(crate) use proc_macro2::{ + TokenStream as TokenStream2, TokenTree, + Ident, Span, Punct, Spacing::*, Group, Delimiter, Literal +}; +pub(crate) use syn::{ + parse, parse_macro_input, parse_quote as pq, + braced, bracketed, parenthesized, Token, + Arm, Expr, Attribute, Meta, MetaList, Path, PathSegment, PathArguments, ImplItem, LitStr, Type, + parse::{Parse, ParseStream, Result}, + token::{PathSep, Brace}, + punctuated::Punctuated, +}; +pub(crate) use quote::{quote, TokenStreamExt, ToTokens}; mod proc_view; +mod proc_expose; #[proc_macro_attribute] pub fn view (meta: TokenStream, item: TokenStream) -> TokenStream { self::proc_view::view_impl(meta.into(), item.into()).into() - //for attr in syn::parse_macro_input!(meta as syn::MetaList).iter() { - //} - //let item = syn::parse_macro_input!(item as syn::ItemImpl); - //let output = "TuiOut"; - //let target = "Tek"; - //let define = "self.config.view"; - //let expose = vec![]; - //let output = format!( - //"::tengri_dsl::view!({}:|self:{}|self.size.of(::tengri_dsl::View(self,{}));{{{}}});", - //output, - //target, - //define, - //expose.iter().fold(String::new(), |acc, (key, value)|format!("{acc},{key}=>{value}")), - //); - //let output = ""; - //output.parse().unwrap() +} + +#[proc_macro_attribute] +pub fn expose (meta: TokenStream, item: TokenStream) -> TokenStream { + self::proc_expose::expose_impl(meta.into(), item.into()).into() } diff --git a/proc/src/proc_expose.rs b/proc/src/proc_expose.rs new file mode 100644 index 0000000..2c34b14 --- /dev/null +++ b/proc/src/proc_expose.rs @@ -0,0 +1,392 @@ +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)] +struct ExposeDefinition { + meta: ExposeMeta, + data: ExposeImpl, +} + +impl Parse for ExposeDefinition { + fn parse (input: ParseStream) -> Result { + Ok(Self { + meta: input.parse::()?, + data: input.parse::()?, + }) + } +} + +impl ToTokens for ExposeDefinition { + fn to_tokens (&self, out: &mut TokenStream2) { + let Self { meta, data } = self; + for token in quote! { #data } { + out.append(token) + } + } +} + +#[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; + for token in quote! { impl #target { #(#items)* } } { + out.append(token); + } + for (t, variants) in types.iter() { + let predef = match format!("{}", quote! { #t }).as_str() { + "bool" => vec![ + quote! { ::tengri::dsl::Value::Sym(":true") => true }, + quote! { ::tengri::dsl::Value::Sym(":false") => false }, + ], + "u8" | "u16" | "u32" | "u64" | "usize" | + "i8" | "i16" | "i32" | "i64" | "isize" => vec![ + quote! { ::tengri::dsl::Value::Num(n) => *n }, + ], + _ => vec![], + }; + let values = variants.values(); + let trait_impl = quote! { + impl ::tengri::dsl::Context<#t> for #target { + fn get (&self, dsl: &::tengri::dsl::Value) -> Option<#t> { + Some(match dsl { + #(#predef,)* + #(#values,)* + _ => return None + }) + } + } + }; + for token in trait_impl { + out.append(token); + } + } + } +} + +#[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; + out.append(Punct::new(':', Joint)); + out.append(Punct::new(':', Alone)); + out.append(Ident::new("tengri", Span::call_site())); + out.append(Punct::new(':', Joint)); + out.append(Punct::new(':', Alone)); + out.append(Ident::new("dsl", Span::call_site())); + out.append(Punct::new(':', Joint)); + out.append(Punct::new(':', Alone)); + out.append(Ident::new("Value", Span::call_site())); + out.append(Punct::new(':', Joint)); + out.append(Punct::new(':', Alone)); + out.append(Ident::new("Sym", Span::call_site())); + out.append(Group::new(Delimiter::Parenthesis, { + let mut out = TokenStream2::new(); + out.append(key.0.token()); + out + })); + out.append(Punct::new('=', Joint)); + out.append(Punct::new('>', Alone)); + for token in quote! { #value } { + out.append(token); + } + } +} + +#[derive(Debug, Clone)] +struct ExposeSym(LitStr); + +impl From for ExposeSym { + fn from (this: LitStr) -> Self { + Self(this) + } +} + +impl PartialOrd for ExposeSym { + fn partial_cmp (&self, other: &Self) -> Option { + let this = &self.0; + let that = &other.0; + Some(format!("{}", quote! { #this }).cmp(&format!("{}", quote! { #that }))) + } +} + +impl Ord for ExposeSym { + fn cmp (&self, other: &Self) -> Ordering { + let this = &self.0; + let that = &other.0; + format!("{}", quote! { #this }).cmp(&format!("{}", quote! { #that })) + } +} + +impl PartialEq for ExposeSym { + fn eq (&self, other: &Self) -> bool { + let this = &self.0; + let that = &other.0; + format!("{}", quote! { #this }) == format!("{}", quote! { #that }) + } +} + +impl Eq for ExposeSym {} + +#[derive(Debug, Clone)] +struct ExposeType(Type); + +impl From for ExposeType { + fn from (this: Type) -> Self { + Self(this) + } +} + +impl PartialOrd for ExposeType { + fn partial_cmp (&self, other: &Self) -> Option { + let this = &self.0; + let that = &other.0; + Some(format!("{}", quote! { #this }).cmp(&format!("{}", quote! { #that }))) + } +} + +impl Ord for ExposeType { + fn cmp (&self, other: &Self) -> Ordering { + let this = &self.0; + let that = &other.0; + format!("{}", quote! { #this }).cmp(&format!("{}", quote! { #that })) + } +} + +impl PartialEq for ExposeType { + fn eq (&self, other: &Self) -> bool { + let this = &self.0; + let that = &other.0; + format!("{}", quote! { #this }) == format!("{}", quote! { #that }) + } +} + +impl Eq for ExposeType {} + +impl ToTokens for ExposeType { + fn to_tokens (&self, out: &mut TokenStream2) { + self.0.to_tokens(out) + } +} + +#[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 + //}) + //} + //} + //}) + //); + + 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 () {} + //} + //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 7095fce..2a32d27 100644 --- a/proc/src/proc_view.rs +++ b/proc/src/proc_view.rs @@ -1,14 +1,4 @@ -use proc_macro::TokenStream; -use proc_macro2::{ - TokenStream as TokenStream2, TokenTree, - Ident, Span, Punct, Spacing, Group, Delimiter, Literal -}; -use syn::{parse, parse_macro_input, braced, Token}; -use syn::{Expr, Attribute, Meta, MetaList, Path, PathSegment, PathArguments, ImplItem, LitStr}; -use syn::parse::{Parse, ParseStream, Result}; -use syn::token::{PathSep, Brace}; -use syn::punctuated::Punctuated; -use quote::{quote, TokenStreamExt, ToTokens}; +use crate::*; pub(crate) fn view_impl (meta: TokenStream, data: TokenStream) -> TokenStream { let mut out = TokenStream2::new(); @@ -28,7 +18,6 @@ struct ViewDefinition { #[derive(Debug, Clone)] struct ViewMeta { output: Ident, - //attrs: Vec, } #[derive(Debug, Clone)] @@ -70,7 +59,7 @@ impl Parse for ViewMeta { impl Parse for ViewImpl { fn parse (input: ParseStream) -> Result { let _ = input.parse::()?; - let mut syms = vec![]; + let mut syms = vec![]; Ok(Self { target: input.parse::()?, items: { @@ -131,7 +120,6 @@ impl Parse for ViewItem { impl ToTokens for ViewSym { fn to_tokens (&self, out: &mut TokenStream2) { - use Spacing::*; out.append(Punct::new(':', Joint)); out.append(Punct::new(':', Alone)); out.append(Ident::new("tengri", Span::call_site())); @@ -226,8 +214,6 @@ impl std::cmp::PartialEq for ViewSym { } } -#[cfg(test)] use syn::{ItemImpl, parse_quote as pq}; - #[cfg(test)] #[test] fn test_view_meta () { let x: ViewMeta = pq! { SomeOutput }; let output: Ident = pq! { SomeOutput };