Skip to main content

component_macro/
lib.rs

1//! Procedural macros for the component initialization system.
2//!
3//! Provides `#[init_component]` for registering kernel component init functions
4//! and `parse_components_toml!` for compile-time access to `Components.toml`.
5
6use proc_macro::TokenStream;
7use proc_macro2::Span;
8use quote::quote;
9use syn::{
10    parse::{Parse, ParseStream},
11    parse_macro_input,
12    punctuated::Punctuated,
13    Ident, ItemFn, LitInt, Token,
14};
15
16// =================================================================================
17// Argument parser
18// =================================================================================
19
20/// Parsed arguments for `#[init_component(stage, priority = N, depends_on = fn_or_list)]`.
21struct InitComponentArgs {
22    /// "Bootstrap" | "Kthread" | "Process"  (PascalCase ready for quote!)
23    stage: String,
24    /// Init priority: lower = earlier within the same topological level.
25    priority: u32,
26    /// Names of functions (same stage) that must run before this one.
27    depends_on: Vec<String>,
28}
29
30impl Parse for InitComponentArgs {
31    /// Performs the parse operation.
32    fn parse(input: ParseStream) -> syn::Result<Self> {
33        let mut stage = "Bootstrap".to_string();
34        let mut priority = 0u32;
35        let mut depends_on = Vec::new();
36
37        if input.is_empty() {
38            return Ok(Self {
39                stage,
40                priority,
41                depends_on,
42            });
43        }
44
45        // First token: optional stage ident NOT followed by `=`.
46        // e.g. `bootstrap` in `(bootstrap, priority = 1)`.
47        if input.peek(Ident) && !input.peek2(Token![=]) {
48            let ident: Ident = input.parse()?;
49            stage = match ident.to_string().as_str() {
50                "bootstrap" => "Bootstrap",
51                "kthread" => "Kthread",
52                "hardware" => "Hardware",
53                "process" => "Process",
54                other => {
55                    return Err(syn::Error::new(
56                        ident.span(),
57                        format!(
58                            "unknown stage '{other}'. \
59                             Expected: bootstrap, kthread, hardware, or process"
60                        ),
61                    ));
62                }
63            }
64            .to_string();
65
66            // Consume comma before key=value pairs (if any).
67            if input.peek(Token![,]) {
68                let _: Token![,] = input.parse()?;
69            } else {
70                return Ok(Self {
71                    stage,
72                    priority,
73                    depends_on,
74                });
75            }
76        }
77
78        // Parse remaining `key = value` pairs.
79        loop {
80            if input.is_empty() {
81                break;
82            }
83
84            let key: Ident = input.parse()?;
85            let _: Token![=] = input.parse()?;
86
87            match key.to_string().as_str() {
88                "priority" => {
89                    let lit: LitInt = input.parse()?;
90                    priority = lit.base10_parse()?;
91                }
92                "depends_on" => {
93                    depends_on = parse_depends_on(input)?;
94                }
95                other => {
96                    return Err(syn::Error::new(
97                        key.span(),
98                        format!(
99                            "unknown argument '{other}'. \
100                             Expected: priority, depends_on"
101                        ),
102                    ));
103                }
104            }
105
106            if input.peek(Token![,]) {
107                let _: Token![,] = input.parse()?;
108            } else {
109                break;
110            }
111        }
112
113        Ok(Self {
114            stage,
115            priority,
116            depends_on,
117        })
118    }
119}
120
121/// Parse `fn_name` or `[fn1, fn2, ...]` after `depends_on =`.
122fn parse_depends_on(input: ParseStream) -> syn::Result<Vec<String>> {
123    if input.peek(syn::token::Bracket) {
124        let content;
125        syn::bracketed!(content in input);
126        let names: Punctuated<Ident, Token![,]> = Punctuated::parse_terminated(&content)?;
127        Ok(names.into_iter().map(|i| i.to_string()).collect())
128    } else {
129        let name: Ident = input.parse()?;
130        Ok(vec![name.to_string()])
131    }
132}
133
134/// Register a function as a kernel component initializer.
135///
136/// # Syntax
137///
138/// ```text
139/// #[init_component]
140/// #[init_component(bootstrap)]
141/// #[init_component(bootstrap, priority = 1)]
142/// #[init_component(kthread, priority = 2, depends_on = vfs_init)]
143/// #[init_component(kthread, priority = 3, depends_on = [vfs_init, ipc_init])]
144/// ```
145///
146/// | Argument     | Type             | Default     | Description                                   |
147/// |--------------|------------------|-------------|-----------------------------------------------|
148/// | stage        | positional ident | `bootstrap` | `bootstrap`, `kthread`, `hardware`, or `process` |
149/// | `priority`   | integer          | `0`         | Lower = earlier (tiebreaker within topo level)|
150/// | `depends_on` | ident or list    | `[]`        | Functions that must complete before this one  |
151///
152/// # Example
153///
154/// ```rust,no_run
155/// #[init_component(bootstrap, priority = 1)]
156/// fn vfs_init() -> Result<(), component::ComponentInitError> {
157///     vfs::init();
158///     Ok(())
159/// }
160///
161/// #[init_component(kthread, priority = 2, depends_on = vfs_init)]
162/// fn fs_ext4_init() -> Result<(), component::ComponentInitError> {
163///     fs_ext4::init();
164///     Ok(())
165/// }
166/// ```
167///
168/// The annotated function is emitted unchanged; a companion `#[used]` static is
169/// placed in `.component_entries` so `component::init_all()` can discover,
170/// topologically sort, and call all registered components at runtime.
171#[proc_macro_attribute]
172pub fn init_component(args: TokenStream, input: TokenStream) -> TokenStream {
173    let component_args = parse_macro_input!(args as InitComponentArgs);
174    let function = parse_macro_input!(input as ItemFn);
175
176    let function_name = &function.sig.ident;
177    let function_name_str = function_name.to_string();
178    let function_vis = &function.vis;
179    let function_sig = &function.sig;
180    let function_block = &function.block;
181
182    let stage = Ident::new(&component_args.stage, Span::call_site());
183    let priority = component_args.priority;
184    let depends_on = &component_args.depends_on; // Vec<String> : quoted as &[str_lit, ...]
185
186    // Static name: guaranteed unique within a crate via function name.
187    let static_name = quote::format_ident!("__COMPONENT_ENTRY_{}", function_name);
188
189    let expanded = quote! {
190        #function_vis #function_sig #function_block
191
192        #[doc(hidden)]
193        #[allow(non_upper_case_globals)]
194        #[link_section = ".component_entries"]
195        #[used]
196        static #static_name: component::ComponentEntry = component::ComponentEntry {
197            name:       #function_name_str,
198            stage:      component::InitStage::#stage,
199            init_fn:    #function_name,
200            path:       ::core::concat!(file!(), ":", ::core::stringify!(#function_name)),
201            priority:   #priority,
202            depends_on: &[#(#depends_on),*],
203        };
204    };
205
206    TokenStream::from(expanded)
207}
208
209// ========== parse_components_toml ===============================================
210
211/// Emit compile-time dependency metadata parsed from `Components.toml`.
212///
213/// Searches for `Components.toml` starting from the **calling crate's** manifest
214/// directory and walking up to 6 levels. Returns:
215///
216/// ```text
217/// &'static [(&'static str, &'static [&'static str])]
218/// ```
219///
220/// Each element is `(component_name, &[dep1, dep2, ...])`.
221///
222/// # Example
223///
224/// ```rust,no_run
225/// let meta = component::parse_components_toml!();
226/// for (name, deps) in meta {
227///     log::debug!("{} depends on {:?}", name, deps);
228/// }
229/// ```
230#[proc_macro]
231pub fn parse_components_toml(_input: TokenStream) -> TokenStream {
232    // Locate Components.toml by searching up from the calling crate's dir.
233    let manifest_dir = std::env::var("CARGO_MANIFEST_DIR").unwrap_or_else(|_| ".".to_string());
234
235    let mut search = std::path::PathBuf::from(&manifest_dir);
236    let mut found_path: Option<std::path::PathBuf> = None;
237    for _ in 0..6 {
238        let candidate = search.join("Components.toml");
239        if candidate.exists() {
240            found_path = Some(candidate);
241            break;
242        }
243        if !search.pop() {
244            break;
245        }
246    }
247
248    let entries = match found_path {
249        Some(ref p) => {
250            let content = match std::fs::read_to_string(p) {
251                Ok(c) => c,
252                Err(_e) => {
253                    return TokenStream::from(quote! {
254                        &[] as &[(&'static str, &'static [&'static str])]
255                    });
256                }
257            };
258            parse_toml_deps(&content)
259        }
260        None => vec![],
261    };
262
263    let items = entries.iter().map(|(name, deps)| {
264        quote! { (#name, &[#(#deps),*] as &[&'static str]) }
265    });
266
267    TokenStream::from(quote! {
268        &[#(#items),*] as &[(&'static str, &'static [&'static str])]
269    })
270}
271
272// ========== Internal TOML parser ==========================================================
273
274/// Minimal parser for the Components.toml format used by Strat9-OS.
275///
276/// TODO : improve !
277///
278/// Handles lines of the form (one component per line):
279/// ```toml
280/// name = { path = "...", deps = ["dep1", "dep2"] }
281/// name = { path = "...", deps = [] }
282/// ```
283fn parse_toml_deps(content: &str) -> Vec<(String, Vec<String>)> {
284    let mut result = Vec::new();
285
286    for line in content.lines() {
287        let line = line.trim();
288        // Skip comments, section headers, blank lines.
289        if line.starts_with('#') || line.starts_with('[') || line.is_empty() {
290            continue;
291        }
292
293        let Some(eq_pos) = line.find('=') else {
294            continue;
295        };
296        let name = line[..eq_pos].trim().to_string();
297        let rest = line[eq_pos + 1..].trim();
298
299        // Extract `deps = [...]`.
300        let deps = if let Some(di) = rest.find("deps") {
301            let after = rest[di + 4..].trim_start_matches([' ', '=']).trim_start();
302            if let Some(bs) = after.find('[') {
303                if let Some(be) = after.find(']') {
304                    after[bs + 1..be]
305                        .split(',')
306                        .map(|s| s.trim().trim_matches('"').to_string())
307                        .filter(|s| !s.is_empty())
308                        .collect()
309                } else {
310                    vec![]
311                }
312            } else {
313                vec![]
314            }
315        } else {
316            vec![]
317        };
318
319        result.push((name, deps));
320    }
321
322    result
323}