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