hill_vacuum_proc_macros/
lib.rs

1#![allow(clippy::single_match_else)]
2
3//=======================================================================//
4// IMPORTS
5//
6//=======================================================================//
7
8use std::{
9    fs::File,
10    io::{BufRead, BufReader}
11};
12
13use hill_vacuum_shared::{
14    continue_if_no_match,
15    match_or_panic,
16    return_if_no_match,
17    ManualItem,
18    NextValue,
19    TEXTURE_HEIGHT_RANGE
20};
21use proc_macro::{Ident, TokenStream, TokenTree};
22
23//=======================================================================//
24// FUNCTIONS
25//
26//=======================================================================//
27
28/// Checks whever `value` is a comma.
29/// # Panics
30/// Function panics if `value` is not a comma.
31#[inline]
32fn is_comma(value: TokenTree)
33{
34    assert!(match_or_panic!(value, TokenTree::Punct(p), p).as_char() == ',');
35}
36
37//=======================================================================//
38
39/// Executes `f` for each Ident contained in `group`'s stream.
40/// # Panics
41/// Panics if `group` is not a `TokenTree::Group(_)`.
42fn for_each_ident_in_group<F: FnMut(Ident)>(group: TokenTree, mut f: F)
43{
44    for ident in match_or_panic!(group, TokenTree::Group(g), g)
45        .stream()
46        .into_iter()
47        .filter_map(|item| return_if_no_match!(item, TokenTree::Ident(ident), Some(ident), None))
48    {
49        f(ident);
50    }
51}
52
53//=======================================================================//
54
55/// Extracts the name of an enum for `iter`.
56/// # Panics
57/// Panics if `iter` does not belong to an enum.
58#[inline]
59#[must_use]
60fn enum_ident(iter: &mut impl Iterator<Item = TokenTree>) -> Ident
61{
62    for item in iter.by_ref()
63    {
64        let ident = continue_if_no_match!(item, TokenTree::Ident(ident), ident);
65
66        if &ident.to_string() == "enum"
67        {
68            return match_or_panic!(iter.next_value(), TokenTree::Ident(i), i);
69        }
70    }
71
72    panic!();
73}
74
75//=======================================================================//
76
77/// Implements a constant representing the size of the `input` enum.
78
79#[proc_macro_derive(EnumSize)]
80#[allow(clippy::missing_panics_doc)]
81#[must_use]
82pub fn enum_size(input: TokenStream) -> TokenStream
83{
84    let mut iter = input.into_iter();
85    format!(
86        "impl {} {{ pub const SIZE: usize = {}; }}",
87        enum_ident(&mut iter),
88        enum_len(iter)
89    )
90    .parse()
91    .unwrap()
92}
93
94//=======================================================================//
95
96/// Returns the amount of elements in an enum.
97#[allow(clippy::missing_panics_doc)]
98#[inline]
99#[must_use]
100fn enum_len(mut iter: impl Iterator<Item = TokenTree>) -> usize
101{
102    let mut i = 0;
103    for_each_ident_in_group(iter.next_value(), |_| i += 1);
104    i
105}
106
107//=======================================================================//
108
109/// Implements From `usize` for a plain enum.
110/// # Panics
111/// Panics if `input` does not belong to an enum.
112#[proc_macro_derive(EnumFromUsize)]
113#[must_use]
114pub fn enum_from_usize(input: TokenStream) -> TokenStream
115{
116    let mut iter = input.into_iter();
117    let enum_ident = enum_ident(&mut iter).to_string();
118
119    let mut from_impl = format!(
120        "impl From<usize> for {enum_ident}
121        {{
122            #[inline]
123            #[must_use] fn from(value: usize) -> Self
124            {{
125                match value
126                {{
127        "
128    );
129
130    let mut i = 0;
131
132    for_each_ident_in_group(iter.next_value(), |ident| {
133        from_impl.push_str(&format!("{i} => {enum_ident}::{ident},\n"));
134        i += 1;
135    });
136
137    from_impl.push_str("_ => unreachable!() } } }");
138    from_impl.parse().unwrap()
139}
140
141//=======================================================================//
142
143/// Implements a method that returns an iterator to the values of a plain enum.
144#[proc_macro_derive(EnumIter)]
145#[allow(clippy::missing_panics_doc)]
146#[must_use]
147pub fn enum_iter(input: TokenStream) -> TokenStream
148{
149    let mut iter = input.into_iter();
150    let enum_ident = enum_ident(&mut iter).to_string();
151    let enum_len = enum_len(iter.clone());
152    let mut enum_match = String::new();
153
154    let mut i = 0;
155    for_each_ident_in_group(iter.next_value(), |ident| {
156        enum_match.push_str(&format!("{i} => Some({enum_ident}::{ident}),\n"));
157        i += 1;
158    });
159
160    enum_match.push_str("_ => None");
161
162    format!(
163        "
164        impl {enum_ident}
165        {{
166            #[inline]
167            pub fn iter() -> impl ExactSizeIterator<Item = Self>
168            {{
169                struct EnumIterator(usize, usize);
170
171                impl ExactSizeIterator for EnumIterator
172                {{
173                    #[inline]
174                    #[must_use]
175                    fn len(&self) -> usize {{ self.1 - self.0 }}
176                }}
177
178                impl Iterator for EnumIterator
179                {{
180                    type Item = {enum_ident};
181
182                    #[inline]
183                    fn next(&mut self) -> Option<Self::Item>
184                    {{
185                        let value = match self.0
186                        {{
187                            {enum_match}
188                        }};
189
190                        self.0 += 1;
191                        value
192                    }}
193                }}
194
195                EnumIterator(0, {enum_len})
196            }}
197        }}
198        "
199    )
200    .parse()
201    .unwrap()
202}
203
204//=======================================================================//
205
206/// Generates an array of static [`str`] with name, size, and prefix defined in `stream`.
207/// # Examples
208/// ```
209/// str_array(ARRAY, 4, i_);
210/// // Equivalent to
211/// const ARRAY: [&'static str; 4] = ["i_0", "i_1", "i_2", "i_3"];
212/// ```
213/// # Panics
214/// Panics if `input` is not properly formatted.
215#[proc_macro]
216pub fn str_array(input: TokenStream) -> TokenStream
217{
218    let mut iter = input.into_iter();
219
220    let ident = iter.next_value().to_string();
221    is_comma(iter.next_value());
222
223    let amount = iter.next_value().to_string().parse::<u16>().unwrap();
224
225    let prefix = if let Some(v) = iter.next()
226    {
227        is_comma(v);
228        let v = iter.next_value();
229        assert!(iter.next().is_none());
230        v.to_string()
231    }
232    else
233    {
234        String::new()
235    };
236
237    let mut result = format!("const {ident}: [&'static str; {amount}] = [");
238
239    for i in 0..amount
240    {
241        result.push_str(&format!("\"{prefix}{i}\", "));
242    }
243
244    result.push_str("];");
245    result.parse().unwrap()
246}
247
248//=======================================================================//
249
250/// Generates the built-in manual from some of the markdown files in the `docs` directory.
251#[allow(clippy::missing_panics_doc)]
252#[proc_macro]
253pub fn generate_manual(_: TokenStream) -> TokenStream
254{
255    const SHOW_EXPLANATION: &str = "
256    use crate::map::editor::state::{ui::{Tool, SubTool}, core::tool::ToolInterface};
257
258    #[inline]
259    fn show_explanation<F: FnOnce(&mut egui::Ui)>(ui: &mut egui::Ui, left: F, explanation: &str)
260    {
261        ui.horizontal_wrapped(|ui| {
262            egui_extras::StripBuilder::new(ui)
263                .size(egui_extras::Size::exact(250f32))
264                .size(egui_extras::Size::remainder())
265                .horizontal(|mut strip| {
266                    strip.cell(|ui| {
267                        left(ui);
268                    });
269
270                    strip.cell(|ui| {
271                        ui.label(explanation);
272                    });
273                });
274        });
275    }";
276
277    let path = std::env::current_dir().unwrap();
278    let path = path.to_str().unwrap();
279
280    let body = hill_vacuum_shared::process_docs(
281        |string| {
282            string.push_str("ui.collapsing(\n");
283        },
284        |string, name, item| {
285            match item
286            {
287                ManualItem::Regular =>
288                {
289                    string.push('\"');
290                    string.push_str(&name.to_ascii_uppercase());
291                    string.push_str("\",\n");
292                    string.push_str("|ui| {\nui.vertical(|ui| {\n");
293                },
294                ManualItem::Tool =>
295                {
296                    let mut chars = name.chars();
297                    let mut tool = chars.next_value().to_ascii_uppercase().to_string();
298
299                    while let Some(mut c) = chars.next()
300                    {
301                        if c == ' '
302                        {
303                            c = chars.next_value().to_ascii_uppercase();
304                        }
305
306                        tool.push(c);
307                    }
308
309                    string.push_str(&format!(
310                        "Tool::{tool}.header(),\n|ui| {{\nui.vertical(|ui| \
311                         {{\ntools_buttons.image(ui, Tool::{tool});\n"
312                    ));
313                },
314                ManualItem::Texture => unreachable!()
315            };
316        },
317        |string, name, file, item| {
318            let processed = file
319                .trim()
320                .replace("### ", "")
321                .replace("```ini", "")
322                .replace('\"', "\\\"")
323                .replace("   ", "")
324                .replace('`', "");
325
326            match item
327            {
328                ManualItem::Regular =>
329                {
330                    let mut lines = processed.lines();
331                    let command = lines.next_value();
332                    let mut exp = String::new();
333
334                    for line in lines
335                    {
336                        exp.push_str(line);
337                        exp.push('\n');
338                    }
339
340                    exp.pop();
341                    string.push_str(&format!(
342                        "show_explanation(ui, |ui| {{ ui.label(\"{command}\"); }}, \"{exp}\");\n"
343                    ));
344                },
345                ManualItem::Tool =>
346                {
347                    let mut chars = name.chars();
348                    let mut subtool = chars.next_value().to_ascii_uppercase().to_string();
349
350                    while let Some(mut c) = chars.next()
351                    {
352                        if c == '_'
353                        {
354                            c = chars.next_value().to_ascii_uppercase();
355                        }
356
357                        subtool.push(c);
358                    }
359
360                    let mut lines = processed.lines();
361                    let mut exp = lines.next_value().to_string();
362                    exp.push_str(" (");
363                    exp.push_str(
364                        &std::fs::read_to_string(format!("{path}/docs/subtools binds/{name}.md"))
365                            .unwrap()
366                    );
367                    exp.push_str(")\n");
368
369                    for line in lines
370                    {
371                        exp.push_str(line);
372                        exp.push('\n');
373                    }
374
375                    exp.pop();
376
377                    string.push_str(&format!(
378                        "show_explanation(ui, |ui| {{ tools_buttons.image(ui, \
379                         SubTool::{subtool}); }}, \"{exp}\");\n"
380                    ));
381                },
382                ManualItem::Texture =>
383                {
384                    string.push_str(&format!(
385                        "show_explanation(ui, |ui| {{ ui.label(\"TEXTURE EDITING\"); }}, \
386                         \"{processed}\");\n"
387                    ));
388                }
389            };
390        },
391        |string, last| {
392            string.push_str("})\n});\n\n");
393
394            if !last
395            {
396                string.push_str("ui.separator();\n\n");
397            }
398        }
399    );
400
401    format!("{SHOW_EXPLANATION}\n\n{body}").parse().unwrap()
402}
403
404//=======================================================================//
405
406/// Generates a function which associates a f32 value representing a certain height to each provided
407/// enum match arm.
408#[allow(clippy::missing_panics_doc)]
409#[proc_macro]
410pub fn color_enum(stream: TokenStream) -> TokenStream
411{
412    #[inline]
413    fn is_column<I: Iterator<Item = TokenTree>>(stream: &mut I)
414    {
415        assert!(match_or_panic!(stream.next_value(), TokenTree::Punct(p), p).as_char() == ':');
416    }
417
418    #[inline]
419    fn push_key_and_label(item: &str, label_func: &mut String, key_func: &mut String)
420    {
421        let mut chars = item.chars();
422        let c = chars.next_value();
423        key_func.push_str(&format!("Self::{item} => \"{}", c.to_ascii_lowercase()));
424        label_func.push_str(&format!("Self::{item} => \"{c}"));
425
426        for c in chars
427        {
428            if c.is_uppercase()
429            {
430                key_func.push('_');
431                key_func.push(c.to_ascii_lowercase());
432
433                label_func.push(' ');
434                label_func.push(c);
435
436                continue;
437            }
438
439            for func in [&mut *key_func, &mut *label_func]
440            {
441                func.push(c);
442            }
443        }
444
445        for func in [key_func, label_func]
446        {
447            func.push_str("\",\n");
448        }
449    }
450
451    #[inline]
452    #[must_use]
453    fn extract<I: Iterator<Item = TokenTree>>(
454        stream: &mut I,
455        end_tag: &str,
456        label_func: &mut String,
457        key_func: &mut String
458    ) -> Vec<String>
459    {
460        let mut vec: Vec<String> = Vec::new();
461
462        while let Some(item) = stream.next()
463        {
464            if let TokenTree::Punct(p) = item
465            {
466                let c = p.as_char();
467
468                match c
469                {
470                    ',' => (),
471                    '|' =>
472                    {
473                        let last = vec.last_mut().unwrap();
474                        let item = stream.next_value().to_string();
475                        push_key_and_label(&item, label_func, key_func);
476                        last.push_str(&format!(" | Self::{item}"));
477                    },
478                    _ => panic!()
479                }
480
481                continue;
482            }
483
484            let item = item.to_string();
485
486            if item == end_tag
487            {
488                is_column(stream);
489                break;
490            }
491
492            push_key_and_label(&item, label_func, key_func);
493            vec.push(format!("Self::{item}"));
494        }
495
496        vec
497    }
498
499    #[inline]
500    #[must_use]
501    fn generate_height_func<'a, I: Iterator<Item = &'a str>>(
502        start: &str,
503        mut start_height: f32,
504        interval: f32,
505        iter: I
506    ) -> (String, f32)
507    {
508        let mut height_func = start.to_string();
509
510        for item in iter
511        {
512            height_func.push_str(&format!("{item} => {start_height}f32,\n"));
513            start_height += interval;
514        }
515
516        height_func.push_str("_ => panic!(\"Invalid color: {self:?}\")\n}\n}");
517        (height_func, start_height)
518    }
519
520    let textures_interval = f32::from(*TEXTURE_HEIGHT_RANGE.end());
521    let mut stream = stream.into_iter();
522
523    let mut key_func = "
524    /// The config file key relative to the drawn color associated with [`Color`].
525    #[inline]
526    #[must_use]
527    pub const fn config_file_key(self) -> &'static str
528    {
529        match self
530        {
531    "
532    .to_string();
533
534    let mut label_func = "
535    /// The text label representing [`Color`] in UI elements.
536    #[inline]
537    #[must_use]
538    pub const fn label(self) -> &'static str
539    {
540        match self
541        {
542    "
543    .to_string();
544
545    assert!(stream.next_value().to_string() == "clear");
546    is_column(&mut stream);
547    let clear = stream.next_value().to_string();
548    push_key_and_label(&clear, &mut label_func, &mut key_func);
549    is_comma(stream.next_value());
550
551    assert!(stream.next_value().to_string() == "extensions");
552    is_column(&mut stream);
553    let extensions = stream.next_value().to_string();
554    push_key_and_label(&extensions, &mut label_func, &mut key_func);
555    let extensions = format!("Self::{extensions}");
556    is_comma(stream.next_value());
557
558    assert!(stream.next_value().to_string() == "grid");
559    is_column(&mut stream);
560    let grid = extract(&mut stream, "entities", &mut label_func, &mut key_func);
561    let entities = extract(&mut stream, "ui", &mut label_func, &mut key_func);
562    let ui = extract(&mut stream, "", &mut label_func, &mut key_func);
563
564    for func in [&mut key_func, &mut label_func]
565    {
566        func.push_str("}\n}");
567    }
568
569    let (height_func, clip_height) = generate_height_func(
570        "
571    /// The height at which map elements colored with a certain [`Color`] should be drawn.
572    #[inline]
573    #[must_use]
574    pub fn entity_height(self) -> f32
575    {
576        match self
577        {",
578        1f32,
579        textures_interval + 1f32,
580        entities.iter().map(String::as_str)
581    );
582
583    let (line_height_func, thing_angle_height) = generate_height_func(
584        "
585    /// The draw height of the lines.
586    #[inline]
587    #[must_use]
588    pub fn line_height(self) -> f32
589    {
590        match self
591        {
592    ",
593        clip_height + 1f32,
594        1f32,
595        grid.iter()
596            .chain(Some(&extensions).iter().copied())
597            .chain(&entities)
598            .chain(&ui)
599            .map(String::as_str)
600    );
601
602    let (square_hgl_height_func, _) = generate_height_func(
603        "
604    /// The draw height of the square highlights.
605    #[inline]
606    #[must_use]
607    pub fn square_hgl_height(self) -> f32
608    {
609        match self
610        {
611    ",
612        thing_angle_height + 2f32,
613        1f32,
614        ui.iter().map(String::as_str)
615    );
616
617    format!(
618        "
619    {height_func}
620
621    /// The draw height of an untextured polygon.
622    #[inline]
623    #[must_use]
624    pub(in crate::map::drawer) fn polygon_height(self) -> f32 {{ self.entity_height() - 1f32 }}
625
626    /// The draw height of the clip overlay.
627    #[inline]
628    #[must_use]
629    pub(in crate::map::drawer) const fn clip_height() -> f32 {{ {clip_height}f32 }}
630
631    {line_height_func}
632
633    /// The draw height of the thing angle indicator.
634    #[inline]
635    #[must_use]
636    pub(in crate::map::drawer) fn thing_angle_indicator_height() -> [f32; 2]
637    {{
638        [
639            {thing_angle_height}f32,
640            {thing_angle_height}f32 + 1f32
641        ]
642    }}
643
644    {square_hgl_height_func}
645
646    {key_func}
647
648    {label_func}"
649    )
650    .parse()
651    .unwrap()
652}
653
654//=======================================================================//
655
656/// Generates the `Bind` enum plus the `config_file_key()` and `label()` methods.
657/// # Panics
658/// Panic if the file containing the `Tool` enum is not at the required location.
659#[proc_macro]
660pub fn bind_enum(input: TokenStream) -> TokenStream
661{
662    let mut binds = "{".to_string();
663    binds.push_str(&input.to_string());
664    binds.push(',');
665
666    let mut path = std::env::current_dir().unwrap();
667    path.push("src/map/editor/state/core/tool.rs");
668
669    let mut lines = BufReader::new(File::open(path).unwrap()).lines().map(Result::unwrap);
670    lines.find(|line| line.ends_with("enum Tool"));
671    lines.next();
672
673    for line in lines
674    {
675        binds.push_str(&line);
676        binds.push('\n');
677
678        if line.contains('}')
679        {
680            break;
681        }
682    }
683
684    let mut iter = binds.clone().parse::<TokenStream>().unwrap().into_iter();
685
686    let mut key_func = "
687    /// Returns the string key used in the config file associated with this `Bind`. 
688    #[inline]
689    #[must_use]
690    pub(in crate::config::controls) const fn config_file_key(self) -> &'static str
691    {
692        match self
693        {\n"
694    .to_string();
695
696    let mut label_func = "
697    /// Returns the text representing this `Bind` in UI elements.
698    #[inline]
699    #[must_use]
700    pub const fn label(self) -> &'static str
701    {
702        match self
703        {\n"
704    .to_string();
705
706    for item in match_or_panic!(iter.next_value(), TokenTree::Group(g), g).stream()
707    {
708        if let TokenTree::Ident(ident) = item
709        {
710            let ident = ident.to_string();
711            let mut chars = ident.chars();
712            let mut value = chars.next_value().to_string();
713
714            for ch in chars
715            {
716                if ch.is_ascii_uppercase()
717                {
718                    value.push(' ');
719                }
720
721                value.push(ch);
722            }
723
724            label_func.push_str(&format!("Self::{ident} => \"{value}\",\n"));
725
726            value = value.to_ascii_lowercase().replace(' ', "_");
727            key_func.push_str(&format!("Self::{ident} => \"{value}\",\n"));
728        }
729    }
730
731    for func in [&mut key_func, &mut label_func]
732    {
733        func.push_str("}\n}");
734    }
735
736    format!(
737        "
738        /// The binds associated with the editor actions.
739        #[derive(Clone, Copy, Debug, PartialEq, EnumIter, EnumSize)]
740        pub enum Bind
741        {binds}
742
743        impl Bind
744        {{
745            {key_func}
746
747            {label_func}
748        }}"
749    )
750    .parse()
751    .unwrap()
752}
753
754//=======================================================================//
755
756/// Generates the `header()` and `icon_file_name()` methods for the `Tool` and `SubTool` enums.
757#[inline]
758#[must_use]
759fn tools_common(stream: TokenStream, id: &str) -> [String; 2]
760{
761    let mut header_func = "
762        /// The uppercase tool name.
763        #[inline]
764        #[must_use]
765        fn header(self) -> &'static str
766        {
767            match self
768            {\n"
769    .to_string();
770
771    let mut icon_file_name_func = "
772        /// The file name of the associated icon.
773        #[inline]
774        #[must_use]
775        fn icon_file_name(self) -> &'static str
776        {
777            match self
778            {\n"
779    .to_string();
780
781    for item in stream
782    {
783        let ident = continue_if_no_match!(item, TokenTree::Ident(ident), ident).to_string();
784        let mut chars = ident.chars();
785
786        // Label.
787        let mut value = chars.next_value().to_string();
788
789        for ch in chars
790        {
791            if ch.is_ascii_uppercase()
792            {
793                value.push(' ');
794            }
795
796            value.push(ch);
797        }
798
799        // Header.
800        value = value.to_ascii_uppercase();
801        header_func.push_str(&format!("Self::{ident} => \"{value} {id}\",\n"));
802
803        // Icon paths.
804        value = value.to_ascii_lowercase().replace(' ', "_");
805        icon_file_name_func.push_str(&format!("Self::{ident} => \"{value}.png\",\n"));
806    }
807
808    for func in [&mut icon_file_name_func, &mut header_func]
809    {
810        func.push_str("}\n}");
811    }
812
813    [header_func, icon_file_name_func]
814}
815
816//=======================================================================//
817
818/// Implements the vast majority of the methods of the `Tool` enum.
819/// # Panics
820/// Panics if `input` does not belong to the `Tool` enum.
821#[proc_macro_derive(ToolEnum)]
822#[must_use]
823pub fn declare_tool_enum(input: TokenStream) -> TokenStream
824{
825    let mut iter = input.into_iter();
826    assert!(enum_ident(&mut iter).to_string() == "Tool");
827    let group = match_or_panic!(iter.next_value(), TokenTree::Group(g), g);
828    let [header_func, icon_file_name_func] = tools_common(group.stream(), "TOOL");
829
830    let mut bind_func = "#[inline]
831        pub const fn bind(self) -> Bind
832        {
833            match self
834            {\n"
835    .to_string();
836
837    let mut label_func = "#[inline]
838        fn label(self) -> &'static str
839        {
840            match self
841            {\n"
842    .to_string();
843
844    for item in group.stream()
845    {
846        let ident = continue_if_no_match!(item, TokenTree::Ident(ident), ident).to_string();
847        let mut chars = ident.chars();
848
849        // Bind
850        bind_func.push_str(&format!("Self::{ident} => Bind::{ident},\n"));
851
852        // Label.
853        let mut value = chars.next_value().to_string();
854
855        for ch in chars
856        {
857            if ch.is_ascii_uppercase()
858            {
859                value.push(' ');
860            }
861
862            value.push(ch);
863        }
864
865        label_func.push_str(&format!("Self::{ident} => \"{value}\",\n"));
866    }
867
868    for func in [&mut label_func, &mut bind_func]
869    {
870        func.push_str("}\n}");
871    }
872
873    format!(
874        "
875        impl ToolInterface for Tool
876        {{
877            {label_func}
878
879            {header_func}
880
881            {icon_file_name_func}
882
883            #[inline]
884            fn tooltip_label(self, binds: &BindsKeyCodes) -> String
885            {{
886                format!(\"{{}} ({{}})\", self.label(), self.keycode_str(binds))
887            }}
888
889            #[inline]
890            fn change_conditions_met(self, change_conditions: &ChangeConditions) -> bool
891            {{
892                self.conditions_met(change_conditions)
893            }}
894
895            #[inline]
896            fn subtool(self) -> bool {{ false }}
897
898            #[inline]
899            fn index(self) -> usize {{ self as usize }}
900        }}
901
902        impl Tool
903        {{
904            {bind_func}
905        }}"
906    )
907    .parse()
908    .unwrap()
909}
910
911//=======================================================================//
912
913/// Implements the vast majority of the methods of the `SubTool` enum.
914/// # Panics
915/// Panics if `input` does not belong to the `SubTool` enum.
916#[proc_macro_derive(SubToolEnum)]
917#[allow(clippy::too_many_lines)]
918#[must_use]
919pub fn subtool_enum(input: TokenStream) -> TokenStream
920{
921    let mut iter = input.into_iter();
922    assert!(enum_ident(&mut iter).to_string() == "SubTool");
923    let group = match_or_panic!(iter.next_value(), TokenTree::Group(g), g);
924    let [header_func, icon_file_name_func] = tools_common(group.stream(), "SUBTOOL");
925
926    let mut label_func = "
927        #[inline]
928        fn label(self) -> &'static str
929        {
930            match self
931            {\n"
932    .to_string();
933
934    let mut bind_func = "
935        #[inline]
936        fn bind(self) -> &'static str
937        {
938            match self
939            {\n"
940    .to_string();
941
942    let mut tool_func = "
943        #[inline]
944        const fn tool(self) -> Tool
945        {
946            match self
947            {\n"
948    .to_string();
949
950    let mut tool = String::new();
951    let mut label = String::new();
952    let mut bind = String::new();
953    let mut subtool_binds_path = std::env::current_dir().unwrap();
954    subtool_binds_path.push("docs");
955    subtool_binds_path.push("subtools binds");
956
957    for item in group.stream()
958    {
959        let ident = continue_if_no_match!(item, TokenTree::Ident(ident), ident).to_string();
960        let mut chars = ident.chars();
961        let first = chars.next_value();
962
963        for s in [&mut tool, &mut label, &mut bind]
964        {
965            s.clear();
966        }
967
968        tool.push(first);
969        bind.push(first.to_ascii_lowercase());
970
971        for ch in chars.by_ref()
972        {
973            if ch.is_ascii_uppercase()
974            {
975                label.push(ch);
976
977                bind.push('_');
978                bind.push(ch.to_ascii_lowercase());
979                break;
980            }
981
982            tool.push(ch);
983            bind.push(ch);
984        }
985
986        for ch in chars
987        {
988            if ch.is_ascii_uppercase()
989            {
990                label.push(' ');
991                bind.push('_');
992            }
993
994            label.push(ch);
995            bind.push(ch.to_ascii_lowercase());
996        }
997
998        subtool_binds_path.push(format!("{bind}.md"));
999
1000        label_func.push_str(&format!("Self::{ident} => \"{label}\",\n"));
1001        tool_func.push_str(&format!("Self::{ident} => Tool::{tool},\n"));
1002        bind_func.push_str(&format!("Self::{ident} => include_str!({:?}),\n", subtool_binds_path));
1003
1004        subtool_binds_path.pop();
1005    }
1006
1007    for func in [&mut label_func, &mut tool_func, &mut bind_func]
1008    {
1009        func.push_str("}\n}");
1010    }
1011
1012    format!(
1013        "
1014        impl ToolInterface for SubTool
1015        {{
1016            {label_func}
1017
1018            {header_func}
1019
1020            {icon_file_name_func}
1021
1022            #[inline]
1023            fn tooltip_label(self, _: &BindsKeyCodes) -> String
1024            {{
1025                format!(\"{{}} ({{}})\", self.label(), self.bind())
1026            }}
1027
1028            #[inline]
1029            fn change_conditions_met(self, change_conditions: &ChangeConditions) -> bool
1030            {{
1031                self.conditions_met(change_conditions)
1032            }}
1033
1034            #[inline]
1035            fn subtool(self) -> bool {{ true }}
1036
1037            #[inline]
1038            fn index(self) -> usize {{ self as usize }}
1039        }}
1040
1041        impl SubTool
1042        {{
1043            {tool_func}
1044
1045            {bind_func}
1046        }}
1047        "
1048    )
1049    .parse()
1050    .unwrap()
1051}
1052
1053//=======================================================================//
1054
1055/// Generates the function calls to store the embedded assets from the file names in the
1056/// `src/embedded_assets/` folder.
1057/// # Panics
1058/// Panics if the required folder cannot be found.
1059#[allow(clippy::missing_panics_doc)]
1060#[proc_macro]
1061pub fn embedded_assets(_: TokenStream) -> TokenStream
1062{
1063    let mut path = std::env::current_dir().unwrap();
1064    path.push("src/embedded_assets/");
1065
1066    // Get all the files.
1067    let directory = std::fs::read_dir(path).unwrap();
1068    let mut values = String::new();
1069    values.push_str("use bevy::asset::embedded_asset;\n");
1070
1071    for file in directory.into_iter().map(|p| p.unwrap().file_name())
1072    {
1073        let file_name = file.to_str().unwrap();
1074        values.push_str(&format!("bevy::asset::embedded_asset!(app, \"{file_name}\");\n"));
1075    }
1076
1077    values.parse().unwrap()
1078}
1079
1080//=======================================================================//
1081
1082/// Generates the vector of the indexes used to triangulate the meshes.
1083#[allow(clippy::missing_panics_doc)]
1084#[proc_macro]
1085pub fn meshes_indexes(stream: TokenStream) -> TokenStream
1086{
1087    let mut stream = stream.into_iter();
1088    let ident = stream.next_value().to_string();
1089    is_comma(stream.next_value());
1090    let size = stream.next_value().to_string().parse::<u16>().unwrap();
1091    assert!(stream.next().is_none());
1092
1093    let mut indexes = format!(
1094        "
1095    const MAX_MESH_TRIANGLES: usize = {size};
1096    static mut {ident}: *mut [u16] = &mut [\n"
1097    );
1098
1099    for i in 1..=size
1100    {
1101        indexes.push_str(&format!("0u16, {i}, {i} + 1,\n"));
1102    }
1103
1104    indexes.push_str("];");
1105    indexes.parse().unwrap()
1106}
1107
1108//=======================================================================//
1109
1110/// Generates the sin, cos, tan, lookup table.
1111#[allow(clippy::cast_precision_loss)]
1112#[allow(clippy::missing_panics_doc)]
1113#[proc_macro]
1114pub fn sin_cos_tan_array(_: TokenStream) -> TokenStream
1115{
1116    let mut array = "
1117    #[allow(clippy::approx_constant)]
1118    #[allow(clippy::unreadable_literal)]
1119    const SIN_COS_TAN_LOOKUP: [(f32, f32, f32); 361] = [\n"
1120        .to_string();
1121
1122    for a in 0..=360
1123    {
1124        let a = (a as f32).to_radians();
1125        array.push_str(&format!("({}f32, {}f32, {}f32),\n", a.sin(), a.cos(), a.tan()));
1126    }
1127
1128    array.push_str("];");
1129    array.parse().unwrap()
1130}