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}