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}