|
| 1 | +use proc_macro::TokenStream; |
| 2 | +use proc_macro2::Span; |
| 3 | +use quote::quote; |
| 4 | +use std::fmt::Write; |
| 5 | +use std::fs::File; |
| 6 | +use std::io::{BufRead, BufReader}; |
| 7 | +use std::path::Path; |
| 8 | +use syn::parse_macro_input; |
| 9 | + |
| 10 | +pub fn px4_message(args: TokenStream, input: TokenStream) -> TokenStream { |
| 11 | + // Open .msg file |
| 12 | + |
| 13 | + let path = parse_macro_input!(args as syn::LitStr).value(); |
| 14 | + let root = std::env::var("CARGO_MANIFEST_DIR").unwrap_or(".".into()); |
| 15 | + let path = Path::new(&root).join(&path); |
| 16 | + let file = File::open(&path).unwrap_or_else(|e| { |
| 17 | + panic!("Unable to open {:?}: {}", path, e); |
| 18 | + }); |
| 19 | + let file = BufReader::new(file); |
| 20 | + |
| 21 | + // Verify that the struct looks like `[pub] struct name;` |
| 22 | + |
| 23 | + let input = parse_macro_input!(input as syn::DeriveInput); |
| 24 | + let name = input.ident; |
| 25 | + let is_unit_struct = match input.data { |
| 26 | + syn::Data::Struct(s) => match s.fields { |
| 27 | + syn::Fields::Unit => true, |
| 28 | + _ => false, |
| 29 | + }, |
| 30 | + _ => false, |
| 31 | + }; |
| 32 | + if !is_unit_struct || input.generics.lt_token.is_some() { |
| 33 | + panic!("Expected `;` after `struct {}`", name); |
| 34 | + } |
| 35 | + |
| 36 | + // Read the .msg file line by line, collecting all the struct members. |
| 37 | + |
| 38 | + let mut members = Vec::new(); |
| 39 | + |
| 40 | + for (line_num, line) in file.lines().enumerate() { |
| 41 | + // Parse the lines, throwing away comments and empty lines, splitting them in type and name. |
| 42 | + |
| 43 | + let mut line = line.unwrap_or_else(|e| { |
| 44 | + panic!("Unable to read from {:?}: {}", path, e); |
| 45 | + }); |
| 46 | + if let Some(comment_start) = line.find('#') { |
| 47 | + line.truncate(comment_start); |
| 48 | + } |
| 49 | + let line = line.trim(); |
| 50 | + if line.is_empty() { |
| 51 | + continue; |
| 52 | + } |
| 53 | + let mut words = line.split_whitespace(); |
| 54 | + let mut type_ = words.next().unwrap(); |
| 55 | + let name = words.next().unwrap_or_else(|| { |
| 56 | + panic!("Missing name on line {} in {:?}", line_num + 1, path); |
| 57 | + }); |
| 58 | + if words.next().is_some() { |
| 59 | + panic!( |
| 60 | + "Garbage after end of line on line {} in {:?}", |
| 61 | + line_num + 1, |
| 62 | + path |
| 63 | + ); |
| 64 | + } |
| 65 | + |
| 66 | + // Parse array types. |
| 67 | + |
| 68 | + let array_len = if let Some(b) = type_.find('[') { |
| 69 | + if !type_.ends_with(']') { |
| 70 | + panic!("Missing `]` on line {} in {:?}", line_num + 1, path); |
| 71 | + } |
| 72 | + let len = &type_[b + 1..type_.len() - 1]; |
| 73 | + type_ = &type_[..b]; |
| 74 | + Some(len.parse::<usize>().unwrap_or_else(|_| { |
| 75 | + panic!( |
| 76 | + "Invalid array length on line {} in {:?}", |
| 77 | + line_num + 1, |
| 78 | + path |
| 79 | + ); |
| 80 | + })) |
| 81 | + } else { |
| 82 | + None |
| 83 | + }; |
| 84 | + |
| 85 | + // Look up the type's width, Rust type, and C type. |
| 86 | + |
| 87 | + let (width, mut rust, c) = match type_ { |
| 88 | + "uint64" => (8, quote! { u64 }, "uint64_t"), |
| 89 | + "uint32" => (4, quote! { u32 }, "uint32_t"), |
| 90 | + "uint16" => (2, quote! { u16 }, "uint16_t"), |
| 91 | + "uint8" | "byte" => (1, quote! { u8 }, "uint8_t"), |
| 92 | + "int64" => (8, quote! { i64 }, "int64_t"), |
| 93 | + "int32" => (4, quote! { i32 }, "int32_t"), |
| 94 | + "int16" => (2, quote! { i16 }, "int16_t"), |
| 95 | + "int8" => (1, quote! { i8 }, "int8_t"), |
| 96 | + "float64" => (8, quote! { f64 }, "double"), |
| 97 | + "float32" => (4, quote! { f32 }, "float"), |
| 98 | + "char" => (1, quote! { i8 }, "char"), |
| 99 | + "bool" => (1, quote! { bool }, "bool"), |
| 100 | + _ => { |
| 101 | + panic!( |
| 102 | + "Unknown type `{}` on line {} in {:?}", |
| 103 | + type_, |
| 104 | + line_num + 1, |
| 105 | + path |
| 106 | + ); |
| 107 | + } |
| 108 | + }; |
| 109 | + if let Some(n) = array_len { |
| 110 | + rust = quote! { [#rust; #n] }; |
| 111 | + } |
| 112 | + |
| 113 | + // Add it to the list. |
| 114 | + |
| 115 | + let name = syn::Ident::new(name, Span::call_site()); |
| 116 | + let size = array_len.unwrap_or(1) * width; |
| 117 | + members.push((name, width, rust, c, size)); |
| 118 | + } |
| 119 | + |
| 120 | + // Sort the members by alignment, biggest first. |
| 121 | + |
| 122 | + members.sort_by(|a, b| b.1.cmp(&a.1)); |
| 123 | + |
| 124 | + // Compute the total size and add any padding. |
| 125 | + |
| 126 | + let mut fields = String::new(); |
| 127 | + let mut size = 0; |
| 128 | + let mut pad_num = 0; |
| 129 | + for m in &members { |
| 130 | + add_padding(&mut fields, &mut pad_num, &mut size, m.1); |
| 131 | + write!(&mut fields, "{} {};", m.3, m.0).unwrap(); |
| 132 | + size += m.4; |
| 133 | + } |
| 134 | + let size_no_padding = size; |
| 135 | + add_padding(&mut fields, &mut pad_num, &mut size, 8); |
| 136 | + fields.push('\0'); |
| 137 | + |
| 138 | + if size > 0xFFFF { |
| 139 | + panic!("Message size too big"); |
| 140 | + } |
| 141 | + |
| 142 | + // Generate the Rust code. |
| 143 | + |
| 144 | + let size = size as u16; |
| 145 | + let size_no_padding = size_no_padding as u16; |
| 146 | + let path = path.to_str().unwrap(); |
| 147 | + let vis = input.vis; |
| 148 | + let attrs = input.attrs; |
| 149 | + let mems = members.iter().map(|m| { |
| 150 | + let n = &m.0; |
| 151 | + let t = &m.2; |
| 152 | + quote! { #n: #t } |
| 153 | + }); |
| 154 | + let name_str = format!("{}\0", name); |
| 155 | + |
| 156 | + let expanded = quote! { |
| 157 | + #[repr(C)] |
| 158 | + #[derive(Clone,Debug)] |
| 159 | + #(#attrs)* |
| 160 | + #vis struct #name { |
| 161 | + #(#mems),* |
| 162 | + } |
| 163 | + unsafe impl px4::uorb::Message for #name { |
| 164 | + fn metadata() -> &'static px4::uorb::Metadata { |
| 165 | + let _ = include_bytes!(#path); // This causes the file to be recompiled if the .msg-file is changed. |
| 166 | + static M: px4::uorb::Metadata = px4::uorb::Metadata { |
| 167 | + _name: #name_str.as_ptr(), |
| 168 | + _size: #size, |
| 169 | + _size_no_padding: #size_no_padding, |
| 170 | + _fields: #fields.as_ptr(), |
| 171 | + }; |
| 172 | + &M |
| 173 | + } |
| 174 | + } |
| 175 | + }; |
| 176 | + |
| 177 | + expanded.into() |
| 178 | +} |
| 179 | + |
| 180 | +fn add_padding(fields: &mut String, pad_num: &mut usize, size: &mut usize, alignment: usize) { |
| 181 | + let misalignment = *size % alignment; |
| 182 | + if misalignment != 0 { |
| 183 | + let pad = alignment - misalignment; |
| 184 | + write!(fields, "uint8_t[{}] _padding{};", pad, *pad_num).unwrap(); |
| 185 | + *size += pad; |
| 186 | + *pad_num += 1; |
| 187 | + } |
| 188 | +} |
0 commit comments