macros/lib.rs
1/*
2 * Licensed to the Apache Software Foundation (ASF) under one
3 * or more contributor license agreements. See the NOTICE file
4 * distributed with this work for additional information
5 * regarding copyright ownership. The ASF licenses this file
6 * to you under the Apache License, Version 2.0 (the
7 * "License"); you may not use this file except in compliance
8 * with the License. You may obtain a copy of the License at
9 *
10 * http://www.apache.org/licenses/LICENSE-2.0
11 *
12 * Unless required by applicable law or agreed to in writing,
13 * software distributed under the License is distributed on an
14 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15 * KIND, either express or implied. See the License for the
16 * specific language governing permissions and limitations
17 * under the License.
18 */
19//! Mynewt Macros for calling Mynewt APIs . Import this crate into a source code crate and check the expanded macros using:
20//! ```
21//! clear ; cargo rustc -- -Z unstable-options --pretty expanded | head -20
22//! ```
23#![recursion_limit="128"] // Increase recursion limit to prevent quote!{} errors
24#![feature(proc_macro_span)] // Allow use of spans in Procedural Macros
25
26mod safe_wrap; // Include safe_wrap.rs
27mod infer_type; // Include infer_type.rs
28
29extern crate proc_macro;
30use proc_macro::TokenStream;
31use quote::{quote};
32use syn::{
33 parse_macro_input,
34};
35
36/// Given an `extern "C"` block of function declarations, generate the safe wrapper for the function
37#[proc_macro_attribute]
38pub fn safe_wrap(attr: TokenStream, item: TokenStream) -> TokenStream {
39 safe_wrap::safe_wrap_internal(attr, item)
40}
41
42/// Given a Rust function definition, infer the placeholder types in the function
43#[proc_macro_attribute]
44pub fn infer_type(attr: TokenStream, item: TokenStream) -> TokenStream {
45 infer_type::infer_type_internal(attr, item)
46}
47
48/// Given a static mutable variable, return an unsafe mutable pointer that's suitable for passing to Mynewt APIs for writing output.
49/// `out!(NETWORK_TASK)` expands to `unsafe { &mut NETWORK_TASK }`
50#[proc_macro]
51pub fn out(item: TokenStream) -> TokenStream {
52 // Parse the macro input as an identifier e.g. `NETWORK_TASK`.
53 let input = parse_macro_input!(item as syn::Ident);
54 // Convert the identifier to string.
55 let ident = input.to_string();
56 // Compose the macro expansion as a string. `r#"..."#` represents a raw string (for convenience)
57 let expanded = format!(r#"unsafe {{ &mut {} }}"#, ident); // `{{` and `}}` will be rendered as `{` and `}`
58 // Parse the string into Rust tokens and return the expanded tokens back to the compiler.
59 expanded.parse().unwrap()
60}
61
62/// Create a `Strn` containing a null-terminated byte string that's suitable for passing to Mynewt APIs.
63/// `strn!("network")` expands to `&Strn::new( b"network\0" )`.
64/// `strn!(())` expands to `&Strn::new( b"\0" )`.
65/// For macro calls like `strn!( stringify!( value ) )`, return `&Strn::new( b"value\0" )`.
66/// For complex macro calls like `strn!( $crate::parse!(@ json device_id) )`, return the parameter as is.
67#[proc_macro]
68pub fn strn(item: TokenStream) -> TokenStream {
69 let item_str = item.to_string();
70 let span = proc_macro2::Span::call_site();
71 // println!("item: {:#?}", item_str);
72 if item_str.replace(" ", "") == "()" {
73 // Expand `( )` (old Rust) and `()` (new Rust) to `&Strn::new( b"\0" )`
74 let expanded = quote! {
75 &Strn::new( b"\0" )
76 };
77 return expanded.into();
78 } else if item_str.contains("parse!") {
79 // If `item_str` looks like `$crate::parse!(@ json device_id)` (old Rust) or `::mynewt::parse!(@ json &device_id)` (new Rust), return as is.
80 return item;
81 } else if item_str.starts_with("\"") && item_str.ends_with("\"") {
82 // Transform literal `"\"device\""` to `&Strn::new( b"device\0" )`
83 let item_split: Vec<&str> = item_str.splitn(3, "\"").collect();
84 let lit = item_split[1].to_string() + "\0";
85 // println!("lit: {:#?}", lit);
86 let bytestr = syn::LitByteStr::new(lit.as_bytes(), span);
87 let expanded = quote! {
88 &Strn::new( #bytestr )
89 };
90 return expanded.into();
91 }
92 let expr = parse_macro_input!(item as syn::Expr);
93 let expr_str = quote! { #expr }.to_string();
94 match expr {
95 syn::Expr::Macro(_expr) => {
96 // Transform macro `stringify ! ( value )` to `&Strn::new( b"value\0" )`
97 // macOS gives `stringify ! ( value )` but some Window machines give `stringify ! (value)`.
98 let expr_split: Vec<&str> = expr_str.splitn(2, "stringify ! (").collect();
99 let ident = expr_split[1].trim();
100 let ident_split: Vec<&str> = ident.splitn(2, ")").collect();
101 let ident = ident_split[0].trim().to_string() + "\0";
102 // println!("ident: {:#?}", ident);
103 let bytestr = syn::LitByteStr::new(ident.as_bytes(), span);
104 let expanded = quote! {
105 &Strn::new( #bytestr )
106 };
107 return expanded.into();
108 }
109 syn::Expr::Lit(_expr) => {
110 // Literals already handled above. Should not come here.
111 assert!(false, "strn lit"); // Not supported
112 let expanded = quote! { &Strn::new( b"\0" ) };
113 return expanded.into();
114 }
115 syn::Expr::Try(expr) => {
116 // Handle `strn!( fn() ? )`
117 let expanded = quote! { &Strn::new( #expr ) };
118 return expanded.into();
119 }
120 _ => {}
121 };
122 // println!("strn: {:#?}", expr);
123 // println!("strn3: {:#?}", expr_str);
124 assert!(false, "strn!() pattern not supported: {}", item_str); // Not supported
125 let expanded = quote! { &Strn::new( b"\0" ) };
126 expanded.into()
127}
128
129/// Initialise a null-terminated bytestring `Strn` that's suitable for passing to Mynewt APIs
130/// `init_strn!("network")` expands to `Strn{ rep: ByteStr(b"network\0") }`
131/// Used like this:
132/// ```
133/// static STATIC_STRN: Strn = init_strn!("network");
134/// let local_strn = init_strn!("network");
135/// ```
136#[proc_macro]
137pub fn init_strn(item: TokenStream) -> TokenStream {
138 // Parse the macro input as a literal string e.g. `"network"`.
139 let input = parse_macro_input!(item as syn::LitStr);
140 let span = proc_macro2::Span::call_site();
141
142 // Get the literal string value and terminate with null. Convert to bytestring.
143 let val = input.value().to_string() + "\0";
144 let bytestr = syn::LitByteStr::new(val.as_bytes(), span);
145
146 // Compose the macro expansion as tokens.
147 let expanded = quote! {
148 Strn {
149 rep: mynewt::StrnRep::ByteStr(#bytestr)
150 }
151 };
152 // Return the expanded tokens back to the Rust compiler.
153 return expanded.into();
154}
155
156/// Transform a block of CBOR encoding calls by adding error checking. All lines must terminate with `;`
157/// ```
158/// try_cbor!({
159/// let encoder = COAP_CONTEXT.encoder("COAP_CONTEXT", "_map");
160/// cbor_encode_text_string(
161/// encoder,
162/// COAP_CONTEXT.key_to_cstr(key_with_opt_null),
163/// COAP_CONTEXT.cstr_len(key_with_opt_null));
164/// cbor_encode_int(encoder, value);
165/// })
166/// ```
167/// expands to:
168/// ```
169/// unsafe {
170/// let encoder = COAP_CONTEXT.encoder("COAP_CONTEXT", "_map");
171/// let res =
172/// tinycbor::cbor_encode_text_string(encoder,
173/// COAP_CONTEXT.key_to_cstr(key_with_opt_null),
174/// COAP_CONTEXT.cstr_len(key_with_opt_null));
175/// COAP_CONTEXT.check_result(res);
176/// let res = tinycbor::cbor_encode_int(encoder, value);
177/// COAP_CONTEXT.check_result(res);
178/// }
179/// ```
180#[proc_macro]
181pub fn try_cbor(item: TokenStream) -> TokenStream {
182 // Parse the macro input as a block of statements.
183 let input = parse_macro_input!(item as syn::Block);
184 // Construct a new `TokenStream` to accumulate the expanded code.
185 // We use `TokenStream` instead of string because `TokenStream` remembers the source location (span) in case of errors.
186 // `quote!` returns `proc_macro2::TokenStream` instead of `proc_macro::TokenStream`, so we use `proc_macro2::TokenStream`.
187 let mut expanded = proc_macro2::TokenStream::new();
188 for stmt in input.stmts { // For every statement in the block...
189 // Copy the statement into tokens to prevent borrowing problems later.
190 let stmt_tokens = quote! { #stmt };
191 match stmt {
192 // If this is a statement followed by a semicolon...
193 syn::Stmt::Semi(expr, _semi) => {
194 match expr {
195 // If statement is a function call like `func(...)`...
196 syn::Expr::Call(expr) => {
197 let func = *expr.func; // Get the function called.
198 let func = quote! { #func }; // Summarise as token form.
199 // If this is a CBOR encoding call..
200 if func.to_string().starts_with("cbor_encode_") ||
201 func.to_string().starts_with("cbor_encoder_") {
202 // Add error checking to the CBOR statement.
203 let updated_stmt = quote! {
204 let res = mynewt::encoding::tinycbor::#stmt_tokens;
205 COAP_CONTEXT.check_result(res);
206 };
207 // Append updated statement tokens to result.
208 expanded.extend(updated_stmt);
209 continue; // Skip to next statement.
210 }
211 }
212 // If statement is not a function call...
213 _ => {} // TODO
214 }
215 }
216 // If this is a statement not followed by a semicolon...
217 syn::Stmt::Expr(_expr) => {} // TOOD
218 // If this is a `let` statement like `let a = ...`...
219 syn::Stmt::Local(_local) => {} // TODO
220 // If this is an item definition like `const a = ...`...
221 syn::Stmt::Item(_item) => {} // TODO
222 }
223 // If we reach here, this statement is not a CBOR encoding call. Return verbatim.
224 expanded.extend(stmt_tokens); // Append statement tokens to result.
225 }
226 // Wrap the expanded tokens with an `unsafe` block.
227 expanded = quote! {
228 unsafe {
229 #expanded
230 }
231 };
232 // Return the expanded tokens back to the compiler.
233 TokenStream::from(expanded)
234}