declarative_macros/
lib.rs

1/*
2 * SPDX-FileCopyrightText: 2025 Eduardo Javier Alvarado Aarón <eduardo.javier.alvarado.aaron@gmail.com>
3 *
4 * SPDX-License-Identifier: (Apache-2.0 or MIT)
5 */
6
7//! Generic DSL macros for easy view code manipulation.
8
9mod content;
10mod item;
11mod property;
12mod view;
13
14use proc_macro::TokenStream;
15use proc_macro2::{Span, TokenStream as TokenStream2};
16use quote::ToTokens;
17use std::{iter::Map, slice::Iter};
18use syn::{punctuated::Punctuated, visit_mut::VisitMut};
19
20#[proc_macro]
21/// See the "Usage" section of README.md or the [repository]((https://github.com/ejaa3/declarative))
22/// examples for details on how to use this macro.
23///
24/// ### Basic usage
25///
26/// ~~~
27/// use declarative_macros::block as view;
28/// 
29/// fn usage() -> String {
30///     view! {
31///         String::new() mut greeting {
32///             push_str: "Hello world!"
33///         }
34///     }
35///     greeting
36/// }
37/// ~~~
38pub fn block(stream: TokenStream) -> TokenStream {
39	if stream.is_empty() {
40		let error = syn::Error::new(Span::call_site(), "this view block has no content");
41		return TokenStream::from(error.into_compile_error())
42	}
43	
44	let mut structs = vec![];
45	let view::Streaming::Roots(roots) = syn::parse_macro_input!(stream) else { panic!() };
46	let (mut stream, bindings) = view::expand(&mut structs, roots, false);
47	
48	bindings_error(&mut stream, bindings.spans);
49	for strukt in structs { strukt.to_tokens(&mut stream) }
50	TokenStream::from(stream)
51}
52
53#[proc_macro_attribute]
54/// See the "Usage" section of README.md or the [repository](https://github.com/ejaa3/declarative)
55/// examples for details on how to use this macro.
56///
57/// ### Basic usage
58///
59/// ~~~
60/// use declarative_macros::view;
61/// 
62/// #[view {
63///     String::new() mut greeting {
64///         push_str: "Hello world!"
65///     }
66/// }]
67/// fn usage() -> String {
68///     expand_view_here! { }
69///     greeting
70/// }
71/// ~~~
72///
73/// ### Alternate usage
74///
75/// ~~~
76/// use declarative_macros::view;
77/// 
78/// #[view]
79/// mod example {
80///     view! {
81///         String::new() mut greeting {
82///             push_str: "Hello world!"
83///         }
84///     }
85///     fn usage() -> String {
86///         expand_view_here! { }
87///         greeting
88///     }
89/// }
90/// ~~~
91pub fn view(stream: TokenStream, code: TokenStream) -> TokenStream {
92	let item = &mut syn::parse_macro_input!(code);
93	let mut output = TokenStream2::new();
94	
95	let fill = |item: &mut _, output: &mut _, structs: &mut Vec<_>| {
96		if let syn::Item::Mod(mod_) = item {
97			if let Some((_, items)) = &mut mod_.content {
98				items.reserve(structs.len());
99				while let Some(item) = structs.pop() {
100					items.push(syn::Item::Struct(item))
101				} return
102			}
103		}
104		while let Some(item) = structs.pop() { item.to_tokens(output) }
105	};
106	
107	match syn::parse_macro_input!(stream) {
108		view::Streaming::Struct { vis, ident, generics, fields } => {
109			let errable = !matches!(vis, syn::Visibility::Inherited)
110				|| ident.is_some() || generics.lt_token.is_some() || !fields.is_empty();
111			
112			let structs = vec![syn::ItemStruct {
113				vis, fields: syn::Fields::Named(syn::FieldsNamed {
114					brace_token: Default::default(), named: fields
115				}),
116				attrs: vec![], struct_token: Default::default(),
117				ident: ident.unwrap_or(syn::Ident::new("_", Span::call_site())),
118				generics, semi_token: Default::default(),
119			}];
120			
121			let mut visitor = view::Visitor::Ok { structs, errable, deque: Default::default() };
122			visitor.visit_item_mut(item);
123			
124			match visitor {
125				view::Visitor::Ok { mut structs, mut deque, .. } => {
126					if deque.is_empty() {
127						let error = syn::Error::new(Span::call_site(), "if no view code is \
128							written as the content of this attribute, at least one view must \
129							be created with `view!` in the scope of a `mod`, `impl` or `trait`");
130						return TokenStream::from(error.into_compile_error())
131					}
132					while let Some((spans, stream, bindings)) = deque.pop_front() {
133						view::parse(item, &mut output, spans, stream, bindings);
134						fill(item, &mut output, &mut structs)
135					}
136				}
137				view::Visitor::Error(error) => return TokenStream::from(error.into_compile_error())
138			}
139		}
140		view::Streaming::Roots(roots) => {
141			let (range, mut structs) = (Range(Span::call_site(), Span::call_site()), vec![]);
142			let (stream, bindings) = view::expand(&mut structs, roots, false);
143			view::parse(item, &mut output, range, stream, bindings);
144			fill(item, &mut output, &mut structs)
145		}
146	}
147	
148	item.to_tokens(&mut output); TokenStream::from(output)
149}
150
151#[derive(Copy, Clone)]
152enum Assignee<'a> {
153	Field (Option<&'a Assignee<'a>>, &'a Punctuated<syn::Ident, syn::Token![.]>),
154	Ident (Option<&'a Assignee<'a>>, &'a syn::Ident),
155}
156
157#[derive(Copy, Clone)]
158enum Attributes<T: AsRef<[syn::Attribute]>> { Some(T), None(usize) }
159
160#[derive(Default)]
161struct Bindings { spans: Vec<Span>, stream: TokenStream2 }
162
163enum Construction {
164	BuilderPattern {
165		 left: TokenStream2,
166		right: TokenStream2,
167		 span: Span,
168		tilde: Option<syn::Token![~]>,
169	},
170	StructLiteral {
171		  left: TokenStream2,
172		    ty: TokenStream2,
173		fields: TokenStream2,
174		  span: Span,
175		 tilde: Option<syn::Token![~]>,
176	},
177}
178
179enum Path {
180	Type(syn::TypePath), Field {
181		access: Punctuated<syn::Ident, syn::Token![.]>,
182		  gens: Option<syn::AngleBracketedGenericArguments>,
183	}
184}
185
186struct Range(Span, Span);
187
188enum Visitor<'a, 'b> {
189	#[allow(clippy::type_complexity)]
190	Ok {  items: Option<&'a mut Map<Iter<'b, item::Item>, fn(&item::Item) -> Assignee>>,
191	   assignee: &'a mut Option<Assignee<'b>>,
192	placeholder: &'static str,
193	     stream: &'a mut TokenStream2 },
194	
195	Error(syn::Error)
196}
197
198fn bindings_error(stream: &mut TokenStream2, spans: Vec<Span>) {
199	for span in spans { stream.extend(syn::Error::new(span, BINDINGS_ERROR).to_compile_error()) }
200}
201
202fn extend_attributes(attrs: &mut Vec<syn::Attribute>, pattrs: &[syn::Attribute]) {
203	let current = std::mem::take(attrs);
204	attrs.reserve(pattrs.len() + current.len());
205	attrs.extend_from_slice(pattrs);
206	attrs.extend(current);
207}
208
209fn parse_unterminated<T, P>(input: syn::parse::ParseStream) -> syn::Result<Punctuated<T, P>>
210where T: syn::parse::Parse, P: syn::parse::Parse {
211	let mut punctuated = Punctuated::new();
212	punctuated.push_value(input.parse()?);
213	
214	while let Ok(punct) = input.parse() {
215		punctuated.push_punct(punct);
216		punctuated.push_value(input.parse()?)
217	}
218	Ok(punctuated)
219}
220
221const BINDINGS_ERROR: &str = "bindings must be consumed with the `bindings!` placeholder macro";
222
223struct ConstrError(&'static str);
224
225impl std::fmt::Display for ConstrError {
226	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
227		f.write_str(self.0)?;
228		f.write_str(" in the initial content of an item whose definition should be expanded in a builder pattern or a struct literal")
229	}
230}