Skip to main content

Crate cinrs_core

Crate cinrs_core 

Source
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 one more step on the way: sema hands its body to cfg, which turns it into basic blocks that codegen emits as a state machine. See that module for why, and for what it costs.

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 ir becomes 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
#include resolution: 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.
sema
Semantic analysis: the untyped AST becomes the typed ir.
target
The target data model, and the triples it is derived from.

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 complex feature.
COMPLEX_UNSUPPORTED
What every diagnostic about a complex type says when Options::complex is off.
C_VARIADIC_SUPPORTED
Whether the compiler supports C-variadic definitions and core::ffi::VaList, which Rust stabilised in 1.99.
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 .c file rather than over C written inside the .rs.
expand_with
Expands one c99!-style invocation, with everything the host says about the invocation itself.