Skip to main content

cinrs_macros/
lib.rs

1//! The procedural macro front end for [`cinrs`](https://docs.rs/cinrs).
2//!
3//! Deliberately a thin shim: everything interesting lives in `cinrs-core`,
4//! which is built on `proc-macro2` and can therefore be tested without a
5//! procedural macro context.
6//!
7//! # The `nightly` feature
8//!
9//! `cinrs-core` never touches `proc_macro`, so the one thing it cannot do by
10//! itself is build a span that points *inside* a string literal —
11//! `proc_macro::Literal::subspan` is unstable on every channel, and
12//! `proc-macro2` does not expose it. With the `nightly` feature this crate
13//! turns on `#![feature(proc_macro_span)]` and hands the front end a
14//! [`Subspan`] hook that does exactly that, so a `c99! { r#"…"# }` body reports
15//! its errors on the offending C token rather than on the whole literal with
16//! the position spelled out in the message. Everything else is unchanged, and
17//! without the feature — or wherever `subspan` declines to answer, as under
18//! `rust-analyzer` — the stable behaviour comes back on its own.
19
20#![cfg_attr(feature = "nightly", feature(proc_macro_span))]
21
22use proc_macro::TokenStream;
23
24use cinrs_core::{Options, Standard, Subspan};
25
26/// Compiles a C89 (C90) translation unit written inside Rust.
27///
28/// The oldest entry point, and the one written for code that predates the 1999
29/// standard: **implicit `int`** (`static x;`, `f() { … }`), **implicit
30/// function declarations** (calling `abs` with nothing declaring it declares
31/// `extern int abs();` from that point on) and **old-style (K&R) function
32/// definitions** all work here, and everything C99 added is a diagnostic that
33/// says so:
34///
35/// ```text
36/// error: a '//' comment requires C99 or later (this block is c89!)
37/// ```
38///
39/// What is gated: `//` comments, mixed declarations and code, a declaration in
40/// a `for` clause, variable length arrays, `_Bool`, `restrict`, `inline`,
41/// `long long`, designated initializers, compound literals, variadic macros,
42/// flexible array members, hexadecimal floating constants, `__func__`,
43/// `_Pragma`, universal character names, a trailing comma in an enumerator
44/// list, `static` and `[*]` in an array parameter declarator, `_Complex` and
45/// an imaginary constant (`2.0i`).
46/// The *library* additions are not: a bundled header is a set of declarations,
47/// and `snprintf` is one of them — with `<complex.h>` the one exception, since
48/// every declaration in it names a type C89 does not have.
49///
50/// `__STDC_VERSION__` is **not defined** — C89 as published had no such macro
51/// — while `__STDC__` is `1`, exactly as in `gcc -std=c89`.
52/// [`c90!`](macro@c90) is another name for this macro, and
53/// [`gnu89!`](macro@gnu89) is the GNU dialect of it.
54#[proc_macro]
55pub fn c89(input: TokenStream) -> TokenStream {
56    expand(input, Standard::C89)
57}
58
59/// Compiles a C90 translation unit written inside Rust.
60///
61/// ISO/IEC 9899:1990 is ANSI X3.159-1989 republished with no technical change,
62/// so this is [`c89!`](macro@c89) under the other name the language has.
63#[proc_macro]
64pub fn c90(input: TokenStream) -> TokenStream {
65    expand(input, Standard::C89)
66}
67
68/// Compiles a C99 translation unit written inside Rust.
69///
70/// The body may be written as raw Rust tokens:
71///
72/// ```ignore
73/// cinrs::c99! {
74///     int add(int a, int b) { return a + b; }
75/// }
76/// ```
77///
78/// or, for C that the Rust lexer refuses (hex float literals, `L"…"`,
79/// multi-character character constants, `\` line continuations, `##`), as a
80/// single string literal:
81///
82/// ```ignore
83/// cinrs::c99! { r#"
84///     int add(int a, int b) { return a + b; }
85/// "# }
86/// ```
87///
88/// The expansion is a private module of its own plus a glob re-export of it,
89/// so that two blocks in one Rust module may share a header without their
90/// types colliding. Each function becomes a `pub unsafe extern "C" fn` of the
91/// same name, taking and returning the [`core::ffi`] types, so the C above is
92/// called as `unsafe { add(1, 2) }`; a C `static` function stays private to
93/// the module, and a file-scope variable becomes a `static mut` item. A
94/// `struct`, `union` or `enum` becomes a `#[repr(C)]` item Rust code can use
95/// directly, and a function the unit only declares becomes an `extern "C"`
96/// declaration linked against the real symbol.
97///
98/// The C99 preprocessor runs first — every directive, macros with `#`, `##`
99/// and `__VA_ARGS__` included. `#include` finds the headers `cinrs` bundles
100/// (`<stdio.h>`, `<string.h>`, `<math.h>` and the rest, written in plain C99
101/// rather than read from the platform) and the user's own, in the directory of
102/// the invoking `.rs` file and in whatever `#pragma cinrs include_path` adds.
103/// Since Rust's lexer refuses `##` in raw-token form, a replacement list may
104/// write the pasting operator as `a # # b` instead; see [Input forms] for that,
105/// [the preprocessor] for the predefined macros, and [`#include` and `#embed`]
106/// for the bundled headers.
107///
108/// [Input forms]: https://github.com/tanakh/cinrs/blob/master/doc/features.md#input-forms
109/// [the preprocessor]: https://github.com/tanakh/cinrs/blob/master/doc/features.md#the-preprocessor
110/// [`#include` and `#embed`]: https://github.com/tanakh/cinrs/blob/master/doc/features.md#include-and-embed
111///
112/// Errors are reported at the exact C token that caused them. In string
113/// literal form, stable Rust cannot build a span pointing inside a literal, so
114/// the position inside the C source is appended to the message instead — see
115/// [the `nightly` feature](self#the-nightly-feature) for the way around that.
116///
117/// [`c11!`](macro@c11), [`c17!`](macro@c17) and [`c23!`](macro@c23) are the
118/// same macro for a later revision of the language.
119#[proc_macro]
120pub fn c99(input: TokenStream) -> TokenStream {
121    expand(input, Standard::C99)
122}
123
124/// Compiles a C11 translation unit written inside Rust.
125///
126/// Everything [`c99!`](macro@c99) does, plus what C11 added:
127/// `_Static_assert`, `_Alignof`, `_Alignas` (on the members of a `struct` or
128/// `union`), `_Generic`, `_Noreturn`, `_Thread_local`, `_Atomic` with
129/// `<stdatomic.h>`, anonymous `struct`/`union` members, and
130/// the Unicode literals `u8"…"`, `u"…"`, `U"…"`, `u'x'` and `U'x'` with
131/// `<uchar.h>`'s `char16_t` and `char32_t`, and the `CMPLX` family in
132/// `<complex.h>`. `__STDC_VERSION__` is `201112L`.
133///
134/// C11's threads are here too, as the platform's own: `<threads.h>` is
135/// bundled for the C libraries whose objects it can lay out — glibc and musl,
136/// both on Linux — and is an `#error` naming the reason elsewhere, which is
137/// where `__STDC_NO_THREADS__` is predefined. An `_Atomic` `struct` is a clear
138/// error rather than a silent mistranslation — it would need a lock, and there
139/// is nothing in the generated Rust to be one.
140#[proc_macro]
141pub fn c11(input: TokenStream) -> TokenStream {
142    expand(input, Standard::C11)
143}
144
145/// Compiles a C17 translation unit written inside Rust.
146///
147/// C17 is C11 with the defect reports applied and no new features, so this is
148/// [`c11!`](macro@c11) with `__STDC_VERSION__` set to `201710L`.
149#[proc_macro]
150pub fn c17(input: TokenStream) -> TokenStream {
151    expand(input, Standard::C17)
152}
153
154/// Compiles a C23 translation unit written inside Rust.
155///
156/// Everything [`c11!`](macro@c11) does, plus the C23 keywords (`bool`, `true`,
157/// `false`, `nullptr`, `static_assert`, `alignof`, `alignas`, `constexpr`,
158/// `typeof`), `[[…]]` attributes, `__VA_OPT__`, `#elifdef` / `#elifndef`,
159/// binary constants, digit separators, empty initialisers, `auto` type
160/// inference, enumerations with a fixed underlying type, improved tag
161/// compatibility (a tag defined twice in one scope with the same members is
162/// one type), `unreachable()`,
163/// `#embed`, `char8_t` and the `u8'x'` character prefix.
164/// `__STDC_VERSION__` is `202311L`.
165///
166/// It is also the one entry point that has **no trigraphs**, which is what
167/// C23 removed: `??=` here is two question marks and an `=`.
168///
169/// A digit separator (`1'000'000`) needs string-literal form: Rust's own lexer
170/// reads `1'000` as a literal followed by a lifetime and refuses it.
171#[proc_macro]
172pub fn c23(input: TokenStream) -> TokenStream {
173    expand(input, Standard::C23)
174}
175
176/// Compiles a C99 translation unit with the GNU extensions switched on.
177///
178/// Everything [`c99!`](macro@c99) does, plus what GCC's `-std=gnu99` adds over
179/// its `-std=c99`: the plain spellings `typeof` and `asm` are keywords, and a
180/// construct a later revision introduced — `_Static_assert`, `_Generic`, a
181/// `0b` literal — is accepted rather than being told which macro to write.
182///
183/// The extensions spelled with a leading double underscore — `__typeof__`,
184/// `__attribute__`, `__extension__`, `__builtin_*`, `__restrict` — are
185/// available in *every* entry point, exactly as they are in GCC's strict
186/// modes: the names are reserved, so nothing a program may legally call its
187/// own is taken away. `__STRICT_ANSI__` is defined only in the strict entry
188/// points; `__GNUC__` is 4 in all of them. A GNU dialect also switches
189/// **trigraphs off**, as `gcc -std=gnu99` does, so `"what??!"` there is an
190/// exclamation rather than a pipe.
191///
192/// See [`doc/gnu-extensions.md`](https://github.com/tanakh/cinrs/blob/master/doc/gnu-extensions.md)
193/// in the repository for the whole catalogue.
194#[proc_macro]
195pub fn gnu99(input: TokenStream) -> TokenStream {
196    expand_gnu(input, Standard::C99)
197}
198
199/// Compiles a C89 translation unit with the GNU extensions switched on.
200///
201/// What `gcc -std=gnu89` is: **everything a later revision added is accepted**
202/// — `//` comments, mixed declarations and code, `long long`, designated
203/// initializers and the rest, which C89 as an ISO document does not have and
204/// GCC has always taken as extensions — *and* the three C89 rules that are not
205/// a matter of extension at all, because a later revision deleted them:
206/// implicit `int`, implicit function declarations, and old-style (K&R)
207/// definitions (which every entry point below `c23!` has).
208///
209/// So `gnu89!` is [`gnu99!`](macro@gnu99) plus those three, with
210/// `__STDC_VERSION__` left undefined; it is the entry point for the C of the
211/// 1990s, and the one the GCC torture suite is measured with.
212#[proc_macro]
213pub fn gnu89(input: TokenStream) -> TokenStream {
214    expand_gnu(input, Standard::C89)
215}
216
217/// Compiles a C11 translation unit with the GNU extensions switched on.
218///
219/// [`c11!`](macro@c11) plus what [`gnu99!`](macro@gnu99) adds.
220#[proc_macro]
221pub fn gnu11(input: TokenStream) -> TokenStream {
222    expand_gnu(input, Standard::C11)
223}
224
225/// Compiles a C17 translation unit with the GNU extensions switched on.
226///
227/// [`c17!`](macro@c17) plus what [`gnu99!`](macro@gnu99) adds.
228#[proc_macro]
229pub fn gnu17(input: TokenStream) -> TokenStream {
230    expand_gnu(input, Standard::C17)
231}
232
233/// Compiles a C23 translation unit with the GNU extensions switched on.
234///
235/// [`c23!`](macro@c23) plus what [`gnu99!`](macro@gnu99) adds.
236#[proc_macro]
237pub fn gnu23(input: TokenStream) -> TokenStream {
238    expand_gnu(input, Standard::C23)
239}
240
241/// Compiles the C89 (C90) file at `path`.
242///
243/// [`include_c99!`](macro@include_c99) documents the whole family; this is the
244/// [`c89!`](macro@c89) entry point of it, so implicit `int`, implicit function
245/// declarations and old-style definitions are what the file may use, and
246/// everything C99 added is a diagnostic.
247#[proc_macro]
248pub fn include_c89(input: TokenStream) -> TokenStream {
249    include(input, Options::new(Standard::C89))
250}
251
252/// Compiles the C90 file at `path`; see
253/// [`include_c99!`](macro@include_c99) and [`c90!`](macro@c90).
254#[proc_macro]
255pub fn include_c90(input: TokenStream) -> TokenStream {
256    include(input, Options::new(Standard::C89))
257}
258
259/// Compiles the C99 file at `path` — one translation unit, exactly as if its
260/// text had been written inside [`c99!`](macro@c99).
261///
262/// ```ignore
263/// cinrs::include_c99!("vendor/parser.c");
264/// ```
265///
266/// The file is read at expansion time and translated with the same front end:
267/// every C construct is accepted (the file is text, so the lexemes Rust's own
268/// lexer refuses are no trouble), `#pragma cinrs …` inside it configures the
269/// unit, `#include "…"` in it searches the file's own directory first, and the
270/// expansion is a module plus a glob re-export like any other invocation's.
271/// `__FILE__` and `__LINE__` name the `.c` file and its own lines.
272///
273/// A **relative path is resolved against the directory of the `.rs` file the
274/// macro is written in** — the same rule `#include "…"` follows — and an
275/// absolute one is used as it stands. The file is named with `include_str!` in
276/// the expansion, so editing it rebuilds the crate.
277///
278/// **Diagnostics land on the invocation.** There is no C in the `.rs` file for
279/// a caret to point at, so a message of this crate's carries the position
280/// inside the file — `vendor/parser.c:12:5: unknown type name 'foo'` — and an
281/// error `rustc` raises about the generated code is reported at the macro call.
282/// Neither `cargo` nor an IDE will jump into the `.c` file; the position is in
283/// the text of the message.
284///
285/// There is one of these per entry point: `include_c89!`, `include_c90!`,
286/// `include_c11!`, `include_c17!`, `include_c23!` and the five `include_gnu…!`
287/// forms.
288#[proc_macro]
289pub fn include_c99(input: TokenStream) -> TokenStream {
290    include(input, Options::new(Standard::C99))
291}
292
293/// Compiles the C11 file at `path`; see
294/// [`include_c99!`](macro@include_c99) and [`c11!`](macro@c11).
295#[proc_macro]
296pub fn include_c11(input: TokenStream) -> TokenStream {
297    include(input, Options::new(Standard::C11))
298}
299
300/// Compiles the C17 file at `path`; see
301/// [`include_c99!`](macro@include_c99) and [`c17!`](macro@c17).
302#[proc_macro]
303pub fn include_c17(input: TokenStream) -> TokenStream {
304    include(input, Options::new(Standard::C17))
305}
306
307/// Compiles the C23 file at `path`; see
308/// [`include_c99!`](macro@include_c99) and [`c23!`](macro@c23).
309#[proc_macro]
310pub fn include_c23(input: TokenStream) -> TokenStream {
311    include(input, Options::new(Standard::C23))
312}
313
314/// Compiles the C89 file at `path` with the GNU extensions switched on; see
315/// [`include_c99!`](macro@include_c99) and [`gnu89!`](macro@gnu89).
316#[proc_macro]
317pub fn include_gnu89(input: TokenStream) -> TokenStream {
318    include(input, Options::gnu(Standard::C89))
319}
320
321/// Compiles the C99 file at `path` with the GNU extensions switched on; see
322/// [`include_c99!`](macro@include_c99) and [`gnu99!`](macro@gnu99).
323#[proc_macro]
324pub fn include_gnu99(input: TokenStream) -> TokenStream {
325    include(input, Options::gnu(Standard::C99))
326}
327
328/// Compiles the C11 file at `path` with the GNU extensions switched on; see
329/// [`include_c99!`](macro@include_c99) and [`gnu11!`](macro@gnu11).
330#[proc_macro]
331pub fn include_gnu11(input: TokenStream) -> TokenStream {
332    include(input, Options::gnu(Standard::C11))
333}
334
335/// Compiles the C17 file at `path` with the GNU extensions switched on; see
336/// [`include_c99!`](macro@include_c99) and [`gnu17!`](macro@gnu17).
337#[proc_macro]
338pub fn include_gnu17(input: TokenStream) -> TokenStream {
339    include(input, Options::gnu(Standard::C17))
340}
341
342/// Compiles the C23 file at `path` with the GNU extensions switched on; see
343/// [`include_c99!`](macro@include_c99) and [`gnu23!`](macro@gnu23).
344#[proc_macro]
345pub fn include_gnu23(input: TokenStream) -> TokenStream {
346    include(input, Options::gnu(Standard::C23))
347}
348
349/// The body every strict entry point shares.
350fn expand(input: TokenStream, standard: Standard) -> TokenStream {
351    run(input, Options::new(standard))
352}
353
354/// The body every GNU entry point shares.
355fn expand_gnu(input: TokenStream, standard: Standard) -> TokenStream {
356    run(input, Options::gnu(standard))
357}
358
359/// The body every `include_…!` entry point shares.
360///
361/// No [`Subspan`] hook: the C is in a file of its own, and there is nothing in
362/// the `.rs` for a span to point into.
363fn include(input: TokenStream, options: Options) -> TokenStream {
364    cinrs_core::expand_include(input.into(), &options).into()
365}
366
367fn run(input: TokenStream, options: Options) -> TokenStream {
368    let subspan = subspan(&input);
369    // `Options::origin` is what tells the front end which macro this is and
370    // which directory Cargo is building, so that a host reporting no positions
371    // for its tokens — `rust-analyzer` — can still be shown the invocation's own
372    // text: see `cinrs_core::Origin`.
373    let origin = options.origin().with_subspan(subspan);
374    cinrs_core::expand_with(input.into(), &options, &origin).into()
375}
376
377/// A hook resolving a byte range of a string-literal body into a span.
378///
379/// There is one only when the whole input is a single literal, which is what
380/// string-literal mode is; everything else already has a span per token.
381#[cfg(feature = "nightly")]
382fn subspan(input: &TokenStream) -> Option<Subspan> {
383    let mut trees = input.clone().into_iter();
384    let (Some(proc_macro::TokenTree::Literal(literal)), None) = (trees.next(), trees.next()) else {
385        return None;
386    };
387    Some(Subspan::new(move |range| {
388        literal.subspan(range).map(Into::into)
389    }))
390}
391
392/// Without the `nightly` feature there is nothing to hook up, and
393/// string-literal input keeps reporting positions in the message text.
394#[cfg(not(feature = "nightly"))]
395fn subspan(_input: &TokenStream) -> Option<Subspan> {
396    None
397}