aboutsummaryrefslogtreecommitdiff
path: root/mingling_macros/src/func/gen_program.rs
blob: 35e83525b1a51e9c37f521fe15042cc648b2eb0d (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
// Doc Not Optimize
use proc_macro::TokenStream;
use quote::quote;

/// Entry point for `gen_program!()`.
///
/// Generates the `Next` type alias, `Routable` impl for `ChainProcess`,
/// and delegates to `program_comp_gen!()`, `program_fallback_gen!()`,
/// and `program_final_gen!()`.
///
/// When the `comp` / `pathf` features are enabled, the expansion begins by
/// invoking `build_comp!()` / `build_pathf!()`, which run the build steps
/// (previously done in `build.rs`) as a compile-time side effect and expand
/// to nothing.
pub(crate) fn gen_program_impl(_input: TokenStream) -> TokenStream {
    #[cfg(feature = "comp")]
    let comp_gen = quote! {
        ::mingling::macros::program_comp_gen!();
    };

    #[cfg(not(feature = "comp"))]
    let comp_gen = quote! {};

    // `build_pathf!()` / `build_comp!()` are invoked at the very beginning of the
    // expansion: they run the build logic at compile time and expand to nothing.
    #[cfg(feature = "comp")]
    let comp_build = quote! {
        ::mingling::macros::build_comp!();
    };

    #[cfg(not(feature = "comp"))]
    let comp_build = quote! {};

    #[cfg(feature = "pathf")]
    let pathf_build = quote! {
        ::mingling::macros::build_pathf!();
    };

    #[cfg(not(feature = "pathf"))]
    let pathf_build = quote! {};

    // When pathf is enabled, load the type_using.rs generated by the build logic
    // and emit its use statements so types from submodules are in scope.
    #[cfg(feature = "pathf")]
    let pathf_uses: Vec<proc_macro2::TokenStream> = {
        // The `build_pathf!()` macro emitted above will (re-)run the analysis
        // during expansion, but the `use` statements are needed right now, so
        // make sure the mapping exists before reading it.
        if let Err(e) = crate::build::pathf::analyze_and_build_type_mapping() {
            let msg = format!("pathf: type mapping analysis failed: {e}");
            return syn::Error::new(proc_macro2::Span::call_site(), msg)
                .to_compile_error()
                .into();
        }
        let uses = load_pathf_uses();
        if uses.is_empty() {
            // The analyzer found nothing — emit a clear hint
            let hint: proc_macro2::TokenStream = syn::parse_quote! {
                compile_error!(
                    "pathf: no types were found by the analyzer.\n\
                     Make sure the `pathf` feature is enabled (which also enables\n\
                     the `build_pathf!()` macro) and that `gen_program!()` is called\n\
                     in a crate with a `src/` directory."
                );
            };
            vec![hint]
        } else {
            uses
        }
    };
    #[cfg(not(feature = "pathf"))]
    let pathf_uses: Vec<proc_macro2::TokenStream> = Vec::new();

    #[cfg(feature = "pathf")]
    let super_use = quote! {};

    #[cfg(not(feature = "pathf"))]
    let super_use = quote! {
      use super::*;
    };

    TokenStream::from(quote! {
        #comp_build
        #pathf_build
        pub use __this_program_impl::*;

        #[doc(hidden)]
        pub mod __this_program_impl {
            #super_use
            #(#pathf_uses)*

            /// Alias for the current program type `ThisProgram`
            pub type Next = ::mingling::ChainProcess<ThisProgram>;

            #[derive(::mingling::Grouped, ::mingling::Wrap, Default)]
            pub struct Entry(pub ::std::vec::Vec<::std::string::String>);

            impl ::mingling::Routable<ThisProgram> for ::mingling::ChainProcess<ThisProgram>
            {
                fn to_chain(self) -> ::mingling::ChainProcess<ThisProgram> {
                    match self {
                        ::mingling::ChainProcess::Ok((any, _)) => {
                            ::mingling::ChainProcess::Ok((any, mingling::NextProcess::Chain))
                        }
                        other => other,
                    }
                }

                fn to_render(self) -> ::mingling::ChainProcess<ThisProgram> {
                    match self {
                        ::mingling::ChainProcess::Ok((any, _)) => {
                            ::mingling::ChainProcess::Ok((any, mingling::NextProcess::Renderer))
                        }
                        other => other,
                    }
                }
            }

            #comp_gen
            ::mingling::macros::program_fallback_gen!();
            ::mingling::macros::program_final_gen!();
        }
    })
}

/// Loads `type_using.rs` generated by the pathf build logic and returns each
/// `use ...;` line as a token stream, ready to be emitted in the generated output.
#[cfg(feature = "pathf")]
fn load_pathf_uses() -> Vec<proc_macro2::TokenStream> {
    let Ok(output_dir) = crate::build::pathf::output_dir() else {
        return Vec::new();
    };
    let path = output_dir.join("type_using.rs");
    let Ok(content) = std::fs::read_to_string(&path) else {
        return Vec::new();
    };
    content
        .lines()
        .map(|line| line.trim().to_string())
        .filter(|line| !line.is_empty())
        .filter_map(|line| line.parse::<proc_macro2::TokenStream>().ok())
        .collect()
}