1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *  http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */
//! Mynewt Macros for calling Mynewt APIs . Import this crate into a source code crate and check the expanded macros using:
//! ```
//! clear ; cargo rustc -- -Z unstable-options --pretty expanded | head -20
//! ```
#![recursion_limit="128"]     //  Increase recursion limit to prevent quote!{} errors
#![feature(proc_macro_span)]  //  Allow use of spans in Procedural Macros

mod safe_wrap;   //  Include safe_wrap.rs
mod infer_type;  //  Include infer_type.rs

extern crate proc_macro;
use proc_macro::TokenStream;
use quote::{quote};
use syn::{
    parse_macro_input,
};

/// Given an `extern "C"` block of function declarations, generate the safe wrapper for the function
#[proc_macro_attribute]
pub fn safe_wrap(attr: TokenStream, item: TokenStream) -> TokenStream {
    safe_wrap::safe_wrap_internal(attr, item)
}

/// Given a Rust function definition, infer the placeholder types in the function
#[proc_macro_attribute]
pub fn infer_type(attr: TokenStream, item: TokenStream) -> TokenStream {
    infer_type::infer_type_internal(attr, item)
}

/// Given a static mutable variable, return an unsafe mutable pointer that's suitable for passing to Mynewt APIs for writing output.
/// `out!(NETWORK_TASK)` expands to `unsafe { &mut NETWORK_TASK }`
#[proc_macro]
pub fn out(item: TokenStream) -> TokenStream {
    //  Parse the macro input as an identifier e.g. `NETWORK_TASK`.
    let input = parse_macro_input!(item as syn::Ident);
    //  Convert the identifier to string.
    let ident = input.to_string();
    //  Compose the macro expansion as a string. `r#"..."#` represents a raw string (for convenience) 
    let expanded = format!(r#"unsafe {{ &mut {} }}"#, ident);  //  `{{` and `}}` will be rendered as `{` and `}`
    //  Parse the string into Rust tokens and return the expanded tokens back to the compiler.
    expanded.parse().unwrap()
}

/// Create a `Strn` containing a null-terminated byte string that's suitable for passing to Mynewt APIs.
/// `strn!("network")` expands to `&Strn::new( b"network\0" )`.
/// `strn!(())` expands to `&Strn::new( b"\0" )`.
/// For macro calls like `strn!( stringify!( value ) )`, return `&Strn::new( b"value\0" )`.
/// For complex macro calls like `strn!( $crate::parse!(@ json device_id) )`, return the parameter as is.
#[proc_macro]
pub fn strn(item: TokenStream) -> TokenStream {
    let item_str = item.to_string();
    let span = proc_macro2::Span::call_site();
    //  println!("item: {:#?}", item_str);
    if item_str.replace(" ", "") == "()" {
        //  Expand  `(  )` (old Rust) and `()` (new Rust) to `&Strn::new( b"\0" )`
        let expanded = quote! {
            &Strn::new( b"\0" )
        };
        return expanded.into();
    } else if item_str.contains("parse!") {
        //  If `item_str` looks like `$crate::parse!(@ json device_id)` (old Rust) or `::mynewt::parse!(@ json &device_id)` (new Rust), return as is.
        return item;
    } else if item_str.starts_with("\"") && item_str.ends_with("\"") {
        //  Transform literal `"\"device\""` to `&Strn::new( b"device\0" )`
        let item_split: Vec<&str> = item_str.splitn(3, "\"").collect();
        let lit = item_split[1].to_string() + "\0";            
        //  println!("lit: {:#?}", lit);
        let bytestr = syn::LitByteStr::new(lit.as_bytes(), span);
        let expanded = quote! {
            &Strn::new( #bytestr )
        };
        return expanded.into();
    }
    let expr = parse_macro_input!(item as syn::Expr);
    let expr_str = quote! { #expr }.to_string();
    match expr {
        syn::Expr::Macro(_expr) => {
            //  Transform macro `stringify ! ( value )` to `&Strn::new( b"value\0" )`
            //  macOS gives `stringify ! ( value )` but some Window machines give `stringify ! (value)`.
            let expr_split: Vec<&str> = expr_str.splitn(2, "stringify ! (").collect();
            let ident = expr_split[1].trim();
            let ident_split: Vec<&str> = ident.splitn(2, ")").collect();
            let ident = ident_split[0].trim().to_string() + "\0";            
            //  println!("ident: {:#?}", ident);
            let bytestr = syn::LitByteStr::new(ident.as_bytes(), span);
            let expanded = quote! {
                &Strn::new( #bytestr )
            };
            return expanded.into();
        }
        syn::Expr::Lit(_expr) => {
            //  Literals already handled above. Should not come here.
            assert!(false, "strn lit");  //  Not supported
            let expanded = quote! { &Strn::new( b"\0" ) };
            return expanded.into();
        }
        syn::Expr::Try(expr) => {
            //  Handle `strn!( fn() ? )`
            let expanded = quote! { &Strn::new( #expr ) };
            return expanded.into();
        }
        _ => {}
    };
    //  println!("strn: {:#?}", expr);
    //  println!("strn3: {:#?}", expr_str);
    assert!(false, "strn!() pattern not supported: {}", item_str);  //  Not supported
    let expanded = quote! { &Strn::new( b"\0" ) };
    expanded.into()
}

/// Initialise a null-terminated bytestring `Strn` that's suitable for passing to Mynewt APIs
/// `init_strn!("network")` expands to `Strn{ rep: ByteStr(b"network\0") }`
/// Used like this:
/// ```
/// static STATIC_STRN: Strn = init_strn!("network");
/// let local_strn = init_strn!("network");
/// ```
#[proc_macro]
pub fn init_strn(item: TokenStream) -> TokenStream {
    //  Parse the macro input as a literal string e.g. `"network"`.
    let input = parse_macro_input!(item as syn::LitStr);
    let span = proc_macro2::Span::call_site();

    //  Get the literal string value and terminate with null. Convert to bytestring.
    let val = input.value().to_string() + "\0";
    let bytestr = syn::LitByteStr::new(val.as_bytes(), span);

    //  Compose the macro expansion as tokens.
    let expanded = quote! {
        Strn {
            rep: mynewt::StrnRep::ByteStr(#bytestr)
        }
    };
    //  Return the expanded tokens back to the Rust compiler.
    return expanded.into();
}

/// Transform a block of CBOR encoding calls by adding error checking. All lines must terminate with `;`
/// ```
/// try_cbor!({
///     let encoder = COAP_CONTEXT.encoder("COAP_CONTEXT", "_map");
///     cbor_encode_text_string(
///         encoder,
///         COAP_CONTEXT.key_to_cstr(key_with_opt_null),
///         COAP_CONTEXT.cstr_len(key_with_opt_null));
///     cbor_encode_int(encoder, value);
/// })
/// ```
/// expands to:
/// ```
/// unsafe {
///     let encoder = COAP_CONTEXT.encoder("COAP_CONTEXT", "_map");
///     let res =
///         tinycbor::cbor_encode_text_string(encoder,
///           COAP_CONTEXT.key_to_cstr(key_with_opt_null),
///           COAP_CONTEXT.cstr_len(key_with_opt_null));
///     COAP_CONTEXT.check_result(res);
///     let res = tinycbor::cbor_encode_int(encoder, value);
///     COAP_CONTEXT.check_result(res);
/// }
/// ```
#[proc_macro]
pub fn try_cbor(item: TokenStream) -> TokenStream {
    //  Parse the macro input as a block of statements.
    let input = parse_macro_input!(item as syn::Block);
    //  Construct a new `TokenStream` to accumulate the expanded code.
    //  We use `TokenStream` instead of string because `TokenStream` remembers the source location (span) in case of errors.
    //  `quote!` returns `proc_macro2::TokenStream` instead of `proc_macro::TokenStream`, so we use `proc_macro2::TokenStream`.
    let mut expanded = proc_macro2::TokenStream::new();
    for stmt in input.stmts {  //  For every statement in the block...
        //  Copy the statement into tokens to prevent borrowing problems later.
        let stmt_tokens = quote! { #stmt };
        match stmt {
            //  If this is a statement followed by a semicolon...
            syn::Stmt::Semi(expr, _semi) => {
                match expr {
                    //  If statement is a function call like `func(...)`...
                    syn::Expr::Call(expr) => {                        
                        let func = *expr.func;        //  Get the function called.
                        let func = quote! { #func };  //  Summarise as token form.
                        //  If this is a CBOR encoding call..
                        if func.to_string().starts_with("cbor_encode_") ||
                            func.to_string().starts_with("cbor_encoder_") {
                            //  Add error checking to the CBOR statement.
                            let updated_stmt = quote! { 
                                let res = mynewt::encoding::tinycbor::#stmt_tokens;
                                COAP_CONTEXT.check_result(res);
                            };
                            //  Append updated statement tokens to result.
                            expanded.extend(updated_stmt);  
                            continue;  //  Skip to next statement.
                        }
                    }
                    //  If statement is not a function call...
                    _ => {}  //  TODO
                }            
            }
            //  If this is a statement not followed by a semicolon...
            syn::Stmt::Expr(_expr) => {}  //  TOOD
            //  If this is a `let` statement like `let a = ...`...
            syn::Stmt::Local(_local) => {}  //  TODO
            //  If this is an item definition like `const a = ...`...
            syn::Stmt::Item(_item) => {}  //  TODO
        }
        //  If we reach here, this statement is not a CBOR encoding call.  Return verbatim.
        expanded.extend(stmt_tokens);  //  Append statement tokens to result.
    }
    //  Wrap the expanded tokens with an `unsafe` block.
    expanded = quote! {
        unsafe {
            #expanded
        }
    };
    //  Return the expanded tokens back to the compiler.
    TokenStream::from(expanded)
}