Skip to main content

nucleus_compiler/
lib.rs

1//! The Nucleus pinmux compiler.
2//!
3//! Owns the `stm32.toml` → diagnostics pipeline: [`config`] parses the file,
4//! [`solver`] validates it against the [`nucleus_db`] constraint database, and
5//! [`check`] is the one-call entry point the CLI and (later) the LSP build on.
6//!
7//! Phase 2 ships the parser and the constraint solver (four conflict classes).
8//! HAL code generation lands in Phase 3.
9
10pub mod codegen;
11pub mod config;
12pub mod model;
13pub mod solver;
14
15use nucleus_db::Database;
16
17pub use codegen::{generate, Generated};
18pub use config::{Config, ParseError};
19pub use solver::Conflict;
20
21/// The outcome of checking one `stm32.toml`.
22#[derive(Debug, Clone)]
23pub struct CheckReport {
24    /// The parsed config (useful to callers that go on to codegen).
25    pub config: Config,
26    /// All detected conflicts, in deterministic order. Empty means the config
27    /// is valid.
28    pub conflicts: Vec<Conflict>,
29}
30
31impl CheckReport {
32    /// Whether the config is free of conflicts.
33    pub fn is_ok(&self) -> bool {
34        self.conflicts.is_empty()
35    }
36}
37
38/// The database to validate against for `family`. Phase 2 supports exactly one
39/// MCU (the NUCLEO-F446RE); unknown families fall back to it with the family
40/// recorded in [`UnknownFamily`] so the CLI can warn.
41fn database_for(family: &str) -> Result<Database, UnknownFamily> {
42    match family {
43        "STM32F446RE" | "" => Ok(Database::f446re()),
44        other => Err(UnknownFamily(other.to_string())),
45    }
46}
47
48/// Returned when `[device].family` names an MCU the database doesn't cover yet.
49#[derive(Debug, Clone, PartialEq, Eq)]
50pub struct UnknownFamily(pub String);
51
52impl std::fmt::Display for UnknownFamily {
53    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
54        write!(
55            f,
56            "unsupported device family {:?}: Nucleus currently supports only STM32F446RE",
57            self.0
58        )
59    }
60}
61
62impl std::error::Error for UnknownFamily {}
63
64/// Parse and validate `stm32.toml` text in one call.
65///
66/// Returns [`ParseError`] only for malformed TOML / schema violations; hardware
67/// conflicts are returned *inside* the [`CheckReport`] (a valid file can still
68/// describe an invalid board).
69pub fn check(text: &str) -> Result<CheckReport, ParseError> {
70    let config = config::parse(text)?;
71    // An unknown family is itself a conflict-worthy condition, but we model it
72    // as falling back to the F446 DB; the CLI surfaces the family mismatch.
73    let db = database_for(&config.device.family).unwrap_or_else(|_| Database::f446re());
74    let conflicts = solver::solve(&config, &db);
75    Ok(CheckReport { config, conflicts })
76}
77
78/// Like [`check`], but also reports an unsupported `[device].family`.
79pub fn check_family(text: &str) -> Result<(CheckReport, Option<UnknownFamily>), ParseError> {
80    let config = config::parse(text)?;
81    let family_warning = database_for(&config.device.family).err();
82    let db = Database::f446re();
83    let conflicts = solver::solve(&config, &db);
84    Ok((CheckReport { config, conflicts }, family_warning))
85}
86
87#[cfg(test)]
88mod tests {
89    use super::*;
90
91    #[test]
92    fn check_reports_ok_for_clean_config() {
93        let report = check(
94            r#"
95[device]
96family = "STM32F446RE"
97
98[peripherals.usart2]
99tx = "PA2"
100rx = "PA3"
101"#,
102        )
103        .unwrap();
104        assert!(report.is_ok());
105    }
106
107    #[test]
108    fn check_surfaces_conflicts() {
109        let report = check(
110            r#"
111[peripherals.spi1]
112mosi = "PA7"
113miso = "PA6"
114sck = "PA5"
115
116[peripherals.tim2]
117channel1 = "PA5"
118"#,
119        )
120        .unwrap();
121        assert!(!report.is_ok());
122    }
123
124    #[test]
125    fn unknown_family_is_flagged() {
126        let (_report, warning) = check_family(
127            r#"
128[device]
129family = "STM32H750"
130"#,
131        )
132        .unwrap();
133        assert_eq!(warning, Some(UnknownFamily("STM32H750".to_string())));
134    }
135
136    #[test]
137    fn malformed_toml_is_a_parse_error() {
138        assert!(check("this is not toml = = =").is_err());
139    }
140}