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}