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}