1#![allow(internal_features)]
2#![cfg_attr(any(docsrs, docsrs_dep), feature(rustdoc_internals))]
3#![doc = include_str!("../README.md")]
4#![cfg_attr(doc, deny(missing_docs))]
5
6extern crate proc_macro;
7
8use hephae_macros::{
9 Manifest,
10 proc_macro2::TokenStream,
11 quote::{ToTokens, quote, quote_spanned},
12 syn,
13 syn::{
14 Error, Ident, Token, Type, parenthesized,
15 parse::{Parse, ParseStream},
16 spanned::Spanned,
17 },
18};
19
20struct Syntax {
21 #[cfg(feature = "atlas")]
22 atlas: Option<TokenStream>,
23 #[cfg(feature = "locale")]
24 locale: Option<TokenStream>,
25 render: Option<TokenStream>,
26 #[cfg(feature = "text")]
27 text: Option<TokenStream>,
28 #[cfg(feature = "ui")]
29 ui: Option<TokenStream>,
30}
31
32impl Parse for Syntax {
33 fn parse(input: ParseStream) -> syn::Result<Self> {
34 fn no_data(id: &str, input: ParseStream) -> syn::Result<()> {
35 if input.peek(Token![:]) {
36 Err(input.error(format!("`{id}` doesn't accept type arguments")))
37 } else {
38 Ok(())
39 }
40 }
41
42 fn no_redefine(
43 id: &str,
44 dst: &mut Option<TokenStream>,
45 src: impl FnOnce() -> syn::Result<TokenStream>,
46 tokens: impl ToTokens,
47 ) -> syn::Result<()> {
48 match dst {
49 Some(..) => Err(Error::new_spanned(tokens, format!("`{id}` defined multiple times"))),
50 None => {
51 *dst = Some(src()?);
52 Ok(())
53 }
54 }
55 }
56
57 #[allow(unused)]
58 fn unsupported(id: &str, tokens: impl ToTokens) -> Error {
59 Error::new_spanned(
60 tokens,
61 format!("`{id}` unsupported; have you enabled the feature in `Cargo.toml`?"),
62 )
63 }
64
65 let manifest = Manifest::get();
66
67 #[cfg(feature = "atlas")]
68 let mut atlas = None;
69 #[cfg(feature = "locale")]
70 let mut locale = None;
71 let mut render = None;
72 #[cfg(feature = "text")]
73 let mut text = None;
74 #[cfg(feature = "ui")]
75 let mut ui = None;
76
77 while !input.is_empty() {
78 if let Some(token) = input.parse::<Option<Token![..]>>()? {
79 if !input.is_empty() {
80 return Err(input.error("no more tokens are allowed after `..`"))
81 }
82
83 #[cfg(feature = "atlas")]
84 if let (true, Ok(atlas_crate)) = (atlas.is_none(), manifest.resolve("hephae", "atlas", token)) {
85 atlas = Some(quote_spanned! { token.span() => #atlas_crate::AtlasPlugin::default() })
86 }
87 #[cfg(feature = "locale")]
88 if let (true, Ok(locale_crate)) = (locale.is_none(), manifest.resolve("hephae", "locale", token)) {
89 locale = Some(quote_spanned! { token.span() => #locale_crate::LocalePlugin::<(), ()>::default() })
90 }
91 if let (true, Ok(render_crate)) = (render.is_none(), manifest.resolve("hephae", "render", token)) {
92 render = Some(quote_spanned! { token.span() => #render_crate::RendererPlugin::<(), ()>::default() })
93 }
94 #[cfg(feature = "text")]
95 if let (true, Ok(text_crate)) = (text.is_none(), manifest.resolve("hephae", "text", token)) {
96 text = Some(quote_spanned! { token.span() => #text_crate::TextPlugin::default() })
97 }
98 #[cfg(feature = "ui")]
99 if let (true, Ok(ui_crate)) = (ui.is_none(), manifest.resolve("hephae", "ui", token)) {
100 ui = Some(quote_spanned! { token.span() => #ui_crate::UiPlugin::<(), ()>::default() })
101 }
102
103 break
104 } else {
105 let id = input.parse::<Ident>()?;
106 match &*id.to_string() {
107 #[cfg(feature = "atlas")]
108 "atlas" => {
109 no_data("atlas", input)?;
110 no_redefine(
111 "atlas",
112 &mut atlas,
113 || {
114 let atlas_crate = manifest.resolve("hephae", "atlas", &id)?;
115 Ok(quote_spanned! { id.span() => #atlas_crate::AtlasPlugin::default() })
116 },
117 &id,
118 )?
119 }
120 #[cfg(not(feature = "atlas"))]
121 "atlas" => return Err(unsupported("atlas", id)),
122 #[cfg(feature = "locale")]
123 "locale" => no_redefine(
124 "locale",
125 &mut locale,
126 || {
127 let locale_crate = manifest.resolve("hephae", "locale", &id)?;
128 if input.parse::<Option<Token![:]>>()?.is_some() {
129 let data;
130 parenthesized!(data in input);
131
132 let arg = data.parse::<Type>()?;
133 data.parse::<Token![,]>()?;
134 let target = data.parse::<Type>()?;
135 data.parse::<Option<Token![,]>>()?;
136
137 if !data.is_empty() {
138 return Err(data.error("expected end of tuple"))
139 }
140
141 Ok(quote_spanned! { id.span() => #locale_crate::LocalePlugin::<#arg, #target>::default() })
142 } else {
143 Ok(quote_spanned! { id.span() => #locale_crate::LocalePlugin::<(), ()>::default() })
144 }
145 },
146 &id,
147 )?,
148 #[cfg(not(feature = "locale"))]
149 "locale" => return Err(unsupported("locale", id)),
150 "render" => no_redefine(
151 "render",
152 &mut render,
153 || {
154 let render_crate = manifest.resolve("hephae", "render", &id)?;
155 if input.parse::<Option<Token![:]>>()?.is_some() {
156 let data;
157 parenthesized!(data in input);
158
159 let vertex = data.parse::<Type>()?;
160 data.parse::<Token![,]>()?;
161 let drawer = data.parse::<Type>()?;
162 data.parse::<Option<Token![,]>>()?;
163
164 if !data.is_empty() {
165 return Err(data.error("expected end of tuple"))
166 }
167
168 Ok(
169 quote_spanned! { id.span() => #render_crate::RendererPlugin::<#vertex, #drawer>::default() },
170 )
171 } else {
172 Ok(quote_spanned! { id.span() => #render_crate::RendererPlugin::<(), ()>::default() })
173 }
174 },
175 &id,
176 )?,
177 #[cfg(feature = "text")]
178 "text" => {
179 no_data("text", input)?;
180 no_redefine(
181 "text",
182 &mut text,
183 || {
184 let text_crate = manifest.resolve("hephae", "text", &id)?;
185 Ok(quote_spanned! { id.span() => #text_crate::TextPlugin::default() })
186 },
187 &id,
188 )?
189 }
190 #[cfg(not(feature = "text"))]
191 "text" => return Err(unsupported("text", id)),
192 #[cfg(feature = "ui")]
193 "ui" => no_redefine(
194 "ui",
195 &mut ui,
196 || {
197 let ui_crate = manifest.resolve("hephae", "ui", &id)?;
198 if input.parse::<Option<Token![:]>>()?.is_some() {
199 let data;
200 parenthesized!(data in input);
201
202 let measure = data.parse::<Type>()?;
203 data.parse::<Token![,]>()?;
204 let root = data.parse::<Type>()?;
205 data.parse::<Option<Token![,]>>()?;
206
207 if !data.is_empty() {
208 return Err(data.error("expected end of tuple"))
209 }
210
211 Ok(quote_spanned! { id.span() => #ui_crate::UiPlugin::<#measure, #root>::default() })
212 } else {
213 Ok(quote_spanned! { id.span() => #ui_crate::UiPlugin::<(), ()>::default() })
214 }
215 },
216 &id,
217 )?,
218 #[cfg(not(feature = "ui"))]
219 "ui" => return Err(unsupported("ui", id)),
220 other => return Err(Error::new_spanned(id, format!("unknown plugin `{other}`"))),
221 }
222 }
223
224 if input.is_empty() {
225 break
226 } else {
227 input.parse::<Token![,]>()?;
228 }
229 }
230
231 Ok(Self {
232 #[cfg(feature = "atlas")]
233 atlas,
234 #[cfg(feature = "locale")]
235 locale,
236 render,
237 #[cfg(feature = "text")]
238 text,
239 #[cfg(feature = "ui")]
240 ui,
241 })
242 }
243}
244
245#[proc_macro]
268pub fn hephae(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
269 fn parse_inner(input: TokenStream) -> syn::Result<TokenStream> {
270 let span = input.span();
271 let Syntax {
272 atlas,
273 locale,
274 render,
275 text,
276 ui,
277 } = syn::parse2(input)?;
278
279 let mut plugins = Vec::with_capacity(5);
280
281 if let Some(render) = render {
282 plugins.push(render)
283 }
284 #[cfg(feature = "atlas")]
285 if let Some(atlas) = atlas {
286 plugins.push(atlas)
287 }
288 #[cfg(feature = "locale")]
289 if let Some(locale) = locale {
290 plugins.push(locale)
291 }
292 #[cfg(feature = "text")]
293 if let Some(text) = text {
294 plugins.push(text)
295 }
296 #[cfg(feature = "ui")]
297 if let Some(ui) = ui {
298 plugins.push(ui)
299 }
300
301 match &*plugins {
302 [] => Err(Error::new(
303 span,
304 "at least one plugin must be specified, or `..` use all defaults",
305 )),
306 [data] => Ok(quote! { #data }),
307 data => Ok(quote! { (#(#data),*) }),
308 }
309 }
310
311 parse_inner(input.into()).unwrap_or_else(Error::into_compile_error).into()
312}