inline_c_macro/lib.rs
1//! Please see the `inline-c` crate to learn more.
2
3#![cfg_attr(nightly, feature(proc_macro_span))]
4
5use proc_macro2::TokenStream;
6use quote::quote;
7
8/// Execute a C program and return a `Result` of
9/// `inline_c::Assert`. See examples inside the `inline-c` crate.
10#[proc_macro]
11pub fn assert_c(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
12 let input = TokenStream::from(input);
13 let input_as_string = reconstruct(input);
14
15 quote!(
16 inline_c::run(inline_c::Language::C, #input_as_string).map_err(|e| panic!("{}", e)).unwrap()
17 )
18 .into()
19}
20
21/// Execute a C++ program and return a `Result` of
22/// `inline_c::Assert`. See examples inside the `inline-c` crate.
23#[proc_macro]
24pub fn assert_cxx(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
25 let input = TokenStream::from(input);
26 let input_as_string = reconstruct(input);
27
28 quote!(
29 inline_c::run(inline_c::Language::Cxx, #input_as_string).map_err(|e| panic!("{}", e)).unwrap()
30 )
31 .into()
32}
33
34fn reconstruct(input: TokenStream) -> String {
35 use proc_macro2::{Delimiter, Spacing, TokenTree::*};
36
37 let mut output = String::new();
38 let mut iterator = input.into_iter().peekable();
39
40 loop {
41 match iterator.next() {
42 Some(Punct(token)) => {
43 let token_value = token.as_char();
44
45 match token_value {
46 '#' => {
47 output.push('\n');
48 output.push(token_value);
49
50 match iterator.peek() {
51 // #include …
52 Some(Ident(include)) if *include == "include" => {
53 iterator.next();
54
55 match iterator.next() {
56 // #include <…>
57 Some(Punct(punct)) => {
58 if punct.as_char() != '<' {
59 panic!(
60 "Invalid opening token after `#include`, received `{:?}`.",
61 token
62 )
63 }
64
65 output.push_str("include <");
66
67 loop {
68 match iterator.next() {
69 Some(Punct(punct)) => {
70 let punct = punct.as_char();
71
72 if punct == '>' {
73 break;
74 }
75
76 output.push(punct)
77 }
78
79 Some(Ident(ident)) => {
80 output.push_str(&ident.to_string())
81 }
82
83 token => panic!(
84 "Invalid token in `#include` value, with `{:?}`.",
85 token
86 ),
87 }
88 }
89
90 output.push('>');
91 output.push('\n');
92 }
93
94 // #include "…"
95 Some(Literal(literal)) => {
96 output.push_str("include ");
97 output.push_str(&literal.to_string());
98 output.push('\n');
99 }
100
101 Some(token) => panic!(
102 "Invalid opening token after `#include`, received `{:?}`.",
103 token
104 ),
105
106 None => panic!("`#include` must be followed by `<` or `\"`."),
107 }
108 }
109
110 // #define, only available on nightly.
111 Some(Ident(define)) if *define == "define" => {
112 #[cfg(not(nightly))]
113 panic!(
114 "`#define` in C is only supported in `inline-c` with Rust nightly"
115 );
116
117 #[cfg(nightly)]
118 {
119 // `Span::start()` doesn't work on `proc_macro2`, we need to fallback to
120 // `proc_macro` itself, see https://github.com/dtolnay/proc-macro2/issues/402.
121 let current_line = define.span().unwrap().start().line();
122 iterator.next();
123 output.push_str("define ");
124
125 loop {
126 match iterator.peek() {
127 Some(item) => {
128 if item.span().unwrap().start().line()
129 == current_line
130 {
131 output.push_str(&item.to_string());
132 iterator.next();
133 } else {
134 output.push('\n');
135 break;
136 }
137 }
138
139 None => break,
140 }
141 }
142 }
143 }
144
145 _ => (),
146 }
147 }
148
149 ';' => {
150 output.push(token_value);
151 output.push('\n');
152 }
153
154 _ => {
155 output.push(token_value);
156
157 if token.spacing() == Spacing::Alone {
158 output.push(' ');
159 }
160 }
161 }
162 }
163
164 Some(Ident(ident)) => {
165 output.push_str(&ident.to_string());
166 output.push(' ');
167 }
168
169 Some(Group(group)) => {
170 let group_output = reconstruct(group.stream());
171
172 match group.delimiter() {
173 Delimiter::Parenthesis => {
174 output.push('(');
175 output.push_str(&group_output);
176 output.push(')');
177 }
178
179 Delimiter::Brace => {
180 output.push('{');
181 output.push('\n');
182 output.push_str(&group_output);
183 output.push('\n');
184 output.push('}');
185 }
186
187 Delimiter::Bracket => {
188 output.push('[');
189 output.push_str(&group_output);
190 output.push(']');
191 }
192
193 Delimiter::None => {
194 output.push_str(&group_output);
195 }
196 }
197 }
198
199 Some(token) => {
200 output.push_str(&token.to_string());
201 }
202
203 None => break,
204 }
205 }
206
207 output
208}