proc: expose macro implementation
Some checks are pending
/ build (push) Waiting to run

This commit is contained in:
🪞👃🪞 2025-05-04 19:59:41 +03:00
parent b543c43e68
commit cba23a005c
3 changed files with 417 additions and 34 deletions

View file

@ -1,26 +1,31 @@
extern crate proc_macro; 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_view;
mod proc_expose;
#[proc_macro_attribute] #[proc_macro_attribute]
pub fn view (meta: TokenStream, item: TokenStream) -> TokenStream { pub fn view (meta: TokenStream, item: TokenStream) -> TokenStream {
self::proc_view::view_impl(meta.into(), item.into()).into() 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); #[proc_macro_attribute]
//let output = "TuiOut"; pub fn expose (meta: TokenStream, item: TokenStream) -> TokenStream {
//let target = "Tek"; self::proc_expose::expose_impl(meta.into(), item.into()).into()
//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()
} }

392
proc/src/proc_expose.rs Normal file
View file

@ -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<Self> {
Ok(Self {
meta: input.parse::<ExposeMeta>()?,
data: input.parse::<ExposeImpl>()?,
})
}
}
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<Self> {
Ok(Self {})
}
}
#[derive(Debug, Clone)]
struct ExposeImpl {
target: Ident,
items: Vec<ImplItem>,
types: BTreeMap<ExposeType, BTreeMap<ExposeSym, ExposeArm>>,
}
impl Parse for ExposeImpl {
fn parse (input: ParseStream) -> Result<Self> {
let _impl = input.parse::<Token![impl]>()?;
let target = input.parse::<Ident>()?;
let group;
let brace = braced!(group in input);
let mut items = vec![];
let mut types: BTreeMap<ExposeType, BTreeMap<ExposeSym, ExposeArm>> = Default::default();
while !group.is_empty() {
let fork = group.fork();
if let Ok(block) = fork.parse::<ExposeBlock>() {
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::<ImplItem>() {
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<ExposeSym, ExposeArm>,
}
impl Parse for ExposeBlock {
fn parse (input: ParseStream) -> Result<Self> {
let _ = input.parse::<Token![#]>()?;
let group;
let bracket = bracketed!(group in input);
let path = group.parse::<Path>()?;
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::<Type>()?
} 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::<ExposeArm>()?;
values.insert(arm.key.clone(), arm);
let _ = group.parse::<Token![,]>()?;
}
Ok(Self { type_, values })
}
}
#[derive(Debug, Clone)]
struct ExposeArm {
key: ExposeSym,
value: Expr
}
impl Parse for ExposeArm {
fn parse (input: ParseStream) -> Result<Self> {
let key = input.parse::<LitStr>()?.into();
let _ = input.parse::<Token![=]>()?;
let _ = input.parse::<Token![>]>()?;
let value = input.parse::<Expr>()?;
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<LitStr> for ExposeSym {
fn from (this: LitStr) -> Self {
Self(this)
}
}
impl PartialOrd for ExposeSym {
fn partial_cmp (&self, other: &Self) -> Option<Ordering> {
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<Type> for ExposeType {
fn from (this: Type) -> Self {
Self(this)
}
}
impl PartialOrd for ExposeType {
fn partial_cmp (&self, other: &Self) -> Option<Ordering> {
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<bool> for Something {
//fn get (&self, dsl: &::tengri::Value) -> Option<bool> {
//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<str>)] {
":arcstr1" => "foo".into(),
}
#[tengri::expose(Option<Arc<str>>)] {
":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<Arc<str>> for Something {
//fn get (&self, dsl: &::tengri::Value) -> Option<Arc<str>> {
//Some(match dsl {
//::tengri::Value::Sym(":arcstr1") => "foo".into(),
//_ => return None
//})
//}
//}
//impl ::tengri::Context<Option<Arc<str>>> for Something {
//fn get (&self, dsl: &::tengri::Value) -> Option<Option<Arc<str>>> {
//Some(match dsl {
//::tengri::Value::Sym(":optarcstr1") => Some("bar".into()),
//::tengri::Value::Sym(":optarcstr2") => Some("baz".into()),
//_ => return None
//})
//}
//}
//impl ::tengri::Context<bool> for Something {
//fn get (&self, dsl: &::tengri::Value) -> Option<bool> {
//Some(match dsl {
//::tengri::Value::Sym(":true") => true,
//::tengri::Value::Sym(":false") => false,
//::tengri::Value::Sym(":bool1") => true || false,
//_ => return None
//})
//}
//}
//impl ::tengri::Context<u16> for Something {
//fn get (&self, dsl: &::tengri::Value) -> Option<u16> {
//Some(match dsl {
//::tengri::Value::Num(n) => *n as u16,
//::tengri::Value::Sym(":u161") => 0 + 1,
//_ => return None
//})
//}
//}
//impl ::tengri::Context<usize> for Something {
//fn get (&self, dsl: &::tengri::Value) -> Option<usize> {
//Some(match dsl {
//::tengri::Value::Num(n) => *n as usize,
//::tengri::Value::Sym(":usize1") => 1 + 2,
//_ => return None
//})
//}
//}
//})
//)
}

View file

@ -1,14 +1,4 @@
use proc_macro::TokenStream; use crate::*;
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};
pub(crate) fn view_impl (meta: TokenStream, data: TokenStream) -> TokenStream { pub(crate) fn view_impl (meta: TokenStream, data: TokenStream) -> TokenStream {
let mut out = TokenStream2::new(); let mut out = TokenStream2::new();
@ -28,7 +18,6 @@ struct ViewDefinition {
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
struct ViewMeta { struct ViewMeta {
output: Ident, output: Ident,
//attrs: Vec<Attribute>,
} }
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
@ -70,7 +59,7 @@ impl Parse for ViewMeta {
impl Parse for ViewImpl { impl Parse for ViewImpl {
fn parse (input: ParseStream) -> Result<Self> { fn parse (input: ParseStream) -> Result<Self> {
let _ = input.parse::<Token![impl]>()?; let _ = input.parse::<Token![impl]>()?;
let mut syms = vec![]; let mut syms = vec![];
Ok(Self { Ok(Self {
target: input.parse::<Ident>()?, target: input.parse::<Ident>()?,
items: { items: {
@ -131,7 +120,6 @@ impl Parse for ViewItem {
impl ToTokens for ViewSym { impl ToTokens for ViewSym {
fn to_tokens (&self, out: &mut TokenStream2) { fn to_tokens (&self, out: &mut TokenStream2) {
use Spacing::*;
out.append(Punct::new(':', Joint)); out.append(Punct::new(':', Joint));
out.append(Punct::new(':', Alone)); out.append(Punct::new(':', Alone));
out.append(Ident::new("tengri", Span::call_site())); 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 () { #[cfg(test)] #[test] fn test_view_meta () {
let x: ViewMeta = pq! { SomeOutput }; let x: ViewMeta = pq! { SomeOutput };
let output: Ident = pq! { SomeOutput }; let output: Ident = pq! { SomeOutput };