diff --git a/Cargo.lock b/Cargo.lock index 5844762..3e2e6b8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,6 +2,43 @@ # It is not intended for manual editing. version = 3 +[[package]] +name = "equivalent" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" + +[[package]] +name = "hashbrown" +version = "0.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7dfda62a12f55daeae5015f81b0baea145391cb4520f86c248fc615d72640d12" + +[[package]] +name = "indexmap" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8adf3ddd720272c6ea8bf59463c04e0f93d0bbf7c5439b691bca2987e0270897" +dependencies = [ + "equivalent", + "hashbrown", +] + +[[package]] +name = "memchr" +version = "2.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f665ee40bc4a3c5590afb1e9677db74a508659dfd71e126420da8274909a0167" + +[[package]] +name = "proc-macro-crate" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e8366a6159044a37876a2b9817124296703c586a5c92e2c53751fa06d8d43e8" +dependencies = [ + "toml_edit", +] + [[package]] name = "proc-macro2" version = "1.0.67" @@ -31,6 +68,23 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "toml_datetime" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cda73e2f1397b1262d6dfdcef8aafae14d1de7748d66822d3bfeeb6d03e5e4b" + +[[package]] +name = "toml_edit" +version = "0.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "396e4d48bbb2b7554c944bde63101b5ae446cff6ec4a24227428f15eb72ef338" +dependencies = [ + "indexmap", + "toml_datetime", + "winnow", +] + [[package]] name = "unicode-ident" version = "1.0.12" @@ -48,7 +102,17 @@ dependencies = [ name = "uwui-dsl" version = "0.1.0" dependencies = [ + "proc-macro-crate", "proc-macro2", "quote", "syn", ] + +[[package]] +name = "winnow" +version = "0.5.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c2e3184b9c4e92ad5167ca73039d0c42476302ab603e2fec4487511f38ccefc" +dependencies = [ + "memchr", +] diff --git a/dsl/Cargo.toml b/dsl/Cargo.toml index 5845f5a..e59d70d 100644 --- a/dsl/Cargo.toml +++ b/dsl/Cargo.toml @@ -8,6 +8,7 @@ repository.workspace = true proc-macro = true [dependencies] +proc-macro-crate = "2.0.0" proc-macro2 = "1.0.67" quote = "1.0.33" syn = "2.0.37" diff --git a/dsl/src/derive/component.rs b/dsl/src/derive/component.rs new file mode 100644 index 0000000..cd1258d --- /dev/null +++ b/dsl/src/derive/component.rs @@ -0,0 +1,179 @@ +use crate::util::{define_ident, use_absolute}; +use proc_macro2::TokenStream; +use quote::{quote, ToTokens}; +use syn::{parse_macro_input, DeriveInput, GenericParam}; + +pub fn custom(input: proc_macro::TokenStream) -> proc_macro::TokenStream { + let input = ComponentDef::new(parse_macro_input!(input as DeriveInput)); + let name = input.name_with_generics(); + let generics = input.generic_predicates(); + let where_clause_predicates = input.where_clause_predicates(); + + use_absolute! { + uwui::backend::Backend as Backend, + uwui::view::GetTemplate as GetTemplate, + uwui::view::__internal::CustomComponent as CustomComponent, + uwui::view::__internal::IntoNode as IntoNode, + uwui::view::__internal::CustomComponentWrapper as CustomComponentWrapper, + } + + define_ident! { + B + } + + let expanded = quote! { + #[automatically_derived] + impl<#B: #Backend, #generics> + #CustomComponent<#B> for #name + where + #name : #GetTemplate<#B>, + #where_clause_predicates + {} + + #[automatically_derived] + impl<#B: #Backend, #generics> + #IntoNode<#B> for #name + where + #name : #GetTemplate<#B>, + #where_clause_predicates + { + type Node = #CustomComponentWrapper<#B, Self>; + + fn into_node(self) -> Self::Node { + #CustomComponentWrapper::new(self) + } + } + }; + expanded.into() +} + +pub fn native(input: proc_macro::TokenStream) -> proc_macro::TokenStream { + let input = ComponentDef::new(parse_macro_input!(input as DeriveInput)); + let name = input.name_with_generics(); + let generics = input.generic_predicates(); + let where_clause_predicates = input.where_clause_predicates(); + + use_absolute! { + uwui::backend::Backend as Backend, + uwui::backend::Render as Render, + uwui::view::__internal::Node as Node, + uwui::view::__internal::IntoNode as IntoNode, + } + + define_ident! { + B + } + + let expanded = quote! { + #[automatically_derived] + impl<#B: #Backend, #generics> + #Node<#B> for #name + where + #name : #Render<#B>, + #where_clause_predicates + {} + + #[automatically_derived] + impl<#B: #Backend, #generics> + #IntoNode<#B> for #name + where + #name : #Render<#B>, + #where_clause_predicates + { + type Node = Self; + + fn into_node(self) -> Self::Node { + self + } + } + }; + expanded.into() +} + +struct ComponentDef { + derive_input: DeriveInput, +} + +impl ComponentDef { + pub fn new(derive_input: DeriveInput) -> Self { + Self { derive_input } + } + + /// `Name` + pub fn name_with_generics(&self) -> TokenStream { + let name = &self.derive_input.ident; + let generics = self.generic_names(); + if generics.is_empty() { + name.to_token_stream() + } else { + quote! { #name < #generics > } + } + } + + /// `T1, T2, T3` + pub fn generic_names(&self) -> TokenStream { + let mut out = Vec::new(); + let generics = &self.derive_input.generics; + for param in generics.params.iter() { + match param { + GenericParam::Lifetime(lft) => { + out.push(lft.lifetime.to_token_stream()); + } + GenericParam::Type(typ) => { + out.push(typ.ident.to_token_stream()); + } + GenericParam::Const(cnst) => { + out.push(cnst.ident.to_token_stream()); + } + } + } + + quote! { + #(#out),* + } + } + + /// `T1: Constraint1, T2: Constraint2 + Constraint1, T3` + pub fn generic_predicates(&self) -> TokenStream { + let mut out = TokenStream::new(); + let generics = &self.derive_input.generics; + for param in generics.params.iter() { + match param { + GenericParam::Lifetime(lft) => { + lft.lifetime.to_tokens(&mut out); + if let Some(colon) = lft.colon_token { + let bounds = &lft.bounds; + out.extend(quote! { #colon #bounds }); + } + out.extend(quote! { , }); + } + GenericParam::Type(typ) => { + typ.ident.to_tokens(&mut out); + if let Some(colon) = typ.colon_token { + let bounds = &typ.bounds; + out.extend(quote! { #colon #bounds }); + } + out.extend(quote! { , }); + } + GenericParam::Const(cnst) => { + let kw = &cnst.const_token; + let ident = &cnst.ident; + let colon = &cnst.colon_token; + let ty = &cnst.ty; + out.extend(quote! { #kw #ident #colon #ty , }); + } + } + } + + out + } + + pub fn where_clause_predicates(&self) -> TokenStream { + self.derive_input + .generics + .where_clause + .as_ref() + .map(|clause| &clause.predicates) + .to_token_stream() + } +} diff --git a/dsl/src/derive/custom_component.rs b/dsl/src/derive/custom_component.rs deleted file mode 100644 index b70503b..0000000 --- a/dsl/src/derive/custom_component.rs +++ /dev/null @@ -1,31 +0,0 @@ -use proc_macro::TokenStream; -use quote::quote; -use syn::{parse_macro_input, DeriveInput}; - -pub fn invoke(input: TokenStream) -> TokenStream { - let input = parse_macro_input!(input as DeriveInput); - let name = input.ident; - - let expanded = quote! { - #[automatically_derived] - impl - crate::view::__internal::CustomComponent for #name - where - #name : crate::view::GetTemplate - {} - - #[automatically_derived] - impl - crate::view::__internal::IntoNode for #name - where - #name : crate::view::GetTemplate - { - type Node = crate::view::__internal::CustomComponentWrapper; - - fn into_node(self) -> Self::Node { - crate::view::__internal::CustomComponentWrapper::new(self) - } - } - }; - expanded.into() -} diff --git a/dsl/src/derive/mod.rs b/dsl/src/derive/mod.rs index 5afd19c..9cea807 100644 --- a/dsl/src/derive/mod.rs +++ b/dsl/src/derive/mod.rs @@ -1,2 +1 @@ -pub mod custom_component; -pub mod native_component; +pub mod component; diff --git a/dsl/src/derive/native_component.rs b/dsl/src/derive/native_component.rs deleted file mode 100644 index 19a69a4..0000000 --- a/dsl/src/derive/native_component.rs +++ /dev/null @@ -1,31 +0,0 @@ -use proc_macro::TokenStream; -use quote::quote; -use syn::{parse_macro_input, DeriveInput}; - -pub fn invoke(input: TokenStream) -> TokenStream { - let input = parse_macro_input!(input as DeriveInput); - let name = input.ident; - - let expanded = quote! { - #[automatically_derived] - impl - crate::view::__internal::Node for #name - where - #name : crate::backend::Render, - {} - - #[automatically_derived] - impl - crate::view::__internal::IntoNode for #name - where - #name : crate::backend::Render, - { - type Node = Self; - - fn into_node(self) -> Self::Node { - self - } - } - }; - expanded.into() -} diff --git a/dsl/src/lib.rs b/dsl/src/lib.rs index 930b64a..ef8a18b 100644 --- a/dsl/src/lib.rs +++ b/dsl/src/lib.rs @@ -4,6 +4,7 @@ use proc_macro::TokenStream; mod derive; mod template; +mod util; #[proc_macro] pub fn template(input: TokenStream) -> TokenStream { @@ -12,10 +13,10 @@ pub fn template(input: TokenStream) -> TokenStream { #[proc_macro_derive(NativeComponent)] pub fn native_component(input: TokenStream) -> TokenStream { - derive::native_component::invoke(input) + derive::component::native(input) } #[proc_macro_derive(CustomComponent)] pub fn custom_component(input: TokenStream) -> TokenStream { - derive::custom_component::invoke(input) + derive::component::custom(input) } diff --git a/dsl/src/template/compile.rs b/dsl/src/template/compile.rs index 42ac01b..cec3a76 100644 --- a/dsl/src/template/compile.rs +++ b/dsl/src/template/compile.rs @@ -1,4 +1,5 @@ use crate::template::Params; +use crate::util::{define_ident, use_absolute}; use proc_macro2::TokenStream; use quote::{quote, ToTokens}; @@ -17,49 +18,62 @@ impl ToTokens for Body { impl ToTokens for Component { fn to_tokens(&self, tokens: &mut TokenStream) { let name = &self.name; + use_absolute! { + core::convert::Into as Into, + core::default::Default as Default, + core::iter::IntoIterator as IntoIterator, + uwui::backend::TargetBackend as TargetBackend, + uwui::view::Construct as Construct, + uwui::view::ConstructGroup as ConstructGroup, + uwui::view::View as View, + uwui::view::__internal::IntoNode as IntoNode, + } + define_ident! { + construct_params + } + let params = match self.params.as_ref() { Some(p) => match p { + #[allow(non_snake_case)] Params::Anon(params) => quote! { - ::core::convert::Into::<<#name as crate::view::Construct>::Params>::into( - (#(::core::convert::Into::<_>::into(#params)),*,) + #Into::<<#name as #Construct>::Params>::into( + (#(#Into::<_>::into(#params)),*,) ) }, Params::Named(params) => quote! { { - let mut __uwui_params = < - <#name as crate::view::Construct>::Params as ::core::default::Default + let mut #construct_params = < + <#name as #Construct>::Params as #Default >::default(); - #(__uwui_params.#params)* - __uwui_params + #(#construct_params.#params)* + #construct_params } }, }, None => quote! { - <<#name as crate::view::Construct>::Params as ::core::default::Default>::default() + <<#name as #Construct>::Params as #Default>::default() }, }; let initializer = if let Some(body) = self.body.as_ref() { let body = body.elements.as_slice(); quote! { - < - #name as crate::view::ConstructGroup:: - >::construct_group( + < #name as #ConstructGroup::<#TargetBackend> >::construct_group( #params, - ::core::iter::IntoIterator::into_iter([ + #IntoIterator::into_iter([ #(#body),* ]) ) } } else { quote! { - <#name as crate::view::Construct>::construct(#params) + <#name as #Construct>::construct(#params) } }; let initializer = quote! { - crate::view::View::::new( - crate::view::__internal::IntoNode::::into_node( + #View::<#TargetBackend>::new( + #IntoNode::<#TargetBackend>::into_node( #initializer ) ) diff --git a/dsl/src/template/mod.rs b/dsl/src/template/mod.rs index ccc6bd9..c947025 100644 --- a/dsl/src/template/mod.rs +++ b/dsl/src/template/mod.rs @@ -1,3 +1,4 @@ +use crate::util::use_absolute; use proc_macro::TokenStream; use quote::quote; use syn::{parse_macro_input, token, Expr, Ident, Path}; @@ -7,12 +8,16 @@ mod parse; pub fn invoke(input: TokenStream) -> TokenStream { let input = parse_macro_input!(input as Body); + use_absolute! { + core::iter::FromIterator as FromIterator, + uwui::view::Template as Template, + uwui::view::View as View, + } + let output = quote! { match (()) { (()) => { - < - crate::view::Template as ::core::iter::FromIterator> - >::from_iter([ + < #Template as #FromIterator<#View<_>> >::from_iter([ #input ]) } diff --git a/dsl/src/util.rs b/dsl/src/util.rs index d9b5a1a..dbef52f 100644 --- a/dsl/src/util.rs +++ b/dsl/src/util.rs @@ -1,30 +1,92 @@ -use proc_macro::{Diagnostic, Level, Span}; -use proc_macro2::TokenStream; +use proc_macro2::{Ident, Span, TokenStream}; +use proc_macro_crate::{crate_name, FoundCrate}; +use quote::quote; +use syn::{parse::ParseStream, Error}; -pub trait Compile: Sized { - fn compile(self) -> TokenStream; +pub fn __make_absolute_path(name: &str, input: TokenStream) -> TokenStream { + match name { + // core and std are special because they don't appear in Cargo.toml + "core" => quote! { ::core #input }, + "std" => quote! { ::std #input }, + name => { + let prefix = match crate_name(name) { + Ok(found) => match found { + FoundCrate::Itself => quote! { crate }, + FoundCrate::Name(name) => { + let name = Ident::new(&name, Span::call_site()); + quote! { :: #name } + } + }, + Err(e) => { + // proc_macro_crate failed to do its thing, so we try our best to + // not break other people's builds. Assume we're being called + // from another crate and the crate's author didn't rename us. + eprintln!("uwui_dsl: proc_macro_crate failed to resolve crate {name}, expect macro bugs!"); + eprintln!("uwui_dsl: proc_macro_crate has this to say: \"{e}\""); + let name = Ident::new(name, Span::call_site()); + quote! { :: #name } + } + }; + quote! { #prefix #input } + } + } } -pub fn emit_err(span: impl Into>, msg: impl Into) { - emit_diagnostic(span.into(), Level::Error, msg) +macro_rules! absolute_path { + ($crate_name:ident $(::$segment:ident)*) => { + $crate::util::__make_absolute_path( + stringify!($crate_name), + ::quote::quote!($(::$segment)*) + ) + }; } +pub(crate) use absolute_path; -pub fn emit_warning(span: impl Into>, msg: impl Into) { - emit_diagnostic(span.into(), Level::Warning, msg) +macro_rules! use_absolute { + ($($crate_name:ident $(::$path:ident)* as $name:ident),* $(,)?) => { + $( + #[allow(non_snake_case)] + let $name = $crate::util::absolute_path!($crate_name $(::$path)*); + )* + } } +pub(crate) use use_absolute; + +const RESERVED_IDENT_PREFIX: &str = "__uwui_"; -pub fn emit_note(span: impl Into>, msg: impl Into) { - emit_diagnostic(span.into(), Level::Note, msg) +pub fn make_reserved(name: &str, span: impl Into>) -> Ident { + let span = span.into().unwrap_or_else(Span::call_site); + if name.starts_with(RESERVED_IDENT_PREFIX) { + Ident::new(name, span) + } else { + Ident::new(&format!("{RESERVED_IDENT_PREFIX}{name}"), span) + } } -pub fn emit_help(span: impl Into>, msg: impl Into) { - emit_diagnostic(span.into(), Level::Help, msg) +macro_rules! define_ident { + ($($name:ident),* $(,)?) => { + $( + #[allow(non_snake_case)] + let $name = $crate::util::make_reserved(stringify!($name), None); + )* + } } +pub(crate) use define_ident; -fn emit_diagnostic(span: Option, level: Level, msg: impl Into) { - let diagnostic = match span { - Some(span) => Diagnostic::spanned(span, level, msg), - None => Diagnostic::new(level, msg), - }; - diagnostic.emit(); +/// Recursively walk through a `ParseStream` and examine all identifiers. +/// Emit an error for any identifiers that start with [`RESERVED_IDENT_PREFIX`] +/// (`__uwui_`). +/// +/// TODO: See if we can wrap ParseStream in a monad that does this check when +/// actually consuming the tokens +pub fn scan_for_reserved_idents(stream: ParseStream) -> syn::Result { + let fork = stream.fork(); + match scan_buf_for_ident_prefix(&fork, RESERVED_IDENT_PREFIX) { + Some(error) => Err(error), + None => Ok(stream), + } +} + +fn scan_buf_for_ident_prefix(stream: ParseStream, prefix: &str) -> Option { + todo!() } diff --git a/src/lib.rs b/src/lib.rs index 781de7c..d545bdb 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -5,9 +5,8 @@ pub mod widget; pub use uwui_dsl::{template, CustomComponent, NativeComponent}; -use crate::view::Construct; use layout::Gravity; -use view::{GetTemplate, Template}; +use view::{Construct, GetTemplate, Template}; use widget::{Button, Text, VStack}; fn asdf() -> Template {