Skip to main content

Crate rucc_pp

Crate rucc_pp 

Source
Expand description

Macro expansion, conditionals, include resolution, and the header cache.

Design: spec/05-preprocessor.md. Layer rank 5, see spec/18-package-layout.md.

§Status

Macro expansion is implemented: object-like and function-like macros, # and ##, variadics in both the standard and the GNU spelling, __VA_OPT__, and the GNU comma swallowing extension, all on hide sets rather than on a depth counter.

Every token that comes out of a macro carries the chain of macros it came out of, so an error inside a macro three headers deep prints the way to it rather than only the two ends of it. The chain is interned, so a hundred token replacement list costs one node.

Directives are implemented: #define, #undef, the whole conditional family with the #if expression evaluator, #error, #warning, #line, #pragma, the _Pragma operator, and #include and #include_next against a search path that follows GCC’s order. #embed produces its bytes as tokens, and the fast path that avoids making them at all waits on the parser.

A header is read once. #pragma once and the multiple include optimization, which spots the ordinary #ifndef wrapper and skips the file rather than reading it and throwing the result away, both do that.

The __has_* family is implemented. __has_include and __has_include_next ask the search path the same question the directive on the same line would ask it, and the rest answer out of the matrix in rucc-gnu, which means they answer no for almost everything until the parser lands. That is the point of them. They answer in ordinary text as well as in a #if, because both GCC and clang make them builtin macros rather than something only the conditional parser knows about. The exception is the three whose operand is a header name, __has_include, __has_include_next and __has_embed, which both compilers refuse outside a directive and so does this one.

The predefined macro set is generated from the target description rather than hardcoded, and arrives as two synthetic files, <built-in> and <command-line>, so that a diagnostic about one of them says where it came from. __DATE__ and __TIME__ are in it because they are fixed for a translation unit. The ones that are not fixed, __FILE__, __FILE_NAME__, __BASE_FILE__, __LINE__, __INCLUDE_LEVEL__ and __COUNTER__, are answered by the expander out of the source map at the place they are used.

print writes the token stream back out the way -E does, with GCC’s line markers, GCC’s blank line padding, the indentation the source had, and a space wherever two tokens would otherwise read back as one. -P turns the markers and the padding off.

use rucc_base::Interner;
use rucc_diag::SourceMap;
use rucc_lex::PpTokenKind;
use rucc_pp::{Context, Preprocessor};
use rucc_session::{MemoryFileSystem, SearchPath};

let mut fs = MemoryFileSystem::new();
fs.insert("/square.h", b"#define SQUARE(x) ((x) * (x))\n".to_vec());

let mut interner = Interner::new();
let mut sources = SourceMap::new();
let main = b"#include \"square.h\"\n#if SQUARE(2) == 4\nSQUARE(3)\n#endif\n";
let file = sources.add("/main.c", main.to_vec())?;

let search = SearchPath::new();
let mut cx = Context::new(&mut interner, &mut sources, &fs, &search);
let mut pp = Preprocessor::new();
let out = pp.run(file, &mut cx);
assert!(pp.diagnostics().is_empty());

let spelled: Vec<&str> = out
    .iter()
    .map(|t| match t.kind {
        PpTokenKind::Punct(p) => p.as_str(),
        _ => interner.resolve(t.value.unwrap()),
    })
    .collect();
assert_eq!(spelled.concat(), "((3)*(3))");

Every crate in the workspace is published, and publishing implies a promise. This one is tier 3: its Rust API is explicitly unstable and will change without a major version bump. Depend on the rucc binary’s behaviour, not on this.

Structs§

Context
Everything phase 4 needs from outside itself.
Expander
Macro expansion state that outlives a single expansion.
GnucVersion
The GCC release the compiler claims to be, as __GNUC__, __GNUC_MINOR__ and __GNUC_PATCHLEVEL__.
HideSet
An interned set of macro names.
HideSets
The interning table for hide sets.
LineDirective
A #line directive, kept for the source map to apply.
MacroDef
A #define.
MacroTable
Every macro currently defined.
Predef
Everything the predefined set is built from that is not the target.
Preprocessor
Translation phase 4 over one file.
PrintOptions
What -E was asked for.
Step
One macro traversed on the way from the user’s text to a token.
Timestamp
The translation date, as __DATE__ and __TIME__ spell it.
Tok
A token in flight through macro expansion.
TraceId
A pointer into a Traces table, or TraceId::NONE for a token the user wrote.
Traces
The interning table for expansion traces.

Enums§

Builtin
A predefined macro whose value is a question rather than a replacement list.

Constants§

BUILT_IN
The name a diagnostic about the generated set points at.
COMMAND_LINE
The name a diagnostic about -D or -U points at.
MILESTONE
The milestone in spec/17-milestones.md that fills this crate in.

Functions§

dump_macros
Every macro that is defined, as #define lines, sorted by name and newline terminated.
parse_define
Parses the tokens after #define into a definition.
print
Renders tokens the way -E prints them.