Expand description
The cinrs C front end.
This crate holds everything the c89!, c99!, c11!, c17! and c23!
procedural macros do — they are one pipeline parameterised by
Standard — but is built on proc_macro2 alone and never touches
proc_macro. That makes the whole pipeline — input capture, lexing,
preprocessing, parsing, sema and code generation — unit-testable outside a
procedural macro context.
The pipeline is:
TokenStream ──capture──▶ (C source text, SourceMap)
──lex─────▶ Vec<lex::Token> (byte ranges into that text)
──pp──────▶ Vec<pp::Token> (directives gone, macros gone)
──parse───▶ TranslationUnit (every node carries a range)
──sema────▶ ir::Program (typed, conversions explicit)
──codegen─▶ TokenStream (every token spanned at its C)A function that jumps takes two more steps on the way: sema hands its body
to cfg, which turns it into basic blocks, and
reloop reads those back into loops, ifs and matches for
codegen to emit. Most gotos take neither — regions is
the line between an outward jump, which stays a labelled block or a
labelled loop over the statements themselves, and one that does not fit.
Which revision is being compiled reaches every pass that has an opinion
about it: the lexer (which spellings are keywords, and the C23
constant forms), the preprocessor (__VA_OPT__, #elifdef,
__STDC_VERSION__), the parser (the C11 and C23 grammar) and
sema (a C23 keyword used as a name in an older block). A
feature from a later revision is a diagnostic naming the macro that would
have it; see Standard::requires.
Everything downstream reports problems as Diagnostics carrying a
SourceRange; Diagnostics::to_token_stream turns those into
compile_error! invocations whose tokens are spanned at the exact C token
that caused them. That is the point of the whole design: an error — ours or
rustc’s — must land on the C code the user wrote. The lexer takes part in
that by not reporting: its problems ride on the tokens they were found
in, and the preprocessor reports only the ones whose token
survives, because a group skipped by #if 0 may hold anything at all.
A token a macro produced carries the range of the invocation, which is
the only place the user can look; pp::Expansions::annotate adds the
in expansion of macro 'X' note that says how it got there.
Capture also hands sema a unit id identifying
the invocation, which every synthetic name the expansion needs is built
from — the renamed extern objects, the mangled function-local
statics, the names given to anonymous tags, and the module the whole
expansion goes into (see expand), so that two c99! blocks in one Rust
module never collide.
§Where #include fits
A header is another file in the same offset space, so nothing downstream of
the preprocessor has to know it exists: a SourceRange identifies a file
as well as a position, and a diagnostic inside a header resolves to the
span of the #include that pulled it in, with the header’s own path, line
and column put into the message text.
The one seam is that the preprocessor runs on a thread where a
proc_macro2::Span cannot follow it, so it allocates the offsets of the
files it opens and hands them back — see pp::Preprocessed::included and
SourceMap::next_base — and analyze adds them to the map afterwards,
in the order they were opened. The include module is where
a header name is resolved and what is bundled; the user headers that were
read come back as Analysis::user_headers, which expand turns into
the include_str! items that make Cargo rebuild when one changes. C23’s
#embed takes the same route with Analysis::embedded_files and
include_bytes!, since a resource is bytes rather than text.
§Where include_c99! fits
expand_include is expand with a .c file in place of the token
stream: the file becomes the map’s root — see
capture::capture_c_file — and every pass after that is the same one.
Since nothing in the .rs file corresponds to a position in the .c, that
root resolves every range to the span of the macro invocation and carries
its own path:line:column into the message, which is exactly what a header
already does.
§Example
use std::str::FromStr;
use cinrs_core::{expand, Options, Standard};
let input = proc_macro2::TokenStream::from_str("int add(int a, int b) { return a + b; }")
.unwrap();
let out = expand(input, &Options::new(Standard::C99));
assert!(out.to_string().contains("extern \"C\" fn add"));Re-exports§
pub use ast::TranslationUnit;pub use capture::FileId;pub use capture::InputMode;pub use capture::Origin;pub use capture::Pos;pub use capture::Source;pub use capture::SourceMap;pub use capture::SourceRange;pub use capture::Subspan;pub use diag::Diagnostic;pub use diag::Diagnostics;pub use diag::Level;pub use ir::Program;pub use ir::Ty;pub use pp::Token;pub use target::Arch;pub use target::Env;pub use target::Os;pub use target::TargetModel;pub use target::TargetSource;pub use target::UnknownTarget;
Modules§
- ast
- The C99 abstract syntax tree.
- capture
- Input capture and source mapping.
- cfg
- The control-flow-graph lowering: the fallback for the jumps Rust cannot make.
- codegen
- Code generation: the typed
irbecomes Rust tokens. - complex
- Complex arithmetic, for folding a constant expression.
- diag
- Diagnostics: collection and emission as
compile_error!invocations. - dump
- A deterministic, human readable dump of the AST.
- gnu
- The GNU extensions’ name tables.
- include
#includeresolution: where a header is looked for, and what is bundled.- ir
- The typed intermediate representation.
- lex
- A complete C99 lexer working on the captured source text.
- parse
- A recursive-descent parser for the whole C99 grammar.
- pp
- The C99 preprocessor: translation phase 4.
- regions
- The
gotos Rust can express on its own, and the regions they become. - reloop
- Recovering Rust’s own control flow from the graph.
- sema
- Semantic analysis: the untyped AST becomes the typed
ir. - target
- The target data model, and the triples it is derived from.
- x86
- The x86 SIMD intrinsics, and how a C name becomes a
core::archone.
Structs§
- Analysis
- The result of running the front end over one macro invocation.
- Gating
- The pair every pass needs to answer “may this block write that?”.
- Options
- Knobs for one macro expansion.
Enums§
- Dialect
- Whether the GNU extensions that need a plain spelling are switched on.
- Standard
- Which revision of the C standard to accept.
Constants§
- COMPLEX_
SUPPORTED - Whether the complex types are available, which is this crate’s
complexfeature. - COMPLEX_
UNSUPPORTED - What every diagnostic about a complex type says when
Options::complexis off. - TARGET_
ENV_ VAR - The environment variable that names the target the expansion is for.
Functions§
- analyze
- Runs capture, lexing, preprocessing and parsing over
input. - analyze_
with - Runs capture, lexing, preprocessing and parsing over
input, with everything the host says about the invocation itself. - expand
- Expands one
c99!-style invocation. - expand_
include - Expands one
include_c99!("path")invocation: the same translation, over a.cfile rather than over C written inside the.rs. - expand_
with - Expands one
c99!-style invocation, with everything the host says about the invocation itself.