diff --git a/Cargo.lock b/Cargo.lock index 96211a0..4e5ce2b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -972,6 +972,15 @@ dependencies = [ "tengri_tui", ] +[[package]] +name = "tengri_proc" +version = "0.13.0" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "tengri_tui" version = "0.13.0" diff --git a/Cargo.toml b/Cargo.toml index daeb8ce..4a6d408 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,5 +1,6 @@ [workspace.package] version = "0.13.0" +edition = "2024" [workspace] resolver = "2" @@ -8,7 +9,8 @@ members = [ "./input", "./output", "./tui", - "./dsl" + "./dsl", + "./proc", ] [profile.release] diff --git a/dsl/Cargo.toml b/dsl/Cargo.toml index 94238a8..3609ada 100644 --- a/dsl/Cargo.toml +++ b/dsl/Cargo.toml @@ -1,8 +1,8 @@ [package] name = "tengri_dsl" -edition = "2024" description = "UI metaframework, tiny S-expression-based DSL." version = { workspace = true } +edition = { workspace = true } [dependencies] konst = { version = "0.3.16", features = [ "rust_1_83" ] } diff --git a/input/Cargo.toml b/input/Cargo.toml index a3ca0ce..3b7772f 100644 --- a/input/Cargo.toml +++ b/input/Cargo.toml @@ -1,8 +1,8 @@ [package] name = "tengri_input" -edition = "2024" description = "UI metaframework, input layer." version = { workspace = true } +edition = { workspace = true } [dependencies] tengri_dsl = { optional = true, path = "../dsl" } diff --git a/output/Cargo.toml b/output/Cargo.toml index b208a5b..2505df5 100644 --- a/output/Cargo.toml +++ b/output/Cargo.toml @@ -1,8 +1,8 @@ [package] name = "tengri_output" -edition = "2024" description = "UI metaframework, output layer." version = { workspace = true } +edition = { workspace = true } [dependencies] tengri_dsl = { optional = true, path = "../dsl" } diff --git a/proc/Cargo.toml b/proc/Cargo.toml new file mode 100644 index 0000000..2462993 --- /dev/null +++ b/proc/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "tengri_proc" +description = "UI metaframework, procedural macros." +version = { workspace = true } +edition = { workspace = true } + +[lib] +proc-macro = true + +[dependencies] +syn = { version = "2", features = ["full"] } +quote = { version = "1" } +proc-macro2 = { version = "1", features = ["span-locations"] } diff --git a/proc/src/lib.rs b/proc/src/lib.rs new file mode 100644 index 0000000..0709e4b --- /dev/null +++ b/proc/src/lib.rs @@ -0,0 +1,26 @@ +extern crate proc_macro; +use proc_macro::TokenStream; +use proc_macro2::{TokenStream as TokenStream2}; + +mod proc_view; + +#[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() +} diff --git a/proc/src/proc_view.rs b/proc/src/proc_view.rs new file mode 100644 index 0000000..fe91a8b --- /dev/null +++ b/proc/src/proc_view.rs @@ -0,0 +1,120 @@ +use proc_macro::TokenStream; +use proc_macro2::{TokenStream as TokenStream2, TokenTree, Ident}; +use syn::parse_macro_input; +use syn::{Expr, Attribute, Meta, MetaList, Path, PathSegment, PathArguments, ImplItem}; +use syn::parse::{Parse, ParseStream, Result}; +use syn::token::PathSep; +use syn::punctuated::Punctuated; + +pub(crate) fn view_impl (meta: TokenStream, item: TokenStream) -> TokenStream { + let ViewMeta { output, define, attrs, .. } = parse_macro_input!(meta as ViewMeta); + let ViewItem { target, mapped, items, .. } = parse_macro_input!(item as ViewItem); + quote::quote! { + #(#attrs)* + impl #target { + #(#items)* + } + /// Generated by [tengri_proc]. + impl ::tengri::Content<#output> for #target { + fn content (&self) -> impl Render<#output> { + self.size.of(::tengri::View(self, #define)) + } + } + /// Generated by [tengri_proc]. + impl<'a> ::tengri::ViewContext<'a, #output> for #target { + fn get_content_sym (&'a self, value: &Value<'a>) -> Option> { + match value { + #mapped + _ => panic!("expected Sym(content), got: {value:?}") + } + //if let Value::Sym(s) = value { + //match *s { + //$($sym => Some($body.boxed()),)* + //_ => None + //} + //} else { + //panic!("expected Sym(content), got: {value:?}") + //} + } + } + }.into() +} + +struct ViewMeta { + attrs: Vec, + output: Option, + define: Expr, +} + +impl Parse for ViewMeta { + fn parse (input: ParseStream) -> Result { + let mut output = None; + let mut define = None; + let mut attrs = input.call(Attribute::parse_outer)?.into_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::Group(group)) = tokens.clone().into_iter().next() + && let Some(TokenTree::Ident(ident)) = group.stream().into_iter().next() + { + output = Some(ident); + return false + } + true + }).collect(); + + if let Some(define) = define { + Ok(Self { attrs, output, define }) + } else { + Err(input.error("Missing view definition.")) + } + } +} + +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 { + return true + } + } + return false +} + +struct ViewItem { + items: Vec, + target: &'static str, + mapped: &'static str, +} + +impl Parse for ViewItem { + fn parse (input: ParseStream) -> Result { + let items = vec![]; + Ok(Self { + items, + target: "", + mapped: "", + }) + } +} + +#[cfg(test)] #[test] fn test_view () { + + use syn::{ItemImpl, parse_quote}; + + let _: ItemImpl = parse_quote! { + #[tengri::view(Tui)] + impl SomeView { + #[tengri::view] + fn view (&self) -> impl Content + use<'_> { + "view-1" + } + + #[tengri::view(":view-1")] + fn view_1 (&self) -> impl Content + use<'_> { + "view-1" + } + } + }; + +} diff --git a/tui/Cargo.toml b/tui/Cargo.toml index e005d37..5bf0dc2 100644 --- a/tui/Cargo.toml +++ b/tui/Cargo.toml @@ -1,8 +1,8 @@ [package] name = "tengri_tui" -edition = "2024" description = "UI metaframework, Ratatui backend." version = { workspace = true } +edition = { workspace = true } [dependencies] palette = { version = "0.7.6", features = [ "random" ] }