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) }