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}