use proc_macro::TokenStream;
use proc_macro2::{Ident, Span};
use quote::quote;
use regex::Regex;
use std::fs::File;
use std::io::{BufRead, BufReader};
use std::path::{Path, PathBuf};
use std::{env, fs};
use syn::parse::{Parse, ParseStream};
use syn::{parse_macro_input, Result};
struct Args {
control: syn::Ident,
}
impl Parse for Args {
fn parse(input: ParseStream) -> Result<Self> {
let model = input.parse()?;
Ok(Self { control: model })
}
}
#[derive(Debug, Default)]
struct IO {
pub name: String,
pub size: Option<usize>,
}
impl IO {
fn new(name: &str, size: Option<&str>) -> Self {
Self {
name: name.to_string(),
size: size.and_then(|s| s.parse().ok()),
}
}
fn var(&self) -> Ident {
Ident::new(&self.name, Span::call_site())
}
}
#[derive(Debug, Default)]
struct List(Vec<IO>);
impl List {
fn quote(&self) -> proc_macro2::TokenStream {
self.0
.iter()
.fold(proc_macro2::TokenStream::default(), |t, io| {
let var = io.var();
if let Some(size) = io.size {
quote! {
#t
#var: [0f64;#size],
}
} else {
quote! {
#t
#var: 0f64,
}
}
})
}
}
fn parse_io(lines: &mut std::io::Lines<BufReader<File>>, io: &str) -> Option<List> {
let re = Regex::new(r"_T (?P<name>\w+)(?:\[(?P<size>\d+)\])?").unwrap();
match lines.next() {
Some(Ok(line)) if line.starts_with("typedef struct") => {
println!("| {}:", io);
let mut io_data = vec![];
while let Some(Ok(line)) = lines.next() {
if line.contains(io) {
break;
} else {
if let Some(caps) = re.captures(&line) {
let size = caps.name("size").map(|m| m.as_str());
println!("| - {:<22}: {:>5}", &caps["name"], size.unwrap_or("1"),);
io_data.push(IO::new(&caps["name"], size))
}
}
}
Some(List(io_data))
}
_ => None,
}
}
#[proc_macro]
pub fn import(input: TokenStream) -> TokenStream {
let Args { control } = parse_macro_input!(input);
let sys = Path::new(&env::var("CARGO_MANIFEST_DIR").unwrap()).join("sys");
let mut file_name = PathBuf::new();
if let Ok(entries) = fs::read_dir(sys) {
for entry in entries {
if let Ok(entry) = entry {
file_name = entry.path();
if let Some(extension) = file_name.extension() {
match extension.to_str() {
Some("h") => {
if file_name
.to_str()
.filter(|f| {
!(f.ends_with("rtwtypes.h")
|| f.ends_with("rt_defines.h")
|| f.ends_with("_private.h")
|| f.ends_with("_types.h"))
})
.is_some()
{
break;
}
}
_ => (),
}
}
}
}
}
let file = File::open(&file_name).expect(&format!("file {:?} not found", file_name));
let reader = BufReader::new(file);
let mut lines = reader.lines();
let model = loop {
if let Some(Ok(line)) = lines.next() {
if line.contains("File:") {
let regex = regex::Regex::new(r"File:\s*(\w+)\.h").unwrap();
if let Some(captures) = regex.captures(&line) {
let name = captures.get(1).unwrap().as_str();
break Ident::new(name, Span::call_site());
}
}
}
};
println!("Parsing Simulink model {}:", model);
let mut model_inputs = List::default();
let mut model_outputs = List::default();
let mut model_states = List::default();
while let Some(Ok(line)) = lines.next() {
if line.contains("External inputs") {
if let Some(io) = parse_io(&mut lines, "ExtU") {
model_inputs = io;
}
}
if line.contains("External outputs") {
if let Some(io) = parse_io(&mut lines, "ExtY") {
model_outputs = io;
}
}
if line.contains("Block states") {
if let Some(io) = parse_io(&mut lines, "DW") {
model_states = io;
}
}
}
let var_u = model_inputs.quote();
let var_y = model_outputs.quote();
let var_s = model_states.quote();
let code = quote! {
include!(concat!(env!("OUT_DIR"), "/bindings.rs"));
pub type #control = #model;
paste::paste!{
#[derive(Debug, Clone, Copy, Default)]
pub struct #model {
pub inputs: [<ExtU_ #model _T>],
pub outputs: [<ExtY_ #model _T>],
states: [<DW_ #model _T>],
}
impl Default for [<ExtU_ #model _T>] {
fn default() -> Self {
Self { #var_u }
}
}
impl Default for [<ExtY_ #model _T>] {
fn default() -> Self {
Self { #var_y }
}
}
impl Default for [<DW_ #model _T>] {
fn default() -> Self {
Self { #var_s }
}
}
impl #model {
pub fn new() -> Self {
let mut this: Self = Default::default();
let mut data: [<RT_MODEL_ #model _T>] = [<tag_RTM_ #model _T>] {
dwork: &mut this.states as *mut _,
};
unsafe {
[< #model _initialize>](
&mut data as *mut _,
&mut this.inputs as *mut _,
&mut this.outputs as *mut _,
)
}
this
}
pub fn step(&mut self) {
let mut data: [<RT_MODEL_ #model _T>] = [<tag_RTM_ #model _T>] {
dwork: &mut self.states as *mut _,
};
unsafe {
[<#model _step>](
&mut data as *mut _,
&mut self.inputs as *mut _,
&mut self.outputs as *mut _,
)
}
}
} }
};
code.into()
}