Skip to content

Commit 919d9ac

Browse files
committed
Add #[px4_message] proc macro for importing msg files.
1 parent 7d4aced commit 919d9ac

File tree

6 files changed

+252
-8
lines changed

6 files changed

+252
-8
lines changed

macros/Cargo.toml

+1-1
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@ authors = ["Mara Bos <[email protected]>"]
55

66
[lib]
77
proc_macro = true
8-
path = "macros.rs"
98

109
[dependencies]
10+
proc-macro2 = "0.4"
1111
quote = "0.6"
1212
syn = { version = "0.15", features = ["full"] }

macros/src/lib.rs

+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
#![recursion_limit="128"]
2+
3+
extern crate proc_macro;
4+
extern crate proc_macro2;
5+
extern crate quote;
6+
extern crate syn;
7+
8+
use proc_macro::TokenStream;
9+
10+
mod message;
11+
mod module_main;
12+
13+
#[proc_macro_attribute]
14+
pub fn px4_message(args: TokenStream, input: TokenStream) -> TokenStream {
15+
message::px4_message(args, input)
16+
}
17+
18+
#[proc_macro_attribute]
19+
pub fn px4_module_main(args: TokenStream, input: TokenStream) -> TokenStream {
20+
module_main::px4_module_main(args, input)
21+
}

macros/src/message.rs

+188
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,188 @@
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+
}

macros/macros.rs renamed to macros/src/module_main.rs

-5
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,7 @@
1-
extern crate proc_macro;
2-
extern crate quote;
3-
extern crate syn;
4-
51
use proc_macro::TokenStream;
62
use quote::quote;
73
use syn::parse_macro_input;
84

9-
#[proc_macro_attribute]
105
pub fn px4_module_main(args: TokenStream, input: TokenStream) -> TokenStream {
116
if args.to_string() != "" {
127
panic!("px4_module_main does not take any arguments");

src/lib.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,7 @@ pub mod uorb;
9696
mod logging;
9797

9898
pub use crate::logging::{log_raw, LogLevel};
99-
pub use px4_macros::px4_module_main;
99+
pub use px4_macros::{px4_message, px4_module_main};
100100

101101
#[doc(hidden)]
102102
pub fn _run<F>(modulename: &'static [u8], argc: u32, argv: *mut *mut u8, f: F) -> i32

src/uorb/mod.rs

+41-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,46 @@
11
//! Bindings to the uORB messaging system.
22
//!
3-
//! This part is not yet finished.
3+
//! ## Message definitions
4+
//!
5+
//! This crate provides a way to import a message definition from a `.msg`
6+
//! file:
7+
//!
8+
//! ```ignore
9+
//! use px4::px4_message;
10+
//!
11+
//! #[px4_message("msg/foo.msg")] pub struct foo;
12+
//! ```
13+
//!
14+
//! This will read `msg/foo.msg`, relative to the root of the crate (where your
15+
//! Cargo.toml is), parse its contents, and generate the equivalent Rust
16+
//! struct. In addition, [`Message`](trait.Message.html), `Clone` and `Debug`
17+
//! are derived automatically.
18+
//!
19+
//! ## Subscribing
20+
//!
21+
//! Subscribing is done through the [`Subscribe` trait](trait.Subscribe.html),
22+
//! which is automatically implemented for all messages.
23+
//!
24+
//! ```ignore
25+
//! use px4::uorb::Subscribe;
26+
//!
27+
//! let sub = foo::subscribe().unwrap();
28+
//!
29+
//! info!("Latest foo: {:?}", sub.get().unwrap());
30+
//! ```
31+
//!
32+
//! ## Publishing
33+
//!
34+
//! Publishing is done through the [`Publish` trait](trait.Publish.html),
35+
//! which is automatically implemented for all messages.
36+
//!
37+
//! ```ignore
38+
//! use px4::uorb::Publish;
39+
//!
40+
//! let publ = foo { timestamp: 123, a: 1, b: 2 }.advertise().unwrap();
41+
//!
42+
//! publ.publish(&foo { timestamp: 456, a: 3, b: 4 }).unwrap();
43+
//! ```
444
545
mod c;
646
mod publish;

0 commit comments

Comments
 (0)