Skip to main content

cfs_synapse/
lib.rs

1use std::{
2    error::Error as StdError,
3    fmt, fs,
4    path::{Path, PathBuf},
5};
6
7/// Target language for Synapse code generation.
8#[derive(Debug, Clone, Copy, PartialEq, Eq)]
9pub enum Lang {
10    /// NASA cFS C header (`.h`).
11    C,
12    /// Rust `#[repr(C)]` bindings (`.rs`).
13    Rust,
14}
15
16impl Lang {
17    /// File extension used for this generated language.
18    pub fn extension(self) -> &'static str {
19        match self {
20            Lang::C => "h",
21            Lang::Rust => "rs",
22        }
23    }
24}
25
26/// Error type returned by the Synapse library facade.
27#[derive(Debug)]
28pub enum Error {
29    Io(std::io::Error),
30    Parse(Box<pest::error::Error<synapse_parser::synapse::Rule>>),
31}
32
33impl fmt::Display for Error {
34    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
35        match self {
36            Error::Io(e) => write!(f, "{e}"),
37            Error::Parse(e) => write!(f, "{e}"),
38        }
39    }
40}
41
42impl StdError for Error {
43    fn source(&self) -> Option<&(dyn StdError + 'static)> {
44        match self {
45            Error::Io(e) => Some(e),
46            Error::Parse(e) => Some(e),
47        }
48    }
49}
50
51impl From<std::io::Error> for Error {
52    fn from(value: std::io::Error) -> Self {
53        Error::Io(value)
54    }
55}
56
57impl From<pest::error::Error<synapse_parser::synapse::Rule>> for Error {
58    fn from(value: pest::error::Error<synapse_parser::synapse::Rule>) -> Self {
59        Error::Parse(Box::new(value))
60    }
61}
62
63/// Generate code from `.syn` source text.
64pub fn generate_str(source: &str, lang: Lang) -> Result<String, Error> {
65    let file = synapse_parser::ast::parse(source)?;
66    let output = match lang {
67        Lang::C => synapse_codegen_cfs::generate_c(&file),
68        Lang::Rust => synapse_codegen_cfs::generate_rust(&file, &Default::default()),
69    };
70    Ok(output)
71}
72
73/// Generate code from an input file and write it into `out_dir`.
74///
75/// The output file uses the input file stem plus the target language extension,
76/// for example `my_msgs.syn` becomes `my_msgs.h` for [`Lang::C`].
77pub fn generate_file(
78    input: impl AsRef<Path>,
79    out_dir: impl AsRef<Path>,
80    lang: Lang,
81) -> Result<PathBuf, Error> {
82    let input = input.as_ref();
83    let source = fs::read_to_string(input)?;
84    let output = generate_str(&source, lang)?;
85
86    let out_dir = out_dir.as_ref();
87    fs::create_dir_all(out_dir)?;
88
89    let stem = input.file_stem().ok_or_else(|| {
90        std::io::Error::new(
91            std::io::ErrorKind::InvalidInput,
92            format!("input file has no stem: {}", input.display()),
93        )
94    })?;
95    let out_path = out_dir.join(format!("{}.{}", stem.to_string_lossy(), lang.extension()));
96    fs::write(&out_path, output)?;
97    Ok(out_path)
98}
99
100/// Generate a cFS C header from an input file.
101pub fn generate_c_file(
102    input: impl AsRef<Path>,
103    out_dir: impl AsRef<Path>,
104) -> Result<PathBuf, Error> {
105    generate_file(input, out_dir, Lang::C)
106}
107
108/// Generate Rust `#[repr(C)]` bindings from an input file.
109pub fn generate_rust_file(
110    input: impl AsRef<Path>,
111    out_dir: impl AsRef<Path>,
112) -> Result<PathBuf, Error> {
113    generate_file(input, out_dir, Lang::Rust)
114}
115
116#[cfg(test)]
117mod tests {
118    use super::*;
119
120    #[test]
121    fn generate_c_from_string() {
122        let out = generate_str("@mid(0x1880)\ncommand SetMode { mode: u8 }", Lang::C).unwrap();
123        assert!(out.contains("#define SET_MODE_MID  0x1880U"));
124        assert!(out.contains("CFE_MSG_CommandHeader_t Header;"));
125    }
126
127    #[test]
128    fn generate_rust_from_string() {
129        let out = generate_str("@mid(0x0801)\ntelemetry NavState { x: f64 }", Lang::Rust).unwrap();
130        assert!(out.contains("pub const NAV_STATE_MID: u16 = 0x0801;"));
131        assert!(out.contains("pub cfs_header: cfs_sys::CFE_MSG_TelemetryHeader_t,"));
132    }
133}