Skip to main content

device_driver_codegen/
rust.rs

1use std::ops::Range;
2
3use askama::Template;
4use clap::Parser;
5use convert_case::Case;
6use device_driver_common::{
7    identifier::{Identifier, IdentifierType, Type},
8    specifiers::{Access, AddressMode},
9};
10use device_driver_lir::model::{
11    BlockMethod, BlockMethodType, Driver, Field, FieldConversionMethod, Repeat,
12};
13
14#[derive(Parser, Debug, Clone, Default)]
15#[command(no_binary_name = true, bin_name = "")]
16pub struct RustCodegenOptions {
17    /// When specified, defmt implementations will be generated using this cfg feature flag
18    #[arg(
19        long = "rust-defmt-feature",
20        value_name = "FEATURE",
21        require_equals = true
22    )]
23    pub defmt_feature: Option<String>,
24}
25
26#[derive(Template)]
27#[template(path = "rust/driver.rs.j2", escape = "none", whitespace = "minimize")]
28pub struct DriverTemplateRust<'a> {
29    driver: &'a Driver,
30    source: &'a str,
31    codegen_options: &'a RustCodegenOptions,
32}
33
34impl<'a> DriverTemplateRust<'a> {
35    pub fn new(
36        device: &'a Driver,
37        source: &'a str,
38        codegen_options: &'a RustCodegenOptions,
39    ) -> Self {
40        Self {
41            driver: device,
42            source,
43            codegen_options,
44        }
45    }
46
47    fn defmt_feature(&self) -> Option<&str> {
48        self.codegen_options.defmt_feature.as_deref()
49    }
50
51    fn get_block_method_docs(&self, method: &BlockMethod) -> String {
52        use std::fmt::Write;
53
54        let mut docs = String::new();
55
56        let operation_type = match method.method_type {
57            BlockMethodType::Block { .. } => "Block",
58            BlockMethodType::Register { .. } => "Register",
59            BlockMethodType::Command { .. } => "Command",
60            BlockMethodType::Buffer { .. } => "Buffer",
61        };
62
63        if !method.description.is_empty() {
64            writeln!(&mut docs, "///").unwrap();
65        }
66
67        writeln!(&mut docs, "/// {} operation:", operation_type).unwrap();
68        writeln!(&mut docs, "/// - Address: `{}`", method.address).unwrap();
69
70        let reset_value_text = match &method.method_type {
71            BlockMethodType::Register { reset_value, .. } => Some(
72                reset_value
73                    .as_ref()
74                    .map(|reset_value| {
75                        self.source
76                            .get(Range::from(reset_value.span))
77                            .unwrap_or("error: invalid span")
78                    })
79                    .unwrap_or("0"),
80            ),
81            _ => None,
82        };
83
84        if let Some(reset_value_text) = reset_value_text {
85            writeln!(&mut docs, "/// - Reset value: `{reset_value_text}`").unwrap();
86        }
87
88        if let Repeat::Count { count, .. } = method.repeat {
89            writeln!(&mut docs, "/// - Index range: `0..{count}`").unwrap();
90        };
91
92        docs
93    }
94}
95
96fn description_to_docstring(description: &str) -> String {
97    use std::fmt::Write;
98
99    let mut docstring = String::new();
100
101    for line in description.lines() {
102        writeln!(
103            &mut docstring,
104            "///{}{line}",
105            if line.starts_with(' ') { "" } else { " " }
106        )
107        .unwrap();
108    }
109
110    docstring
111}
112
113fn get_defmt_fmt_string(field: &Field) -> String {
114    let defmt_type_hint = match field.conversion_method {
115        FieldConversionMethod::None => {
116            let base_type = &field.base_type;
117            format!("={base_type}")
118        }
119        FieldConversionMethod::Bool => "=bool".into(),
120        _ => String::new(),
121    };
122
123    format!(
124        "{}: {{{}}}, ",
125        field.name.to_case(Case::Snake),
126        defmt_type_hint
127    )
128}
129
130fn get_command_fieldset_name(fieldset: &Option<Identifier<Type>>) -> String {
131    match fieldset {
132        Some(fs) => fs.to_case(Case::Pascal),
133        None => "()".into(),
134    }
135}
136
137fn get_enum_base_type<'d>(driver: &'d Driver, enum_name: &Identifier<Type>) -> &'d str {
138    &driver
139        .enums
140        .iter()
141        .find(|e| e.name == *enum_name)
142        .expect("This enum reference is checked in a mir pass")
143        .base_type
144}
145
146fn get_address_mode_const_value(value: &Option<AddressMode>) -> &'static str {
147    match value {
148        Some(AddressMode::Mapped) => "::device_driver::MappedAddressMode",
149        Some(AddressMode::Indexed) => "::device_driver::IndexedAddressMode",
150        None => "()",
151    }
152}
153
154fn maybe_doc_alias<T: IdentifierType>(identifier: &Identifier<T>, case: Case) -> String {
155    if identifier.to_case(case) == identifier.original() {
156        return String::new();
157    }
158
159    format!("#[doc(alias = \"{}\")]", identifier.original())
160}