simulink_binder/
lib.rs

1//! A procedural macro to import a Simulink C model into Rust
2
3use proc_macro::TokenStream;
4use proc_macro2::{Ident, Span};
5use quote::quote;
6use regex::Regex;
7use std::fs::File;
8use std::io::{BufRead, BufReader};
9use std::path::{Path, PathBuf};
10use std::{env, fs};
11use syn::parse::{Parse, ParseStream};
12use syn::{parse_macro_input, Result};
13
14// proc macro inputs argument
15struct Args {
16    // the name of the Simulink model
17    control: syn::Ident,
18    // the path to the Simulink C model header file
19    // header: syn::LitStr,
20}
21impl Parse for Args {
22    // inputs argument parser
23    fn parse(input: ParseStream) -> Result<Self> {
24        let model = input.parse()?;
25        // input.parse::<syn::Token![,]>()?;
26        // let header = input.parse()?;
27        Ok(Self { control: model })
28    }
29}
30// Simulink inputs/outputs
31#[derive(Debug, Default)]
32struct IO {
33    // i/o variable name
34    pub name: String,
35    // i/o variable size
36    pub size: Option<usize>,
37}
38impl IO {
39    // Creates a new IO
40    fn new(name: &str, size: Option<&str>) -> Self {
41        Self {
42            name: name.to_string(),
43            size: size.and_then(|s| s.parse().ok()),
44        }
45    }
46    // Rust variable
47    fn var(&self) -> Ident {
48        Ident::new(&self.name, Span::call_site())
49    }
50}
51#[derive(Debug, Default)]
52struct List(Vec<IO>);
53impl List {
54    fn quote(&self) -> proc_macro2::TokenStream {
55        self.0
56            .iter()
57            .fold(proc_macro2::TokenStream::default(), |t, io| {
58                let var = io.var();
59                if let Some(size) = io.size {
60                    quote! {
61                        #t
62                        #var: [0f64;#size],
63                    }
64                } else {
65                    quote! {
66                        #t
67                        #var: 0f64,
68                    }
69                }
70            })
71    }
72}
73
74// Parse the Simulink C header file to extract inputs and outputs variables
75fn parse_io(lines: &mut std::io::Lines<BufReader<File>>, io: &str) -> Option<List> {
76    let re = Regex::new(r"_T (?P<name>\w+)(?:\[(?P<size>\d+)\])?").unwrap();
77    match lines.next() {
78        Some(Ok(line)) if line.starts_with("typedef struct") => {
79            println!("| {}:", io);
80            let mut io_data = vec![];
81            while let Some(Ok(line)) = lines.next() {
82                if line.contains(io) {
83                    break;
84                } else {
85                    if let Some(caps) = re.captures(&line) {
86                        let size = caps.name("size").map(|m| m.as_str());
87                        println!("|  - {:<22}: {:>5}", &caps["name"], size.unwrap_or("1"),);
88                        io_data.push(IO::new(&caps["name"], size))
89                    }
90                }
91            }
92            Some(List(io_data))
93        }
94        _ => None,
95    }
96}
97
98/// Writes the Rust wrapper for a Simulink C model
99///
100/// # Examples
101///
102///```
103/// import!(M1HPloadcells)
104///```
105#[proc_macro]
106pub fn import(input: TokenStream) -> TokenStream {
107    let Args { control } = parse_macro_input!(input);
108
109    let sys = Path::new(&env::var("CARGO_MANIFEST_DIR").unwrap()).join("sys");
110    let mut file_name = PathBuf::new();
111    if let Ok(entries) = fs::read_dir(sys) {
112        for entry in entries {
113            if let Ok(entry) = entry {
114                file_name = entry.path();
115                if let Some(extension) = file_name.extension() {
116                    match extension.to_str() {
117                        Some("h") => {
118                            if file_name
119                                .to_str()
120                                .filter(|f| {
121                                    !(f.ends_with("rtwtypes.h")
122                                        || f.ends_with("rt_defines.h")
123                                        || f.ends_with("_private.h")
124                                        || f.ends_with("_types.h"))
125                                })
126                                .is_some()
127                            {
128                                break;
129                            }
130                        }
131                        _ => (),
132                    }
133                }
134            }
135        }
136    }
137    let file = File::open(&file_name).expect(&format!("file {:?} not found", file_name));
138    let reader = BufReader::new(file);
139    let mut lines = reader.lines();
140
141    let model = loop {
142        if let Some(Ok(line)) = lines.next() {
143            if line.contains("File:") {
144                let regex = regex::Regex::new(r"File:\s*(\w+)\.h").unwrap();
145                if let Some(captures) = regex.captures(&line) {
146                    let name = captures.get(1).unwrap().as_str();
147                    break Ident::new(name, Span::call_site());
148                }
149            }
150        }
151    };
152    println!("Parsing Simulink model {}:", model);
153
154    let mut model_inputs = List::default();
155    let mut model_outputs = List::default();
156    let mut model_states = List::default();
157    while let Some(Ok(line)) = lines.next() {
158        if line.contains("External inputs") {
159            if let Some(io) = parse_io(&mut lines, "ExtU") {
160                model_inputs = io;
161            }
162        }
163        if line.contains("External outputs") {
164            if let Some(io) = parse_io(&mut lines, "ExtY") {
165                model_outputs = io;
166            }
167        }
168        if line.contains("Block states") {
169            if let Some(io) = parse_io(&mut lines, "DW") {
170                model_states = io;
171            }
172        }
173    }
174
175    let var_u = model_inputs.quote();
176    let var_y = model_outputs.quote();
177    let var_s = model_states.quote();
178
179    let code = quote! {
180        include!(concat!(env!("OUT_DIR"), "/bindings.rs"));
181
182        pub type #control = #model;
183
184        paste::paste!{
185        /// Simulink controller wrapper
186        #[derive(Debug, Clone, Copy, Default)]
187        pub struct #model {
188            // Inputs Simulink structure
189            pub inputs: [<ExtU_ #model _T>],
190            // Outputs Simulink structure
191            pub outputs: [<ExtY_ #model _T>],
192            states: [<DW_ #model _T>],
193        }
194        impl Default for [<ExtU_ #model _T>] {
195            fn default() -> Self {
196                Self { #var_u }
197            }
198        }
199        impl Default for [<ExtY_ #model _T>] {
200            fn default() -> Self {
201                Self { #var_y }
202            }
203        }
204        impl Default for [<DW_ #model _T>] {
205            fn default() -> Self {
206                Self { #var_s }
207            }
208        }
209        impl #model {
210            /// Creates a new controller
211            pub fn new() -> Self {
212                let mut this: Self = Default::default();
213                let mut data: [<RT_MODEL_ #model _T>] = [<tag_RTM_ #model _T>] {
214                    dwork: &mut this.states as *mut _,
215                };
216                unsafe {
217                    [< #model _initialize>](
218                        &mut data as *mut _,
219                        &mut this.inputs as *mut _,
220                        &mut this.outputs as *mut _,
221                    )
222                }
223                this
224            }
225            /// Steps the controller
226            pub fn step(&mut self) {
227                let mut data: [<RT_MODEL_ #model _T>] = [<tag_RTM_ #model _T>] {
228                    dwork: &mut self.states as *mut _,
229                };
230                unsafe {
231                    [<#model _step>](
232                        &mut data as *mut _,
233                        &mut self.inputs as *mut _,
234                        &mut self.outputs as *mut _,
235                    )
236                }
237            }
238        }        }
239    };
240    code.into()
241}