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