use super::ast::*;
use super::lexer;
use super::token::*;
use super::CLangStandard;
use std::collections::HashMap;
pub type MacroDefinition = MacroDef;
pub type PreprocessorState = Preprocessor;
#[derive(Debug, Clone)]
pub struct MacroDef {
pub name: String,
pub params: Vec<String>,
pub body: Vec<Token>,
pub is_function_like: bool,
pub is_variadic: bool,
pub def_location: SourceLoc,
}
impl MacroDef {
pub fn object_like(name: &str, body: Vec<Token>) -> Self {
Self {
name: name.to_string(),
params: Vec::new(),
body,
is_function_like: false,
is_variadic: false,
def_location: SourceLoc::unknown(),
}
}
pub fn function_like(
name: &str,
params: Vec<String>,
body: Vec<Token>,
is_variadic: bool,
) -> Self {
Self {
name: name.to_string(),
params,
body,
is_function_like: true,
is_variadic,
def_location: SourceLoc::unknown(),
}
}
pub fn param_is_raw_in_replacement(&self, param: &str) -> bool {
for i in 0..self.body.len() {
let tok = &self.body[i];
if tok.kind == TokenKind::Hash
&& i + 1 < self.body.len()
&& self.body[i + 1].text == param
{
return true;
}
if tok.text == param {
let left_paste = i > 0 && self.body[i - 1].kind == TokenKind::HashHash;
let right_paste =
i + 1 < self.body.len() && self.body[i + 1].kind == TokenKind::HashHash;
if left_paste || right_paste {
return true;
}
}
}
false
}
}
#[derive(Debug, Clone)]
pub struct Preprocessor {
pub defines: HashMap<String, MacroDef>,
pub include_paths: Vec<String>,
pub standard: CLangStandard,
pub output: Vec<Token>,
pub errors: Vec<String>,
counter: u32,
cond_stack: Vec<ConditionalState>,
current_file: String,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum ConditionalState {
Active,
SawTrue,
Skipped,
}
impl Preprocessor {
pub fn new(standard: CLangStandard) -> Self {
let mut pp = Self {
defines: HashMap::new(),
include_paths: Vec::new(),
standard,
output: Vec::new(),
errors: Vec::new(),
counter: 0,
cond_stack: Vec::new(),
current_file: String::new(),
};
pp.add_builtin_defines();
pp
}
pub fn add_include_path(&mut self, path: &str) {
self.include_paths.push(path.to_string());
}
pub fn set_current_file(&mut self, path: &str) {
self.current_file = path.to_string();
}
pub fn define(&mut self, name: &str, body: &str) {
let mut body_tokens = lexer::tokenize(body, self.standard);
while let Some(t) = body_tokens.last() {
if t.kind == TokenKind::Newline || t.kind == TokenKind::Eof {
body_tokens.pop();
} else {
break;
}
}
let m = MacroDef::object_like(name, body_tokens);
self.defines.insert(name.to_string(), m);
}
pub fn add_builtin_defines(&mut self) {
self.define("__STDC__", "1");
let version = match self.standard {
CLangStandard::C89 => "199409L",
CLangStandard::C99 | CLangStandard::Gnu99 => "199901L",
CLangStandard::C11 | CLangStandard::Gnu11 => "201112L",
CLangStandard::C17 | CLangStandard::Gnu17 => "201710L",
CLangStandard::C23 => "202311L",
CLangStandard::Gnu89 => "199409L",
};
self.define("__STDC_VERSION__", version);
if self.standard.is_gnu() {
self.define("__GNUC__", "4");
self.define("__GNUC_MINOR__", "2");
self.define("__GNUC_PATCHLEVEL__", "1");
}
self.define("__llvm_native__", "1");
}
pub fn process(&mut self, tokens: &[Token]) -> Vec<Token> {
self.output.clear();
self.cond_stack.clear();
self.counter = 0;
let mut pos: usize = 0;
let n = tokens.len();
while pos < n {
let tok = &tokens[pos];
if tok.kind == TokenKind::Hash {
pos = self.handle_directive(tokens, pos);
continue;
}
if tok.kind == TokenKind::Eof {
pos += 1;
continue;
}
if self.should_skip() {
pos += 1;
continue;
}
if tok.kind == TokenKind::Identifier {
let name = &tok.text;
if let Some(mac) = self.defines.get(name).cloned() {
if mac.is_function_like {
let mut j = pos + 1;
while j < n && tokens[j].kind == TokenKind::Newline {
j += 1;
}
if j < n
&& tokens[j].kind == TokenKind::LParen
&& !tokens[j].flags.has_leading_space
{
let (args, next_i) = self.parse_macro_args(tokens, j);
let expanded = self.expand_macro(&mac, &args);
let mut expanding = Vec::new();
expanding.push(name.clone());
let reexpanded = self.expand_macros_impl(&expanded, &mut expanding);
self.output.extend(reexpanded);
pos = next_i;
continue;
}
} else {
let expanded = self.expand_macro(&mac, &[]);
let mut expanding = Vec::new();
expanding.push(name.clone());
let reexpanded = self.expand_macros_impl(&expanded, &mut expanding);
self.output.extend(reexpanded);
pos += 1;
continue;
}
}
}
self.output.push(tok.clone());
pos += 1;
}
let expanded = self.expand_macros(&self.output.clone());
self.output = expanded.clone();
expanded
}
fn should_skip(&self) -> bool {
self.cond_stack.last() == Some(&ConditionalState::Skipped)
|| self.cond_stack.last() == Some(&ConditionalState::SawTrue)
}
fn cond_active(&self) -> bool {
self.cond_stack.last() == Some(&ConditionalState::Active) || self.cond_stack.is_empty()
}
fn handle_directive(&mut self, tokens: &[Token], pos: usize) -> usize {
let n = tokens.len();
let mut p = pos + 1;
if p >= n {
return p;
}
let directive_name = tokens[p].text.clone();
p += 1;
if std::env::var("SHADOW_RESIDUAL_TRACE").is_ok() {
eprintln!(" [PP_DIRECTIVE] {}", directive_name);
}
match directive_name.as_str() {
"include" => self.handle_include(tokens, p),
"define" => self.handle_define(tokens, p),
"undef" => self.handle_undef(tokens, p),
"ifdef" => self.handle_ifdef(tokens, p, false),
"ifndef" => self.handle_ifdef(tokens, p, true),
"if" => self.handle_if(tokens, p),
"elif" => self.handle_elif(tokens, p),
"else" => self.handle_else(tokens, p),
"endif" => self.handle_endif(tokens, p),
"error" => self.handle_error(tokens, p),
"pragma" => self.handle_pragma(tokens, p),
"line" => self.handle_line(tokens, p),
_ => {
self.errors.push(format!(
"unknown preprocessor directive: #{}",
directive_name
));
self.skip_to_newline(tokens, p)
}
}
}
fn skip_to_newline(&self, tokens: &[Token], mut pos: usize) -> usize {
let n = tokens.len();
while pos < n {
if tokens[pos].kind == TokenKind::Newline {
return pos + 1;
}
if tokens[pos].kind == TokenKind::Eof {
return pos;
}
pos += 1;
}
pos
}
fn collect_line_tokens(&self, tokens: &[Token], pos: usize) -> (Vec<Token>, usize) {
let n = tokens.len();
let mut p = pos;
let mut out = Vec::new();
while p < n {
if tokens[p].kind == TokenKind::Newline {
break;
}
if tokens[p].kind == TokenKind::Hash {
break;
}
if tokens[p].kind == TokenKind::Eof {
break;
}
out.push(tokens[p].clone());
p += 1;
}
if p < n && tokens[p].kind == TokenKind::Newline {
p += 1;
}
(out, p)
}
fn handle_include(&mut self, tokens: &[Token], pos: usize) -> usize {
if self.should_skip() {
return self.skip_to_newline(tokens, pos);
}
let n = tokens.len();
let mut p = pos;
let is_system;
let mut filename;
if p < n && tokens[p].kind == TokenKind::StringLiteral {
let raw = tokens[p].text.clone();
let inner = &raw[1..raw.len() - 1];
filename = inner.to_string();
is_system = false;
p += 1;
} else if p < n && tokens[p].kind == TokenKind::Less {
p += 1;
let mut parts = String::new();
while p < n
&& tokens[p].kind != TokenKind::Greater
&& tokens[p].kind != TokenKind::Newline
&& tokens[p].kind != TokenKind::Hash
{
parts.push_str(&tokens[p].text);
p += 1;
}
if p < n && tokens[p].kind == TokenKind::Greater {
p += 1;
}
filename = parts;
is_system = true;
} else {
let mut parts = String::new();
while p < n
&& tokens[p].kind != TokenKind::Newline
&& tokens[p].kind != TokenKind::Hash
&& tokens[p].kind != TokenKind::Eof
{
parts.push_str(&tokens[p].text);
p += 1;
}
filename = parts.trim().to_string();
is_system = false;
}
if let Some(full_path) = self.resolve_include(&filename, is_system) {
if let Some(content) = self.read_file(&full_path) {
let saved_file = self.current_file.clone();
let saved_output = std::mem::take(&mut self.output);
let saved_cond_stack = self.cond_stack.clone();
let saved_counter = self.counter;
self.current_file = full_path.clone();
let included_tokens = lexer::tokenize(&content, self.standard);
let expanded = self.process(&included_tokens);
self.output = saved_output;
self.output.extend(expanded);
self.cond_stack = saved_cond_stack;
self.counter = saved_counter;
self.current_file = saved_file;
} else {
self.errors
.push(format!("could not read included file: {}", full_path));
}
} else {
self.errors.push(format!(
"file not found: {} (search paths: {:?})",
filename, self.include_paths
));
}
self.skip_to_newline(tokens, p)
}
fn resolve_include(&self, filename: &str, is_system: bool) -> Option<String> {
use std::path::Path;
if !is_system {
let current_dir = Path::new(&self.current_file).parent()?;
let candidate = current_dir.join(filename);
if candidate.exists() {
return Some(candidate.to_string_lossy().to_string());
}
}
for inc_path in &self.include_paths {
let candidate = Path::new(inc_path).join(filename);
if candidate.exists() {
return Some(candidate.to_string_lossy().to_string());
}
}
let candidate = Path::new(filename);
if candidate.exists() {
return Some(candidate.to_string_lossy().to_string());
}
None
}
fn read_file(&self, path: &str) -> Option<String> {
std::fs::read_to_string(path).ok()
}
fn handle_define(&mut self, tokens: &[Token], pos: usize) -> usize {
if std::env::var("SHADOW_RESIDUAL_TRACE").is_ok() && pos < tokens.len() {
eprintln!(
" [PP_DEFINE_CALL] token='{}' should_skip={} cond_stack={:?}",
tokens[pos].text,
self.should_skip(),
self.cond_stack
);
}
if self.should_skip() {
return self.skip_to_newline(tokens, pos);
}
if std::env::var("SHADOW_RESIDUAL_TRACE").is_ok() && pos < tokens.len() {
if pos + 1 < tokens.len()
&& (tokens[pos].text == "_YUGA_LITTLE_ENDIAN"
|| tokens[pos].text == "_YUGA_BIG_ENDIAN")
{
eprintln!(
" [PP_DEFINE_HIT] {} at pos {} (should_skip={}, cond_stack.len={}, last={:?})",
tokens[pos].text,
pos,
self.should_skip(),
self.cond_stack.len(),
self.cond_stack.last()
);
}
}
let n = tokens.len();
let mut p = pos;
if p >= n || tokens[p].kind != TokenKind::Identifier {
self.errors.push("expected macro name after #define".into());
return self.skip_to_newline(tokens, p);
}
let name = tokens[p].text.clone();
p += 1;
let mut params: Vec<String> = Vec::new();
let mut is_function_like = false;
let mut is_variadic = false;
if p < n && tokens[p].kind == TokenKind::LParen && !tokens[p].flags.has_leading_space {
is_function_like = true;
p += 1;
loop {
if p >= n {
break;
}
if tokens[p].kind == TokenKind::RParen {
p += 1;
break;
}
if tokens[p].kind == TokenKind::Ellipsis {
is_variadic = true;
params.push("__VA_ARGS__".to_string());
p += 1;
if p < n && tokens[p].kind == TokenKind::RParen {
p += 1;
}
break;
}
if tokens[p].kind == TokenKind::Comma {
p += 1;
continue;
}
if tokens[p].kind == TokenKind::Identifier {
params.push(tokens[p].text.clone());
p += 1;
} else {
break;
}
}
}
let mut body: Vec<Token> = Vec::new();
while p < n && tokens[p].kind != TokenKind::Newline && tokens[p].kind != TokenKind::Eof {
body.push(tokens[p].clone());
p += 1;
}
let mac = MacroDef {
name: name.clone(),
params,
body,
is_function_like,
is_variadic,
def_location: tokens[pos].location,
};
self.defines.insert(name.clone(), mac);
if std::env::var("SHADOW_RESIDUAL_TRACE").is_ok() {
if name == "_YUGA_LITTLE_ENDIAN" || name == "_YUGA_BIG_ENDIAN" {
eprintln!(" [PP_DEFINE] {} defined", name);
}
}
if p < n && tokens[p].kind == TokenKind::Newline {
p += 1;
}
p
}
fn handle_undef(&mut self, tokens: &[Token], pos: usize) -> usize {
if self.should_skip() {
return self.skip_to_newline(tokens, pos);
}
let n = tokens.len();
let mut p = pos;
if p < n && tokens[p].kind == TokenKind::Identifier {
self.defines.remove(&tokens[p].text);
p += 1;
} else {
self.errors.push("expected identifier after #undef".into());
}
self.skip_to_newline(tokens, p)
}
fn handle_ifdef(&mut self, tokens: &[Token], pos: usize, is_ifndef: bool) -> usize {
let n = tokens.len();
let mut p = pos;
if self.should_skip() {
self.cond_stack.push(ConditionalState::Skipped);
return self.skip_to_newline(tokens, p);
}
let mut is_defined = false;
if p < n && tokens[p].kind == TokenKind::Identifier {
is_defined = self.defines.contains_key(&tokens[p].text);
p += 1;
}
let cond_true = if is_ifndef { !is_defined } else { is_defined };
if cond_true {
self.cond_stack.push(ConditionalState::Active);
} else {
self.cond_stack.push(ConditionalState::SawTrue);
}
self.skip_to_newline(tokens, p)
}
fn handle_if(&mut self, tokens: &[Token], pos: usize) -> usize {
let n = tokens.len();
let mut p = pos;
if self.should_skip() {
self.cond_stack.push(ConditionalState::Skipped);
return self.skip_to_newline(tokens, p);
}
let (cond_tokens, next_p) = self.collect_line_tokens(tokens, p);
p = next_p;
if std::env::var("SHADOW_RESIDUAL_TRACE").is_ok() && !cond_tokens.is_empty() {
let cond_str: String = cond_tokens
.iter()
.map(|t| t.text.as_str())
.collect::<Vec<_>>()
.join(" ");
eprintln!(" [PP_DEBUG] #if condition: {}", cond_str);
}
let value = self.evaluate_condition(&cond_tokens);
if value {
self.cond_stack.push(ConditionalState::Active);
} else {
self.cond_stack.push(ConditionalState::SawTrue);
}
p
}
fn handle_elif(&mut self, tokens: &[Token], pos: usize) -> usize {
if std::env::var("SHADOW_RESIDUAL_TRACE").is_ok() && pos < tokens.len() {
eprintln!(" [PP_ELIF] called, cond_stack={:?}", self.cond_stack);
}
let n = tokens.len();
let mut p = pos;
match self.cond_stack.last() {
None => {
self.errors.push("#elif without #if".into());
return self.skip_to_newline(tokens, p);
}
Some(ConditionalState::Skipped) => {
if std::env::var("SHADOW_RESIDUAL_TRACE").is_ok() {
eprintln!(" [PP_ELIF_ARM] Skipped branch taken");
}
return self.skip_to_newline(tokens, p);
}
Some(ConditionalState::Active) => {
if std::env::var("SHADOW_RESIDUAL_TRACE").is_ok() {
eprintln!(" [PP_ELIF_ARM] ACTIVE branch taken (pushing Skipped)");
}
self.cond_stack.pop();
self.cond_stack.push(ConditionalState::Skipped);
return self.skip_to_newline(tokens, p);
}
Some(ConditionalState::SawTrue) => {
if std::env::var("SHADOW_RESIDUAL_TRACE").is_ok() {
eprintln!(" [PP_ELIF_ARM] SawTrue branch taken");
}
let (cond_tokens, next_p) = self.collect_line_tokens(tokens, p);
p = next_p;
let value = self.evaluate_condition(&cond_tokens);
if std::env::var("SHADOW_RESIDUAL_TRACE").is_ok() {
let cond_str: String = cond_tokens
.iter()
.map(|t| t.text.as_str())
.collect::<Vec<_>>()
.join(" ");
eprintln!(" [PP_ELIF_EVAL] condition='{}' value={}", cond_str, value);
}
self.cond_stack.pop();
if value {
self.cond_stack.push(ConditionalState::Active);
} else {
self.cond_stack.push(ConditionalState::SawTrue);
}
}
}
p
}
fn handle_else(&mut self, tokens: &[Token], pos: usize) -> usize {
let n = tokens.len();
let mut p = pos;
match self.cond_stack.last() {
None => {
self.errors.push("#else without #if".into());
}
Some(ConditionalState::Skipped) => {
}
Some(ConditionalState::Active) => {
self.cond_stack.pop();
self.cond_stack.push(ConditionalState::SawTrue);
}
Some(ConditionalState::SawTrue) => {
self.cond_stack.pop();
self.cond_stack.push(ConditionalState::Active);
}
}
self.skip_to_newline(tokens, p)
}
fn handle_endif(&mut self, tokens: &[Token], pos: usize) -> usize {
let n = tokens.len();
let mut p = pos;
if self.cond_stack.is_empty() {
self.errors.push("#endif without #if".into());
} else {
self.cond_stack.pop();
}
self.skip_to_newline(tokens, p)
}
fn handle_error(&mut self, tokens: &[Token], pos: usize) -> usize {
if self.should_skip() {
return self.skip_to_newline(tokens, pos);
}
let n = tokens.len();
let mut msg = String::from("#error: ");
let mut p = pos;
while p < n
&& tokens[p].kind != TokenKind::Newline
&& tokens[p].kind != TokenKind::Hash
&& tokens[p].kind != TokenKind::Eof
{
msg.push_str(&tokens[p].text);
p += 1;
}
self.errors.push(msg);
self.skip_to_newline(tokens, p)
}
fn handle_pragma(&mut self, tokens: &[Token], pos: usize) -> usize {
self.skip_to_newline(tokens, pos)
}
fn handle_line(&mut self, tokens: &[Token], pos: usize) -> usize {
self.skip_to_newline(tokens, pos)
}
fn expand_macros(&mut self, tokens: &[Token]) -> Vec<Token> {
self.expand_macros_impl(tokens, &mut Vec::new())
}
fn expand_macros_impl(&mut self, tokens: &[Token], expanding: &mut Vec<String>) -> Vec<Token> {
let mut result: Vec<Token> = Vec::new();
let n = tokens.len();
let mut i = 0;
while i < n {
let tok = &tokens[i];
if tok.kind == TokenKind::Identifier {
let name = &tok.text;
if let Some(expanded) = self.expand_builtin_macro(name) {
result.extend(expanded);
i += 1;
continue;
}
if let Some(mac) = self.defines.get(name).cloned() {
if expanding.contains(name) {
result.push(tok.clone());
i += 1;
continue;
}
if mac.is_function_like {
let mut j = i + 1;
while j < n && tokens[j].kind == TokenKind::Newline {
j += 1;
}
if j < n && tokens[j].kind == TokenKind::LParen {
let (args, next_i) = self.parse_macro_args(tokens, j);
let expanded = self.expand_macro(&mac, &args);
expanding.push(name.clone());
let reexpanded = self.expand_macros_impl(&expanded, expanding);
expanding.pop();
result.extend(reexpanded);
i = next_i;
continue;
}
} else {
let expanded = self.expand_macro(&mac, &[]);
expanding.push(name.clone());
let reexpanded = self.expand_macros_impl(&expanded, expanding);
expanding.pop();
result.extend(reexpanded);
i += 1;
continue;
}
}
}
if !result.is_empty() && tok.kind == TokenKind::HashHash && i + 1 < n {
let a = result.pop().unwrap();
let b = tokens[i + 1].clone();
let pasted = self.token_paste(&a, &b);
result.push(pasted);
i += 2; continue;
}
if tok.kind == TokenKind::Hash
&& i + 1 < n
&& tokens[i + 1].kind == TokenKind::Identifier
{
let stringified = self.stringify(&[tokens[i + 1].clone()]);
result.push(stringified);
i += 2;
continue;
}
result.push(tok.clone());
i += 1;
}
result
}
fn parse_macro_args(&self, tokens: &[Token], pos: usize) -> (Vec<Vec<Token>>, usize) {
let n = tokens.len();
let mut p = pos + 1; let mut args: Vec<Vec<Token>> = Vec::new();
let mut current_arg: Vec<Token> = Vec::new();
let mut paren_depth: usize = 0;
let mut brace_depth: usize = 0;
while p < n {
match tokens[p].kind {
TokenKind::LParen => {
paren_depth += 1;
current_arg.push(tokens[p].clone());
}
TokenKind::RParen => {
if paren_depth == 0 && brace_depth == 0 {
if !current_arg.is_empty() || args.is_empty() {
args.push(current_arg.clone());
}
p += 1;
break;
} else {
if paren_depth > 0 {
paren_depth -= 1;
}
current_arg.push(tokens[p].clone());
}
}
TokenKind::LBrace => {
brace_depth += 1;
current_arg.push(tokens[p].clone());
}
TokenKind::RBrace => {
if brace_depth > 0 {
brace_depth -= 1;
}
current_arg.push(tokens[p].clone());
}
TokenKind::Comma => {
if paren_depth == 0 && brace_depth == 0 {
args.push(current_arg.clone());
current_arg.clear();
} else {
current_arg.push(tokens[p].clone());
}
}
TokenKind::Eof => {
if !current_arg.is_empty() {
args.push(current_arg.clone());
}
break;
}
_ => {
current_arg.push(tokens[p].clone());
}
}
p += 1;
}
if !current_arg.is_empty() || args.is_empty() {
}
if args.len() == 1 && args[0].is_empty() {
args.clear();
}
(args, p)
}
fn expand_macro(&mut self, mac: &MacroDef, args: &[Vec<Token>]) -> Vec<Token> {
let effective_args: Vec<Vec<Token>> = mac
.params
.iter()
.enumerate()
.map(|(idx, param)| {
if idx >= args.len() {
Vec::new()
} else if mac.param_is_raw_in_replacement(param) {
args[idx].clone() } else {
self.expand_macros(&args[idx]) }
})
.collect();
let mut result: Vec<Token> = Vec::new();
let n = mac.body.len();
let mut i = 0;
while i < n {
let tok = &mac.body[i];
if tok.kind == TokenKind::Hash && i + 1 < n {
let next = &mac.body[i + 1];
if next.kind == TokenKind::Identifier {
if let Some(idx) = mac.params.iter().position(|p| *p == next.text) {
if idx < args.len() {
let stringified = self.stringify(&args[idx]);
result.push(stringified);
} else {
result.push(Token::new(
TokenKind::StringLiteral,
"\"\"",
SourceLoc::unknown(),
));
}
i += 2;
continue;
}
}
}
if tok.kind == TokenKind::HashHash && i + 1 < n {
let next_tok = &mac.body[i + 1];
let next_substituted = if next_tok.kind == TokenKind::Identifier {
if let Some(idx) = mac.params.iter().position(|p| *p == next_tok.text) {
if idx < args.len() && !args[idx].is_empty() {
args[idx][0].clone()
} else {
i += 2;
continue;
}
} else {
next_tok.clone()
}
} else {
next_tok.clone()
};
if result.is_empty() {
result.push(next_substituted);
} else {
let a = result.pop().unwrap();
let pasted = self.token_paste(&a, &next_substituted);
result.push(pasted);
}
i += 2;
continue;
}
if tok.kind == TokenKind::Identifier {
if let Some(idx) = mac.params.iter().position(|p| *p == tok.text) {
if idx < effective_args.len() {
result.extend(effective_args[idx].clone());
i += 1;
continue;
}
}
if mac.is_variadic && tok.text == "__VA_ARGS__" {
let va_start = mac.params.len() - 1; let args_len = args.len().max(effective_args.len());
for arg_idx in va_start..args_len {
if arg_idx > va_start {
result.push(Token::new(TokenKind::Comma, ",", SourceLoc::unknown()));
}
let ea = if arg_idx < effective_args.len() {
effective_args[arg_idx].clone()
} else {
Vec::new()
};
result.extend(ea);
}
i += 1;
continue;
}
}
result.push(tok.clone());
i += 1;
}
result
}
fn token_paste(&self, a: &Token, b: &Token) -> Token {
let new_text = format!("{}{}", a.text, b.text);
let relexed = lexer::tokenize(&new_text, self.standard);
let real_tokens: Vec<&Token> = relexed.iter().filter(|t| !t.is_eof()).collect();
if real_tokens.len() == 1 {
real_tokens[0].clone()
} else {
Token::new(TokenKind::Identifier, &new_text, a.location)
}
}
fn stringify(&self, tokens: &[Token]) -> Token {
let s: String = tokens.iter().map(|t| t.text.as_str()).collect();
let escaped = s
.replace('\\', "\\\\")
.replace('"', "\\\"")
.replace('\n', "\\n")
.replace('\t', "\\t");
let text = format!("\"{}\"", escaped);
Token::new(TokenKind::StringLiteral, &text, SourceLoc::unknown())
}
fn expand_builtin_macro(&mut self, name: &str) -> Option<Vec<Token>> {
match name {
"__FILE__" => {
let text = format!("\"{}\"", self.current_file);
Some(vec![Token::new(
TokenKind::StringLiteral,
&text,
SourceLoc::unknown(),
)])
}
"__LINE__" => {
let text = "0".to_string();
Some(vec![Token::new(
TokenKind::NumericLiteral,
&text,
SourceLoc::unknown(),
)])
}
"__DATE__" => {
let date = "\"Jun 26 2026\"".to_string();
Some(vec![Token::new(
TokenKind::StringLiteral,
&date,
SourceLoc::unknown(),
)])
}
"__TIME__" => {
let time = "\"00:00:00\"".to_string();
Some(vec![Token::new(
TokenKind::StringLiteral,
&time,
SourceLoc::unknown(),
)])
}
"__COUNTER__" => {
let val = self.counter;
self.counter += 1;
Some(vec![Token::new(
TokenKind::NumericLiteral,
&val.to_string(),
SourceLoc::unknown(),
)])
}
"__STDC__" => Some(vec![Token::new(
TokenKind::NumericLiteral,
"1",
SourceLoc::unknown(),
)]),
_ => None,
}
}
fn evaluate_condition(&self, tokens: &[Token]) -> bool {
let expanded = self.expand_macros_in_cond(tokens);
if std::env::var("SHADOW_RESIDUAL_TRACE").is_ok() {
let s: String = expanded
.iter()
.map(|t| t.text.as_str())
.collect::<Vec<_>>()
.join(" ");
eprintln!(" [PP_EVAL] expanded: {}", s);
}
match self.eval_expr(&expanded) {
Ok(val) => val != 0,
Err(_) => {
false
}
}
}
fn eval_expr(&self, tokens: &[Token]) -> Result<i64, String> {
let (val, pos) = self.eval_conditional(tokens, 0)?;
if pos < tokens.len() {
return Err("unexpected tokens after expression".into());
}
Ok(val)
}
fn eval_conditional(&self, tokens: &[Token], pos: usize) -> Result<(i64, usize), String> {
let (mut left, mut p) = self.eval_logical_or(tokens, pos)?;
while p < tokens.len() && tokens[p].kind == TokenKind::Question {
p += 1;
let (then_val, p2) = self.eval_conditional(tokens, p)?;
if p2 < tokens.len() && tokens[p2].kind == TokenKind::Colon {
let (else_val, p3) = self.eval_conditional(tokens, p2 + 1)?;
left = if left != 0 { then_val } else { else_val };
p = p3;
} else {
return Err("expected ':' in conditional".into());
}
}
Ok((left, p))
}
fn eval_logical_or(&self, tokens: &[Token], pos: usize) -> Result<(i64, usize), String> {
let (mut left, mut p) = self.eval_logical_and(tokens, pos)?;
while p < tokens.len() && tokens[p].kind == TokenKind::OrOr {
p += 1;
let (right, p2) = self.eval_logical_and(tokens, p)?;
left = if left != 0 || right != 0 { 1 } else { 0 };
p = p2;
}
Ok((left, p))
}
fn eval_logical_and(&self, tokens: &[Token], pos: usize) -> Result<(i64, usize), String> {
let (mut left, mut p) = self.eval_or(tokens, pos)?;
while p < tokens.len() && tokens[p].kind == TokenKind::AndAnd {
p += 1;
let (right, p2) = self.eval_or(tokens, p)?;
left = if left != 0 && right != 0 { 1 } else { 0 };
p = p2;
}
Ok((left, p))
}
fn eval_or(&self, tokens: &[Token], pos: usize) -> Result<(i64, usize), String> {
let (mut left, mut p) = self.eval_xor(tokens, pos)?;
while p < tokens.len() && tokens[p].kind == TokenKind::Pipe {
p += 1;
let (right, p2) = self.eval_xor(tokens, p)?;
left |= right;
p = p2;
}
Ok((left, p))
}
fn eval_xor(&self, tokens: &[Token], pos: usize) -> Result<(i64, usize), String> {
let (mut left, mut p) = self.eval_and(tokens, pos)?;
while p < tokens.len() && tokens[p].kind == TokenKind::Caret {
p += 1;
let (right, p2) = self.eval_and(tokens, p)?;
left ^= right;
p = p2;
}
Ok((left, p))
}
fn eval_and(&self, tokens: &[Token], pos: usize) -> Result<(i64, usize), String> {
let (mut left, mut p) = self.eval_equality(tokens, pos)?;
while p < tokens.len() && tokens[p].kind == TokenKind::Ampersand {
p += 1;
let (right, p2) = self.eval_equality(tokens, p)?;
left &= right;
p = p2;
}
Ok((left, p))
}
fn eval_equality(&self, tokens: &[Token], pos: usize) -> Result<(i64, usize), String> {
let (mut left, mut p) = self.eval_relational(tokens, pos)?;
while p < tokens.len() {
let op = match tokens[p].kind {
TokenKind::EqualEqual => |a: i64, b: i64| if a == b { 1 } else { 0 },
TokenKind::NotEqual => |a: i64, b: i64| if a != b { 1 } else { 0 },
_ => break,
};
p += 1;
let (right, p2) = self.eval_relational(tokens, p)?;
left = op(left, right);
p = p2;
}
Ok((left, p))
}
fn eval_relational(&self, tokens: &[Token], pos: usize) -> Result<(i64, usize), String> {
let (mut left, mut p) = self.eval_shift(tokens, pos)?;
while p < tokens.len() {
let op: fn(i64, i64) -> i64 = match tokens[p].kind {
TokenKind::Less => |a, b| if a < b { 1 } else { 0 },
TokenKind::Greater => |a, b| if a > b { 1 } else { 0 },
TokenKind::LessEqual => |a, b| if a <= b { 1 } else { 0 },
TokenKind::GreaterEqual => |a, b| if a >= b { 1 } else { 0 },
_ => break,
};
p += 1;
let (right, p2) = self.eval_shift(tokens, p)?;
left = op(left, right);
p = p2;
}
Ok((left, p))
}
fn eval_shift(&self, tokens: &[Token], pos: usize) -> Result<(i64, usize), String> {
let (mut left, mut p) = self.eval_additive(tokens, pos)?;
while p < tokens.len() {
let op: fn(i64, i64) -> i64 = match tokens[p].kind {
TokenKind::LessLess => |a, b| a << b,
TokenKind::GreaterGreater => |a, b| a >> b,
_ => break,
};
p += 1;
let (right, p2) = self.eval_additive(tokens, p)?;
left = op(left, right);
p = p2;
}
Ok((left, p))
}
fn eval_additive(&self, tokens: &[Token], pos: usize) -> Result<(i64, usize), String> {
let (mut left, mut p) = self.eval_multiplicative(tokens, pos)?;
while p < tokens.len() {
let op: fn(i64, i64) -> i64 = match tokens[p].kind {
TokenKind::Plus => |a, b| a + b,
TokenKind::Minus => |a, b| a - b,
_ => break,
};
p += 1;
let (right, p2) = self.eval_multiplicative(tokens, p)?;
left = op(left, right);
p = p2;
}
Ok((left, p))
}
fn eval_multiplicative(&self, tokens: &[Token], pos: usize) -> Result<(i64, usize), String> {
let (mut left, mut p) = self.eval_unary(tokens, pos)?;
while p < tokens.len() {
let op: fn(i64, i64) -> i64 = match tokens[p].kind {
TokenKind::Star => |a, b| a * b,
TokenKind::Slash => |a, b| {
if b == 0 {
0
} else {
a / b
}
},
TokenKind::Percent => |a, b| {
if b == 0 {
0
} else {
a % b
}
},
_ => break,
};
p += 1;
let (right, p2) = self.eval_unary(tokens, p)?;
left = op(left, right);
p = p2;
}
Ok((left, p))
}
fn eval_unary(&self, tokens: &[Token], pos: usize) -> Result<(i64, usize), String> {
if pos >= tokens.len() {
return Err("unexpected end of expression".into());
}
match tokens[pos].kind {
TokenKind::Plus => self.eval_unary(tokens, pos + 1),
TokenKind::Minus => {
let (val, p) = self.eval_unary(tokens, pos + 1)?;
Ok((-val, p))
}
TokenKind::Exclaim => {
let (val, p) = self.eval_unary(tokens, pos + 1)?;
Ok((if val != 0 { 0 } else { 1 }, p))
}
TokenKind::Tilde => {
let (val, p) = self.eval_unary(tokens, pos + 1)?;
Ok((!val, p))
}
TokenKind::LParen => {
let (val, p) = self.eval_conditional(tokens, pos + 1)?;
if p < tokens.len() && tokens[p].kind == TokenKind::RParen {
Ok((val, p + 1))
} else {
Err("expected ')'".into())
}
}
_ => self.eval_primary(tokens, pos),
}
}
fn eval_primary(&self, tokens: &[Token], pos: usize) -> Result<(i64, usize), String> {
if pos >= tokens.len() {
return Err("unexpected end of expression".into());
}
let tok = &tokens[pos];
if tok.text == "defined" {
let mut p = pos + 1;
let defined_name;
if p < tokens.len() && tokens[p].kind == TokenKind::LParen {
p += 1;
if p < tokens.len() && tokens[p].kind == TokenKind::Identifier {
defined_name = tokens[p].text.clone();
p += 1;
if p < tokens.len() && tokens[p].kind == TokenKind::RParen {
p += 1;
}
} else {
return Err("expected identifier in defined()".into());
}
} else if p < tokens.len() && tokens[p].kind == TokenKind::Identifier {
defined_name = tokens[p].text.clone();
p += 1;
} else {
return Err("expected identifier after defined".into());
}
let found = self.defines.contains_key(&defined_name);
if std::env::var("SHADOW_RESIDUAL_TRACE").is_ok() {
if defined_name == "__ELF__"
|| defined_name == "__MINGW32__"
|| defined_name == "_YUGA_LITTLE_ENDIAN"
|| defined_name == "_YUGA_BIG_ENDIAN"
{
eprintln!(
" [PP_DEFINED] {} = {} (found={})",
defined_name,
if found { 1 } else { 0 },
self.defines.contains_key(&defined_name)
);
}
}
let val: i64 = if found { 1 } else { 0 };
return Ok((val, p));
}
if tok.kind == TokenKind::NumericLiteral {
let clean_text = tok
.text
.trim_end_matches(|c: char| matches!(c, 'u' | 'U' | 'l' | 'L'));
if let Ok(val) = clean_text.parse::<i64>() {
return Ok((val, pos + 1));
}
if clean_text.starts_with("0x") || clean_text.starts_with("0X") {
let hex = &clean_text[2..];
if let Ok(val) = i64::from_str_radix(hex, 16) {
return Ok((val, pos + 1));
}
if let Ok(val) = u64::from_str_radix(hex, 16) {
return Ok((val as i64, pos + 1));
}
}
if clean_text.starts_with('0') && clean_text.len() > 1 {
if let Ok(val) = i64::from_str_radix(&clean_text[1..], 8) {
return Ok((val, pos + 1));
}
}
if let Ok(val) = clean_text.parse::<u64>() {
return Ok((val as i64, pos + 1));
}
return Ok((0, pos + 1));
}
if tok.kind == TokenKind::Identifier && tok.text == "__has_include" {
let mut p = pos + 1;
if p < tokens.len() && tokens[p].kind == TokenKind::LParen {
p += 1;
let mut filename = String::new();
if p < tokens.len() && tokens[p].kind == TokenKind::Less {
p += 1;
while p < tokens.len() && tokens[p].kind != TokenKind::Greater {
filename.push_str(&tokens[p].text);
p += 1;
}
if p < tokens.len() && tokens[p].kind == TokenKind::Greater {
p += 1; }
} else {
while p < tokens.len() && tokens[p].kind != TokenKind::RParen {
filename.push_str(&tokens[p].text);
p += 1;
}
}
if p < tokens.len() && tokens[p].kind == TokenKind::RParen {
p += 1; }
let found = self.resolve_include(&filename, true).is_some();
return Ok((if found { 1 } else { 0 }, p));
}
return Ok((0, pos + 1));
}
if tok.kind == TokenKind::Identifier {
return Ok((0, pos + 1));
}
if tok.kind == TokenKind::CharLiteral {
if tok.text.len() >= 3 {
let c = tok.text.chars().nth(1).unwrap_or('\0');
return Ok((c as i64, pos + 1));
}
return Ok((0, pos + 1));
}
Err(format!("unexpected token in #if expression: {:?}", tok))
}
fn expand_macros_in_cond(&self, tokens: &[Token]) -> Vec<Token> {
let mut result: Vec<Token> = Vec::new();
let n = tokens.len();
let mut i = 0;
while i < n {
let tok = &tokens[i];
if tok.kind == TokenKind::Identifier && tok.text == "defined" {
result.push(tok.clone());
i += 1;
if i < n {
if tokens[i].kind == TokenKind::LParen {
result.push(tokens[i].clone()); i += 1;
if i < n && tokens[i].kind == TokenKind::Identifier {
result.push(tokens[i].clone()); i += 1;
}
if i < n && tokens[i].kind == TokenKind::RParen {
result.push(tokens[i].clone()); i += 1;
}
} else if tokens[i].kind == TokenKind::Identifier {
result.push(tokens[i].clone()); i += 1;
}
}
continue;
}
if tok.kind == TokenKind::Identifier {
let name = &tok.text;
if let Some(mac) = self.defines.get(name) {
if !mac.is_function_like {
result.extend(mac.body.clone());
i += 1;
continue;
}
}
}
result.push(tok.clone());
i += 1;
}
result
}
}
pub struct PragmaOnceSet {
files: HashMap<String, ()>,
}
impl PragmaOnceSet {
pub fn new() -> Self {
Self {
files: HashMap::new(),
}
}
pub fn contains(&self, path: &str) -> bool {
self.files.contains_key(path)
}
pub fn insert(&mut self, path: &str) {
self.files.insert(path.to_string(), ());
}
pub fn clear(&mut self) {
self.files.clear();
}
}
impl Default for PragmaOnceSet {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone)]
pub struct DiagnosticState {
pub warnings_as_errors: bool,
pub ignored_warnings: Vec<String>,
pub warning_level: WarningLevel,
}
impl Default for DiagnosticState {
fn default() -> Self {
Self {
warnings_as_errors: false,
ignored_warnings: Vec::new(),
warning_level: WarningLevel::Default,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum WarningLevel {
Ignored,
Warning,
Error,
Fatal,
Default,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum PragmaPackState {
Default,
Packed(u8),
Reset,
}
pub struct PragmaManager {
pub messages: Vec<String>,
pub diagnostic_stack: Vec<DiagnosticState>,
pub current_diag: DiagnosticState,
pub pack_state: PragmaPackState,
}
impl PragmaManager {
pub fn new() -> Self {
Self {
messages: Vec::new(),
diagnostic_stack: Vec::new(),
current_diag: DiagnosticState::default(),
pack_state: PragmaPackState::Default,
}
}
pub fn push_diagnostic(&mut self) {
self.diagnostic_stack.push(self.current_diag.clone());
}
pub fn pop_diagnostic(&mut self) {
if let Some(state) = self.diagnostic_stack.pop() {
self.current_diag = state;
}
}
pub fn emit_message(&mut self, msg: &str) {
self.messages.push(msg.to_string());
}
pub fn set_warning(&mut self, name: &str, level: WarningLevel) {
match level {
WarningLevel::Ignored => {
if !self
.current_diag
.ignored_warnings
.contains(&name.to_string())
{
self.current_diag.ignored_warnings.push(name.to_string());
}
}
WarningLevel::Error => {
self.current_diag.warnings_as_errors = true;
}
WarningLevel::Warning => {
self.current_diag.ignored_warnings.retain(|w| w != name);
}
_ => {}
}
}
pub fn set_pack(&mut self, alignment: Option<u8>) {
self.pack_state = match alignment {
Some(0) | None => PragmaPackState::Reset,
Some(n) => PragmaPackState::Packed(n),
};
}
pub fn get_pack_alignment(&self) -> Option<u8> {
match self.pack_state {
PragmaPackState::Packed(n) => Some(n),
_ => None,
}
}
}
impl Default for PragmaManager {
fn default() -> Self {
Self::new()
}
}
pub struct FullPreprocessor {
pub base: Preprocessor,
pub pragma_mgr: PragmaManager,
pub once_files: PragmaOnceSet,
pub known_builtins: HashMap<String, ()>,
pub known_features: HashMap<String, ()>,
pub known_attributes: HashMap<String, ()>,
pub cpp_attributes: HashMap<String, ()>,
pub declspec_attributes: HashMap<String, ()>,
}
impl FullPreprocessor {
pub fn new(standard: CLangStandard) -> Self {
let mut pp = Self {
base: Preprocessor::new(standard),
pragma_mgr: PragmaManager::new(),
once_files: PragmaOnceSet::new(),
known_builtins: HashMap::new(),
known_features: HashMap::new(),
known_attributes: HashMap::new(),
cpp_attributes: HashMap::new(),
declspec_attributes: HashMap::new(),
};
pp.init_known_names();
pp
}
fn init_known_names(&mut self) {
let builtins = [
"__builtin_abs",
"__builtin_alloca",
"__builtin_constant_p",
"__builtin_expect",
"__builtin_memcpy",
"__builtin_memset",
"__builtin_prefetch",
"__builtin_trap",
"__builtin_unreachable",
"__builtin_bswap16",
"__builtin_bswap32",
"__builtin_bswap64",
"__builtin_clz",
"__builtin_ctz",
"__builtin_popcount",
"__builtin_frame_address",
"__builtin_return_address",
"__builtin_va_start",
"__builtin_va_end",
"__builtin_va_copy",
"__builtin_va_arg",
"__builtin_offsetof",
"__builtin_types_compatible_p",
"__builtin_choose_expr",
"__builtin_isinf",
"__builtin_isnan",
"__builtin_inf",
"__builtin_nan",
"__builtin_huge_val",
"__builtin_shufflevector",
"__builtin_convertvector",
"__builtin_add_overflow",
"__builtin_sub_overflow",
"__builtin_mul_overflow",
"__builtin_sadd_overflow",
"__builtin_ssub_overflow",
"__builtin_smul_overflow",
"__builtin_uadd_overflow",
"__builtin_usub_overflow",
"__builtin_umul_overflow",
"__builtin_assume",
"__builtin_assume_aligned",
"__builtin_bit_cast",
"__builtin_launder",
"__builtin_operator_new",
"__builtin_operator_delete",
"__builtin_longjmp",
"__builtin_setjmp",
"__builtin_va_list",
"__builtin_FILE",
"__builtin_FUNCTION",
"__builtin_LINE",
];
for b in &builtins {
self.known_builtins.insert(b.to_string(), ());
}
let features = [
"c_static_assert",
"c_generic_selections",
"c_alignas",
"c_alignof",
"c_atomic",
"c_thread_local",
"cxx_rvalue_references",
"cxx_variadic_templates",
"cxx_constexpr",
"cxx_decltype",
"cxx_auto_type",
"cxx_lambdas",
"cxx_nullptr",
"cxx_noexcept",
"cxx_override",
"cxx_final",
"cxx_range_for",
"cxx_trailing_return",
"cxx_raw_string_literals",
"cxx_deleted_functions",
"cxx_defaulted_functions",
"thread_safe_statics",
"modules",
"tls",
"address_sanitizer",
"memory_sanitizer",
"thread_sanitizer",
"leak_sanitizer",
"undefined_behavior_sanitizer",
"dataflow_sanitizer",
"safe_stack",
"cfi",
"shadow_call_stack",
"bounds_safety",
];
for f in &features {
self.known_features.insert(f.to_string(), ());
}
let attrs = [
"noreturn",
"noinline",
"always_inline",
"pure",
"const",
"malloc",
"alloc_size",
"format",
"nonnull",
"warn_unused_result",
"deprecated",
"unavailable",
"visibility",
"used",
"unused",
"packed",
"aligned",
"section",
"weak",
"alias",
"ifunc",
"constructor",
"destructor",
"hot",
"cold",
"flatten",
"target",
"target_clones",
"error",
"warning",
"artificial",
"gnu_inline",
"may_alias",
"mode",
"vector_size",
"cleanup",
"transparent_union",
"returns_twice",
"not_tail_called",
"disable_tail_calls",
"nodebug",
"optnone",
"minsize",
"cold",
"hot",
"regcall",
"stdcall",
"fastcall",
"thiscall",
"vectorcall",
"ms_abi",
"sysv_abi",
"preserve_most",
"preserve_all",
"swiftcall",
"swift_context",
"swift_error_result",
"swift_async",
"interrupt",
"signal",
];
for a in &attrs {
self.known_attributes.insert(a.to_string(), ());
}
let cpp_attrs = [
"noreturn",
"carries_dependency",
"deprecated",
"fallthrough",
"nodiscard",
"maybe_unused",
"likely",
"unlikely",
"no_unique_address",
"assume",
"assert",
"expect",
"lifetimebound",
];
for a in &cpp_attrs {
self.cpp_attributes.insert(a.to_string(), ());
}
let decl_attrs = [
"dllimport",
"dllexport",
"naked",
"noinline",
"noreturn",
"nothrow",
"novtable",
"selectany",
"thread",
"uuid",
"deprecated",
"restrict",
"property",
"allocate",
"align",
];
for a in &decl_attrs {
self.declspec_attributes.insert(a.to_string(), ());
}
}
pub fn has_include(&self, filename: &str) -> bool {
let resolved = self.base.resolve_include(filename, true);
resolved.is_some()
}
pub fn has_builtin(&self, name: &str) -> bool {
self.known_builtins.contains_key(name)
}
pub fn has_feature(&self, name: &str) -> bool {
self.known_features.contains_key(name)
}
pub fn has_attribute(&self, name: &str) -> bool {
self.known_attributes.contains_key(name)
}
pub fn has_cpp_attribute(&self, name: &str) -> bool {
self.cpp_attributes.contains_key(name)
}
pub fn has_declspec_attribute(&self, name: &str) -> bool {
self.declspec_attributes.contains_key(name)
}
pub fn handle_pragma_directive(&mut self, tokens: &[Token]) {
if tokens.is_empty() {
return;
}
match tokens[0].text.as_str() {
"once" => {
self.once_files.insert(&self.base.current_file);
}
"message" => {
let msg: String = tokens[1..]
.iter()
.map(|t| t.text.as_str())
.collect::<Vec<_>>()
.join(" ");
self.pragma_mgr.emit_message(&msg);
}
"GCC" | "clang" => {
self.handle_gcc_clang_pragma(tokens);
}
"pack" => {
self.handle_pragma_pack(tokens);
}
_ => {
}
}
}
fn handle_gcc_clang_pragma(&mut self, tokens: &[Token]) {
if tokens.len() < 2 {
return;
}
match tokens[1].text.as_str() {
"diagnostic" => {
if tokens.len() >= 3 {
match tokens[2].text.as_str() {
"push" => self.pragma_mgr.push_diagnostic(),
"pop" => self.pragma_mgr.pop_diagnostic(),
"ignored" | "warning" | "error" | "fatal" => {
if tokens.len() >= 4 {
let level = match tokens[2].text.as_str() {
"ignored" => WarningLevel::Ignored,
"warning" => WarningLevel::Warning,
"error" => WarningLevel::Error,
"fatal" => WarningLevel::Fatal,
_ => WarningLevel::Default,
};
let name = tokens[3].text.as_str();
self.pragma_mgr.set_warning(name, level);
}
}
_ => {}
}
}
}
"poison" => {
}
"system_header" => {
}
_ => {}
}
}
fn handle_pragma_pack(&mut self, tokens: &[Token]) {
if tokens.len() < 2 {
self.pragma_mgr.set_pack(None);
return;
}
match tokens[1].text.as_str() {
"(" => {
if tokens.len() >= 3 {
if tokens[2].text.as_str() == "push" {
if tokens.len() >= 4 {
if let Ok(n) = tokens[3].text.parse::<u8>() {
self.pragma_mgr.set_pack(Some(n));
}
}
} else if let Ok(n) = tokens[2].text.parse::<u8>() {
self.pragma_mgr.set_pack(Some(n));
}
}
}
"push" => {
if tokens.len() >= 3 {
if tokens[2].text == "," && tokens.len() >= 4 {
if let Ok(n) = tokens[3].text.parse::<u8>() {
self.pragma_mgr.set_pack(Some(n));
}
}
} else {
self.pragma_mgr.set_pack(None);
}
}
"pop" => {
self.pragma_mgr.set_pack(None);
}
_ => {
if let Ok(n) = tokens[1].text.parse::<u8>() {
self.pragma_mgr.set_pack(Some(n));
}
}
}
}
}
pub struct EvalContext {
pub defines: HashMap<String, String>,
pub include_paths: Vec<String>,
pub standard: CLangStandard,
pub has_include_fn: bool,
pub has_builtin_fn: bool,
pub has_feature_fn: bool,
pub has_attribute_fn: bool,
pub has_cpp_attribute_fn: bool,
pub has_declspec_fn: bool,
}
impl EvalContext {
pub fn new(standard: CLangStandard) -> Self {
Self {
defines: HashMap::new(),
include_paths: Vec::new(),
standard,
has_include_fn: true,
has_builtin_fn: true,
has_feature_fn: true,
has_attribute_fn: true,
has_cpp_attribute_fn: true,
has_declspec_fn: true,
}
}
}
pub fn evaluate_condition_full(tokens: &[Token], ctx: &EvalContext) -> Option<bool> {
if tokens.is_empty() {
return Some(false);
}
let mut evaluator = ConditionEvaluator {
tokens: tokens.to_vec(),
pos: 0,
ctx,
};
let result = evaluator.eval_expression()?;
Some(result != 0)
}
struct ConditionEvaluator<'a> {
tokens: Vec<Token>,
pos: usize,
ctx: &'a EvalContext,
}
impl<'a> ConditionEvaluator<'a> {
fn peek(&self) -> Option<&Token> {
self.tokens.get(self.pos)
}
fn advance(&mut self) -> Option<&Token> {
let tok = self.tokens.get(self.pos);
self.pos += 1;
tok
}
fn expect(&mut self, kind: TokenKind) -> Option<&Token> {
let tok = self.peek()?;
if tok.kind == kind {
self.advance()
} else {
None
}
}
fn eval_expression(&mut self) -> Option<i64> {
self.eval_logical_or()
}
fn eval_logical_or(&mut self) -> Option<i64> {
let mut left = self.eval_logical_and()?;
while self.peek().map_or(false, |t| t.kind == TokenKind::OrOr) {
self.advance();
let right = self.eval_logical_and()?;
left = if left != 0 || right != 0 { 1 } else { 0 };
}
Some(left)
}
fn eval_logical_and(&mut self) -> Option<i64> {
let mut left = self.eval_or_expr()?;
while self.peek().map_or(false, |t| t.kind == TokenKind::AndAnd) {
self.advance();
let right = self.eval_or_expr()?;
left = if left != 0 && right != 0 { 1 } else { 0 };
}
Some(left)
}
fn eval_or_expr(&mut self) -> Option<i64> {
let mut left = self.eval_xor_expr()?;
while self.peek().map_or(false, |t| t.kind == TokenKind::Pipe) {
self.advance();
let right = self.eval_xor_expr()?;
left |= right;
}
Some(left)
}
fn eval_xor_expr(&mut self) -> Option<i64> {
let mut left = self.eval_and_expr()?;
while self.peek().map_or(false, |t| t.kind == TokenKind::Caret) {
self.advance();
let right = self.eval_and_expr()?;
left ^= right;
}
Some(left)
}
fn eval_and_expr(&mut self) -> Option<i64> {
let mut left = self.eval_equality_expr()?;
while self
.peek()
.map_or(false, |t| t.kind == TokenKind::Ampersand)
{
self.advance();
let right = self.eval_equality_expr()?;
left &= right;
}
Some(left)
}
fn eval_equality_expr(&mut self) -> Option<i64> {
let mut left = self.eval_relational_expr()?;
loop {
match self.peek().map(|t| t.kind) {
Some(TokenKind::EqualEqual) => {
self.advance();
let right = self.eval_relational_expr()?;
left = if left == right { 1 } else { 0 };
}
Some(TokenKind::NotEqual) => {
self.advance();
let right = self.eval_relational_expr()?;
left = if left != right { 1 } else { 0 };
}
_ => break,
}
}
Some(left)
}
fn eval_relational_expr(&mut self) -> Option<i64> {
let mut left = self.eval_shift_expr()?;
loop {
match self.peek().map(|t| t.kind) {
Some(TokenKind::Less) => {
self.advance();
let right = self.eval_shift_expr()?;
left = if left < right { 1 } else { 0 };
}
Some(TokenKind::Greater) => {
self.advance();
let right = self.eval_shift_expr()?;
left = if left > right { 1 } else { 0 };
}
Some(TokenKind::LessEqual) => {
self.advance();
let right = self.eval_shift_expr()?;
left = if left <= right { 1 } else { 0 };
}
Some(TokenKind::GreaterEqual) => {
self.advance();
let right = self.eval_shift_expr()?;
left = if left >= right { 1 } else { 0 };
}
_ => break,
}
}
Some(left)
}
fn eval_shift_expr(&mut self) -> Option<i64> {
let mut left = self.eval_additive_expr()?;
loop {
match self.peek().map(|t| t.kind) {
Some(TokenKind::LessLess) => {
self.advance();
let right = self.eval_additive_expr()?;
left = (left as u64).wrapping_shl(right as u32) as i64;
}
Some(TokenKind::GreaterGreater) => {
self.advance();
let right = self.eval_additive_expr()?;
left = (left as u64).wrapping_shr(right as u32) as i64;
}
_ => break,
}
}
Some(left)
}
fn eval_additive_expr(&mut self) -> Option<i64> {
let mut left = self.eval_multiplicative_expr()?;
loop {
match self.peek().map(|t| t.kind) {
Some(TokenKind::Plus) => {
self.advance();
let right = self.eval_multiplicative_expr()?;
left = left.wrapping_add(right);
}
Some(TokenKind::Minus) => {
self.advance();
let right = self.eval_multiplicative_expr()?;
left = left.wrapping_sub(right);
}
_ => break,
}
}
Some(left)
}
fn eval_multiplicative_expr(&mut self) -> Option<i64> {
let mut left = self.eval_unary_expr()?;
loop {
match self.peek().map(|t| t.kind) {
Some(TokenKind::Star) => {
self.advance();
let right = self.eval_unary_expr()?;
left = left.wrapping_mul(right);
}
Some(TokenKind::Slash) => {
self.advance();
let right = self.eval_unary_expr()?;
if right == 0 {
return Some(0);
}
left = left.wrapping_div(right);
}
Some(TokenKind::Percent) => {
self.advance();
let right = self.eval_unary_expr()?;
if right == 0 {
return Some(0);
}
left = left.wrapping_rem(right);
}
_ => break,
}
}
Some(left)
}
fn eval_unary_expr(&mut self) -> Option<i64> {
match self.peek().map(|t| t.kind) {
Some(TokenKind::Plus) => {
self.advance();
self.eval_unary_expr()
}
Some(TokenKind::Minus) => {
self.advance();
let val = self.eval_unary_expr()?;
Some(-val)
}
Some(TokenKind::Exclaim) => {
self.advance();
let val = self.eval_unary_expr()?;
Some(if val != 0 { 0 } else { 1 })
}
Some(TokenKind::Tilde) => {
self.advance();
let val = self.eval_unary_expr()?;
Some(!val)
}
_ => self.eval_primary_expr(),
}
}
fn eval_primary_expr(&mut self) -> Option<i64> {
let tok = self.peek()?.clone();
match tok.kind {
TokenKind::NumericLiteral => {
self.advance();
self.parse_integer_literal(&tok.text)
}
TokenKind::CharLiteral => {
self.advance();
let s = tok.text.trim_matches('\'');
if s.len() == 1 {
Some(s.chars().next()? as i64)
} else {
self.parse_escape_char(s)
}
}
TokenKind::LParen => {
self.advance(); let val = self.eval_expression();
self.expect(TokenKind::RParen);
val
}
TokenKind::Identifier => {
let name = tok.text.clone();
self.advance();
if name == "defined" {
self.eval_defined_operator()
} else if name == "__has_include" {
self.eval_has_include_operator()
} else if name == "__has_builtin" {
self.eval_has_builtin_operator()
} else if name == "__has_feature" {
self.eval_has_feature_operator()
} else if name == "__has_attribute" {
self.eval_has_attribute_operator()
} else if name == "__has_cpp_attribute" {
self.eval_has_cpp_attribute_operator()
} else if name == "__has_declspec_attribute" {
self.eval_has_declspec_operator()
} else {
let val = self
.ctx
.defines
.get(&name)
.cloned()
.unwrap_or_else(|| "0".to_string());
val.parse::<i64>().ok().or(Some(0))
}
}
_ => Some(0),
}
}
fn eval_defined_operator(&mut self) -> Option<i64> {
if self.peek().map_or(false, |t| t.kind == TokenKind::LParen) {
self.advance(); let name = self.advance()?.text.clone();
self.expect(TokenKind::RParen);
Some(if self.ctx.defines.contains_key(&name) {
1
} else {
0
})
} else {
let name = self.advance()?.text.clone();
Some(if self.ctx.defines.contains_key(&name) {
1
} else {
0
})
}
}
fn eval_has_include_operator(&mut self) -> Option<i64> {
self.expect(TokenKind::LParen)?;
let filename = self.read_string_until_rparen()?;
self.expect(TokenKind::RParen);
let pp = Preprocessor::new(self.ctx.standard);
let resolved = pp.resolve_include(&filename, true);
Some(if resolved.is_some() { 1 } else { 0 })
}
fn eval_has_builtin_operator(&mut self) -> Option<i64> {
self.expect(TokenKind::LParen)?;
let name = self.advance()?.text.clone();
self.expect(TokenKind::RParen);
Some(0) }
fn eval_has_feature_operator(&mut self) -> Option<i64> {
self.expect(TokenKind::LParen)?;
let name = self.advance()?.text.clone();
self.expect(TokenKind::RParen);
Some(0)
}
fn eval_has_attribute_operator(&mut self) -> Option<i64> {
self.expect(TokenKind::LParen)?;
let name = self.advance()?.text.clone();
self.expect(TokenKind::RParen);
Some(0)
}
fn eval_has_cpp_attribute_operator(&mut self) -> Option<i64> {
self.expect(TokenKind::LParen)?;
let name = self.advance()?.text.clone();
self.expect(TokenKind::RParen);
Some(0)
}
fn eval_has_declspec_operator(&mut self) -> Option<i64> {
self.expect(TokenKind::LParen)?;
let name = self.advance()?.text.clone();
self.expect(TokenKind::RParen);
Some(0)
}
fn read_string_until_rparen(&mut self) -> Option<String> {
let mut result = String::new();
loop {
let tok = match self.peek() {
Some(t) => t.clone(),
None => break,
};
if tok.kind == TokenKind::RParen {
break;
}
self.advance();
if tok.kind == TokenKind::Less {
loop {
let t = match self.peek() {
Some(t) => t.clone(),
None => break,
};
if t.kind == TokenKind::Greater {
self.advance();
break;
}
self.advance();
result.push_str(&t.text);
}
} else {
result.push_str(&tok.text);
}
}
Some(result.trim().to_string())
}
fn parse_integer_literal(&self, text: &str) -> Option<i64> {
let s = text.trim();
if s.starts_with("0x") || s.starts_with("0X") {
let hex = &s[2..];
i64::from_str_radix(hex, 16)
.ok()
.or_else(|| u64::from_str_radix(hex, 16).ok().map(|v| v as i64))
} else if s.starts_with('0') && s.len() > 1 {
i64::from_str_radix(&s[1..], 8).ok()
} else {
s.parse::<i64>()
.ok()
.or_else(|| s.parse::<u64>().ok().map(|v| v as i64))
}
}
fn parse_escape_char(&self, s: &str) -> Option<i64> {
if s.starts_with('\\') && s.len() >= 2 {
match s.chars().nth(1)? {
'n' => Some(b'\n' as i64),
't' => Some(b'\t' as i64),
'r' => Some(b'\r' as i64),
'0' => Some(b'\0' as i64),
'\\' => Some(b'\\' as i64),
'\'' => Some(b'\'' as i64),
'"' => Some(b'"' as i64),
'x' => {
let hex = &s[2..];
i64::from_str_radix(hex, 16).ok()
}
c if c.is_ascii_digit() => {
let octal = &s[1..];
i64::from_str_radix(octal, 8).ok()
}
_ => Some(s.chars().nth(1)? as i64),
}
} else {
s.chars().next().map(|c| c as i64)
}
}
}
pub fn add_extended_builtin_defines(
defines: &mut HashMap<String, String>,
standard: CLangStandard,
target_triple: &str,
) {
defines.insert("__STDC__".to_string(), "1".to_string());
match standard {
CLangStandard::C89 => {
defines.insert("__STDC_VERSION__".to_string(), "199409L".to_string());
}
CLangStandard::C99 => {
defines.insert("__STDC_VERSION__".to_string(), "199901L".to_string());
}
CLangStandard::C11 => {
defines.insert("__STDC_VERSION__".to_string(), "201112L".to_string());
}
CLangStandard::C17 => {
defines.insert("__STDC_VERSION__".to_string(), "201710L".to_string());
}
CLangStandard::C23 => {
defines.insert("__STDC_VERSION__".to_string(), "202311L".to_string());
}
_ => {}
}
defines.insert("__clang__".to_string(), "1".to_string());
defines.insert("__clang_major__".to_string(), "18".to_string());
defines.insert("__clang_minor__".to_string(), "1".to_string());
defines.insert("__clang_patchlevel__".to_string(), "0".to_string());
defines.insert("__clang_version__".to_string(), "\"18.1.0\"".to_string());
defines.insert("__GNUC__".to_string(), "4".to_string());
defines.insert("__GNUC_MINOR__".to_string(), "2".to_string());
defines.insert("__GNUC_PATCHLEVEL__".to_string(), "1".to_string());
defines.insert("__GNUG__".to_string(), "4".to_string());
defines.insert("__VERSION__".to_string(), "\"Clang 18.1.0\"".to_string());
defines.insert("__llvm__".to_string(), "1".to_string());
let target_lower = target_triple.to_lowercase();
if target_lower.contains("x86_64") || target_lower.contains("amd64") {
defines.insert("__x86_64__".to_string(), "1".to_string());
defines.insert("__x86_64".to_string(), "1".to_string());
defines.insert("__amd64__".to_string(), "1".to_string());
defines.insert("__amd64".to_string(), "1".to_string());
defines.insert("__SSE__".to_string(), "1".to_string());
defines.insert("__SSE2__".to_string(), "1".to_string());
defines.insert("__SSE3__".to_string(), "1".to_string());
defines.insert("__SSSE3__".to_string(), "1".to_string());
defines.insert("__SSE4_1__".to_string(), "1".to_string());
defines.insert("__SSE4_2__".to_string(), "1".to_string());
defines.insert("__AVX__".to_string(), "1".to_string());
defines.insert("__AVX2__".to_string(), "1".to_string());
defines.insert("__MMX__".to_string(), "1".to_string());
} else if target_lower.contains("aarch64") || target_lower.contains("arm64") {
defines.insert("__aarch64__".to_string(), "1".to_string());
defines.insert("__ARM_ARCH".to_string(), "8".to_string());
defines.insert("__ARM_64BIT_STATE".to_string(), "1".to_string());
defines.insert("__ARM_NEON".to_string(), "1".to_string());
} else if target_lower.contains("arm") && !target_lower.contains("thumb") {
defines.insert("__arm__".to_string(), "1".to_string());
defines.insert("__arm".to_string(), "1".to_string());
defines.insert("__ARM_ARCH".to_string(), "7".to_string());
} else if target_lower.contains("riscv64") {
defines.insert("__riscv".to_string(), "1".to_string());
defines.insert("__riscv_xlen".to_string(), "64".to_string());
defines.insert("__riscv_flen".to_string(), "64".to_string());
defines.insert("__riscv_f".to_string(), "1".to_string());
defines.insert("__riscv_d".to_string(), "1".to_string());
} else if target_lower.contains("riscv32") {
defines.insert("__riscv".to_string(), "1".to_string());
defines.insert("__riscv_xlen".to_string(), "32".to_string());
} else if target_lower.contains("mips64") {
defines.insert("__mips__".to_string(), "1".to_string());
defines.insert("__mips64".to_string(), "1".to_string());
defines.insert("_MIPS_ARCH".to_string(), "\"mips64\"".to_string());
} else if target_lower.contains("mips") {
defines.insert("__mips__".to_string(), "1".to_string());
defines.insert("_MIPS_ARCH".to_string(), "\"mips32\"".to_string());
} else if target_lower.contains("powerpc64") || target_lower.contains("ppc64") {
defines.insert("__powerpc64__".to_string(), "1".to_string());
defines.insert("__ppc64__".to_string(), "1".to_string());
defines.insert("_ARCH_PPC64".to_string(), "1".to_string());
} else if target_lower.contains("powerpc") || target_lower.contains("ppc") {
defines.insert("__powerpc__".to_string(), "1".to_string());
defines.insert("__ppc__".to_string(), "1".to_string());
} else if target_lower.contains("sparc64") {
defines.insert("__sparc64__".to_string(), "1".to_string());
defines.insert("__sparc_v9__".to_string(), "1".to_string());
} else if target_lower.contains("sparc") {
defines.insert("__sparc__".to_string(), "1".to_string());
} else if target_lower.contains("s390x") {
defines.insert("__s390x__".to_string(), "1".to_string());
defines.insert("__zarch__".to_string(), "1".to_string());
}
if target_lower.contains("linux") {
defines.insert("__linux__".to_string(), "1".to_string());
defines.insert("__linux".to_string(), "1".to_string());
defines.insert("__gnu_linux__".to_string(), "1".to_string());
defines.insert("linux".to_string(), "1".to_string());
defines.insert("__unix__".to_string(), "1".to_string());
defines.insert("__unix".to_string(), "1".to_string());
defines.insert("unix".to_string(), "1".to_string());
defines.insert("__ELF__".to_string(), "1".to_string());
defines.insert("__STDC_HOSTED__".to_string(), "1".to_string());
} else if target_lower.contains("darwin") || target_lower.contains("macos") {
defines.insert("__APPLE__".to_string(), "1".to_string());
defines.insert("__MACH__".to_string(), "1".to_string());
defines.insert("__APPLE_CC__".to_string(), "1".to_string());
defines.insert("__STDC_HOSTED__".to_string(), "1".to_string());
} else if target_lower.contains("windows") || target_lower.contains("mingw") {
defines.insert("_WIN32".to_string(), "1".to_string());
defines.insert("__MINGW32__".to_string(), "1".to_string());
defines.insert("__STDC_HOSTED__".to_string(), "1".to_string());
} else if target_lower.contains("freebsd") {
defines.insert("__FreeBSD__".to_string(), "1".to_string());
defines.insert("__unix__".to_string(), "1".to_string());
defines.insert("__ELF__".to_string(), "1".to_string());
} else if target_lower.contains("netbsd") {
defines.insert("__NetBSD__".to_string(), "1".to_string());
} else if target_lower.contains("openbsd") {
defines.insert("__OpenBSD__".to_string(), "1".to_string());
}
defines.insert("__SIZEOF_POINTER__".to_string(), "8".to_string());
defines.insert("__SIZEOF_SIZE_T__".to_string(), "8".to_string());
defines.insert("__SIZEOF_WCHAR_T__".to_string(), "4".to_string());
defines.insert("__SIZEOF_WINT_T__".to_string(), "4".to_string());
defines.insert("__SIZEOF_INT__".to_string(), "4".to_string());
defines.insert("__SIZEOF_LONG__".to_string(), "8".to_string());
defines.insert("__SIZEOF_LONG_LONG__".to_string(), "8".to_string());
defines.insert("__SIZEOF_SHORT__".to_string(), "2".to_string());
defines.insert("__SIZEOF_FLOAT__".to_string(), "4".to_string());
defines.insert("__SIZEOF_DOUBLE__".to_string(), "8".to_string());
defines.insert("__SIZEOF_LONG_DOUBLE__".to_string(), "16".to_string());
defines.insert("__SIZEOF_INT128__".to_string(), "16".to_string());
defines.insert(
"__BYTE_ORDER__".to_string(),
"__ORDER_LITTLE_ENDIAN__".to_string(),
);
defines.insert("__ORDER_LITTLE_ENDIAN__".to_string(), "1234".to_string());
defines.insert("__ORDER_BIG_ENDIAN__".to_string(), "4321".to_string());
defines.insert("__ORDER_PDP_ENDIAN__".to_string(), "3412".to_string());
defines.insert("__CHAR_BIT__".to_string(), "8".to_string());
defines.insert("__INT_MAX__".to_string(), "2147483647".to_string());
defines.insert(
"__LONG_MAX__".to_string(),
"9223372036854775807L".to_string(),
);
defines.insert("__DBL_DIG__".to_string(), "15".to_string());
defines.insert(
"__DBL_MIN__".to_string(),
"2.2250738585072014e-308".to_string(),
);
defines.insert(
"__DBL_MAX__".to_string(),
"1.7976931348623157e+308".to_string(),
);
defines.insert("__FLT_DIG__".to_string(), "6".to_string());
defines.insert("__FLT_MIN__".to_string(), "1.17549435e-38F".to_string());
defines.insert("__FLT_MAX__".to_string(), "3.40282347e+38F".to_string());
defines.insert("__FINITE_MATH_ONLY__".to_string(), "0".to_string());
defines.insert("__FAST_MATH__".to_string(), "0".to_string());
defines.insert("__WCHAR_TYPE__".to_string(), "int".to_string());
defines.insert("__WINT_TYPE__".to_string(), "unsigned int".to_string());
defines.insert("__SIZE_TYPE__".to_string(), "unsigned long".to_string());
defines.insert("__PTRDIFF_TYPE__".to_string(), "long".to_string());
defines.insert("__INTPTR_TYPE__".to_string(), "long".to_string());
defines.insert("__UINTPTR_TYPE__".to_string(), "unsigned long".to_string());
}
pub fn preprocess(source: &str, standard: CLangStandard, include_paths: &[String]) -> Vec<Token> {
if source.is_empty() {
return Vec::new();
}
let mut pp = Preprocessor::new(standard);
for p in include_paths {
pp.add_include_path(p);
}
pp.add_builtin_defines();
let tokens = lexer::tokenize(source, standard);
let mut result = pp.process(&tokens);
result.retain(|t| t.kind != TokenKind::Eof && t.kind != TokenKind::Newline);
result
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum IncludeDirKind {
User,
System,
Quote,
After,
Framework,
ExternCSystem,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct IncludePath {
pub path: String,
pub kind: IncludeDirKind,
pub is_framework: bool,
pub from_env: bool,
}
impl IncludePath {
pub fn new(path: &str, kind: IncludeDirKind) -> Self {
Self {
path: path.to_string(),
kind,
is_framework: false,
from_env: false,
}
}
pub fn user(path: &str) -> Self {
Self::new(path, IncludeDirKind::User)
}
pub fn system(path: &str) -> Self {
Self::new(path, IncludeDirKind::System)
}
pub fn quote(path: &str) -> Self {
Self::new(path, IncludeDirKind::Quote)
}
pub fn after(path: &str) -> Self {
Self::new(path, IncludeDirKind::After)
}
pub fn framework(path: &str) -> Self {
Self {
path: path.to_string(),
kind: IncludeDirKind::Framework,
is_framework: true,
from_env: false,
}
}
}
#[derive(Debug, Clone)]
pub struct IncludePathRegistry {
pub paths: Vec<IncludePath>,
pub use_standard_system_includes: bool,
pub use_standard_cxx_includes: bool,
pub sysroot: Option<String>,
pub resource_dir: Option<String>,
pub max_include_depth: u32,
}
impl IncludePathRegistry {
pub fn new() -> Self {
Self {
paths: Vec::new(),
use_standard_system_includes: true,
use_standard_cxx_includes: false,
sysroot: None,
resource_dir: None,
max_include_depth: 200,
}
}
pub fn add_user_path(&mut self, path: &str) {
self.paths.push(IncludePath::user(path));
}
pub fn add_system_path(&mut self, path: &str) {
self.paths.push(IncludePath::system(path));
}
pub fn add_quote_path(&mut self, path: &str) {
self.paths.push(IncludePath::quote(path));
}
pub fn add_after_path(&mut self, path: &str) {
self.paths.push(IncludePath::after(path));
}
pub fn add_framework_path(&mut self, path: &str) {
self.paths.push(IncludePath::framework(path));
}
pub fn load_env_paths(&mut self) {
if let Ok(cpath) = std::env::var("CPATH") {
for p in std::env::split_paths(&cpath) {
let mut ip = IncludePath::user(&p.to_string_lossy());
ip.from_env = true;
self.paths.push(ip);
}
}
if let Ok(c_inc) = std::env::var("C_INCLUDE_PATH") {
for p in std::env::split_paths(&c_inc) {
let mut ip = IncludePath::system(&p.to_string_lossy());
ip.from_env = true;
self.paths.push(ip);
}
}
if let Ok(cxx_inc) = std::env::var("CPLUS_INCLUDE_PATH") {
for p in std::env::split_paths(&cxx_inc) {
let mut ip = IncludePath::system(&p.to_string_lossy());
ip.from_env = true;
self.paths.push(ip);
}
}
if let Ok(objc_inc) = std::env::var("OBJC_INCLUDE_PATH") {
for p in std::env::split_paths(&objc_inc) {
let mut ip = IncludePath::system(&p.to_string_lossy());
ip.from_env = true;
self.paths.push(ip);
}
}
}
pub fn resolve_quoted_include(
&self,
filename: &str,
current_file_dir: Option<&str>,
) -> Option<String> {
if let Some(dir) = current_file_dir {
let candidate = format!("{}/{}", dir, filename);
if std::path::Path::new(&candidate).exists() {
return Some(candidate);
}
}
for ip in &self.paths {
if ip.kind == IncludeDirKind::Quote {
let candidate = format!("{}/{}", ip.path, filename);
if std::path::Path::new(&candidate).exists() {
return Some(candidate);
}
}
}
for ip in &self.paths {
if ip.kind == IncludeDirKind::User {
let candidate = format!("{}/{}", ip.path, filename);
if std::path::Path::new(&candidate).exists() {
return Some(candidate);
}
}
}
self.resolve_system_include(filename)
}
pub fn resolve_system_include(&self, filename: &str) -> Option<String> {
for ip in &self.paths {
if ip.kind == IncludeDirKind::User {
let candidate = format!("{}/{}", ip.path, filename);
if std::path::Path::new(&candidate).exists() {
return Some(candidate);
}
}
}
for ip in &self.paths {
if ip.kind == IncludeDirKind::System {
let candidate = format!("{}/{}", ip.path, filename);
if std::path::Path::new(&candidate).exists() {
return Some(candidate);
}
}
}
for ip in &self.paths {
if ip.kind == IncludeDirKind::After {
let candidate = format!("{}/{}", ip.path, filename);
if std::path::Path::new(&candidate).exists() {
return Some(candidate);
}
}
}
for ip in &self.paths {
if ip.is_framework {
if let Some((framework, rest)) = filename.split_once('/') {
let candidate = format!("{}/{}.framework/Headers/{}", ip.path, framework, rest);
if std::path::Path::new(&candidate).exists() {
return Some(candidate);
}
}
}
}
None
}
pub fn dump_paths(&self) -> String {
let mut result = String::from("#include <...> search starts here:\n");
for ip in &self.paths {
let kind_str = match ip.kind {
IncludeDirKind::User => "",
IncludeDirKind::System => " (system)",
IncludeDirKind::Quote => " (quote)",
IncludeDirKind::After => " (after)",
IncludeDirKind::Framework => " (framework)",
IncludeDirKind::ExternCSystem => " (extern-c-system)",
};
result.push_str(&format!(" {}{}\n", ip.path, kind_str));
}
result.push_str("End of search list.\n");
result
}
}
impl Default for IncludePathRegistry {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone)]
pub struct PragmaHandler {
pub name: String,
pub is_namespace: bool,
pub handler_id: String,
}
#[derive(Debug, Clone)]
pub struct PragmaHandlerRegistry {
pub handlers: Vec<PragmaHandler>,
pub warn_unknown_pragmas: bool,
pub ignore_unknown_pragmas: bool,
}
impl PragmaHandlerRegistry {
pub fn new() -> Self {
Self {
handlers: Vec::new(),
warn_unknown_pragmas: false,
ignore_unknown_pragmas: true,
}
}
pub fn register(&mut self, name: &str, handler_id: &str) {
self.handlers.push(PragmaHandler {
name: name.to_string(),
is_namespace: false,
handler_id: handler_id.to_string(),
});
}
pub fn register_namespace(&mut self, namespace: &str, handler_id: &str) {
self.handlers.push(PragmaHandler {
name: namespace.to_string(),
is_namespace: true,
handler_id: handler_id.to_string(),
});
}
pub fn find_handler(&self, name: &str) -> Option<&PragmaHandler> {
self.handlers
.iter()
.find(|h| h.name == name && !h.is_namespace)
}
pub fn process_pragma_operator(&self, pragma_str: &str) -> Option<String> {
let trimmed = pragma_str.trim();
let inner = if trimmed.starts_with('"') && trimmed.ends_with('"') {
&trimmed[1..trimmed.len() - 1]
} else {
trimmed
};
let unescaped = Self::unescape_pragma_string(inner);
Some(format!("_Pragma expanded to: {}", unescaped))
}
fn unescape_pragma_string(s: &str) -> String {
let mut result = String::new();
let mut chars = s.chars().peekable();
while let Some(c) = chars.next() {
if c == '\\' {
match chars.peek() {
Some('"') => {
chars.next();
result.push('"');
}
Some('\\') => {
chars.next();
result.push('\\');
}
Some('n') => {
chars.next();
result.push('\n');
}
Some('t') => {
chars.next();
result.push('\t');
}
_ => {
result.push('\\');
}
}
} else {
result.push(c);
}
}
result
}
}
impl Default for PragmaHandlerRegistry {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum MacroRedefResult {
Identical,
Compatible,
Incompatible(String),
Error(String),
}
pub fn check_macro_redefinition(existing: &MacroDef, new_def: &MacroDef) -> MacroRedefResult {
if existing.is_function_like == new_def.is_function_like
&& existing.is_variadic == new_def.is_variadic
&& existing.params == new_def.params
{
if token_lists_equal(&existing.body, &new_def.body) {
return MacroRedefResult::Identical;
}
let msg = format!(
"'{}' macro redefined; this is the location of the previous definition",
existing.name
);
return MacroRedefResult::Incompatible(msg);
}
let msg = format!(
"'{}' macro redefined with different parameters or form",
existing.name
);
MacroRedefResult::Error(msg)
}
fn token_lists_equal(a: &[Token], b: &[Token]) -> bool {
if a.len() != b.len() {
return false;
}
for (ta, tb) in a.iter().zip(b.iter()) {
if ta.kind != tb.kind || ta.text != tb.text {
return false;
}
}
true
}
#[derive(Debug, Clone)]
pub struct MacroExpansionTracker {
pub depth: u32,
pub max_depth: u32,
pub enable_backtrace: bool,
pub backtrace_limit: u32,
pub expansion_stack: Vec<MacroExpansionFrame>,
}
#[derive(Debug, Clone)]
pub struct MacroExpansionFrame {
pub macro_name: String,
pub file: String,
pub line: u32,
pub depth: u32,
}
impl MacroExpansionTracker {
pub fn new() -> Self {
Self {
depth: 0,
max_depth: 256,
enable_backtrace: false,
backtrace_limit: 10,
expansion_stack: Vec::new(),
}
}
pub fn push(&mut self, name: &str, file: &str, line: u32) -> Result<(), String> {
self.depth += 1;
if self.depth > self.max_depth {
return Err(format!(
"macro '{}' exceeded maximum expansion depth of {}",
name, self.max_depth
));
}
if self.enable_backtrace {
self.expansion_stack.push(MacroExpansionFrame {
macro_name: name.to_string(),
file: file.to_string(),
line,
depth: self.depth,
});
if self.expansion_stack.len() > self.backtrace_limit as usize {
self.expansion_stack.remove(0);
}
}
Ok(())
}
pub fn pop(&mut self) {
if self.depth > 0 {
self.depth -= 1;
}
}
pub fn backtrace(&self) -> String {
if self.expansion_stack.is_empty() {
return String::new();
}
let mut result = String::from("in expansion of macro:\n");
for frame in &self.expansion_stack {
result.push_str(&format!(
" {}:{}:{}: expanding '{}'\n",
frame.file, frame.line, "", frame.macro_name
));
}
result
}
}
impl Default for MacroExpansionTracker {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct PlacemarkerToken {
pub location: SourceLoc,
}
impl PlacemarkerToken {
pub fn new(location: SourceLoc) -> Self {
Self { location }
}
pub fn unknown() -> Self {
Self {
location: SourceLoc::unknown(),
}
}
}
pub fn paste_tokens(left: &[Token], right: &[Token]) -> Vec<Token> {
if left.is_empty() && right.is_empty() {
return Vec::new();
}
if left.is_empty() {
return right.to_vec();
}
if right.is_empty() {
return left.to_vec();
}
let mut result: Vec<Token> = left[..left.len() - 1].to_vec();
let last_left = &left[left.len() - 1];
let first_right = &right[0];
let pasted_text = format!("{}{}", last_left.text, first_right.text);
let new_kind = if pasted_text.chars().all(|c| c.is_ascii_digit()) {
TokenKind::NumericLiteral
} else if pasted_text.starts_with('"') {
TokenKind::StringLiteral
} else if is_valid_identifier(&pasted_text) {
TokenKind::Identifier
} else {
last_left.kind
};
let new_token = Token {
kind: new_kind,
text: pasted_text,
location: last_left.location,
flags: TokenFlags::default(),
};
result.push(new_token);
if right.len() > 1 {
result.extend_from_slice(&right[1..]);
}
result
}
fn is_valid_identifier(s: &str) -> bool {
if s.is_empty() {
return false;
}
let mut chars = s.chars();
let first = chars.next().unwrap();
if !first.is_ascii_alphabetic() && first != '_' {
return false;
}
chars.all(|c| c.is_ascii_alphanumeric() || c == '_')
}
pub fn stringify_tokens(tokens: &[Token]) -> Token {
if tokens.is_empty() {
return Token {
kind: TokenKind::StringLiteral,
text: "\"\"".to_string(),
location: SourceLoc::unknown(),
flags: TokenFlags::default(),
};
}
let mut result = String::from("\"");
let mut first = true;
for token in tokens {
if !first {
result.push(' ');
}
first = false;
let escaped = escape_for_stringification(&token.text);
result.push_str(&escaped);
}
result.push('"');
Token {
kind: TokenKind::StringLiteral,
text: result,
location: tokens
.first()
.map(|t| t.location)
.unwrap_or(SourceLoc::unknown()),
flags: TokenFlags::default(),
}
}
fn escape_for_stringification(s: &str) -> String {
let mut result = String::new();
for c in s.chars() {
match c {
'\\' => result.push_str("\\\\"),
'"' => result.push_str("\\\""),
'\n' => result.push_str("\\n"),
'\t' => result.push_str("\\t"),
'\r' => result.push_str("\\r"),
_ => result.push(c),
}
}
result
}
#[derive(Debug, Clone)]
pub struct PredefinedMacros {
pub arch: String,
pub os: String,
pub vendor: String,
pub environment: String,
}
impl PredefinedMacros {
pub fn new(arch: &str, os: &str) -> Self {
Self {
arch: arch.to_string(),
os: os.to_string(),
vendor: "unknown".to_string(),
environment: "unknown".to_string(),
}
}
pub fn from_triple(triple: &str) -> Self {
let parts: Vec<&str> = triple.split('-').collect();
let arch = parts.first().copied().unwrap_or("unknown");
let vendor = parts.get(1).copied().unwrap_or("unknown");
let os = parts.get(2).copied().unwrap_or("unknown");
let env = parts.get(3).copied().unwrap_or("unknown");
Self {
arch: arch.to_string(),
os: os.to_string(),
vendor: vendor.to_string(),
environment: env.to_string(),
}
}
pub fn generate_all(&self) -> Vec<(String, String)> {
let mut macros = Vec::new();
self.add_arch_macros(&mut macros);
self.add_os_macros(&mut macros);
self.add_compiler_macros(&mut macros);
macros
}
fn add_arch_macros(&self, macros: &mut Vec<(String, String)>) {
match self.arch.as_str() {
"x86_64" | "amd64" => {
macros.push(("__x86_64__".into(), "1".into()));
macros.push(("__x86_64".into(), "1".into()));
macros.push(("__amd64__".into(), "1".into()));
macros.push(("__amd64".into(), "1".into()));
macros.push(("__LP64__".into(), "1".into()));
macros.push(("_LP64".into(), "1".into()));
macros.push(("__SSE2__".into(), "1".into()));
macros.push(("__SSE2_MATH__".into(), "1".into()));
}
"i386" | "i486" | "i586" | "i686" | "x86" => {
macros.push(("__i386__".into(), "1".into()));
macros.push(("__i386".into(), "1".into()));
macros.push(("i386".into(), "1".into()));
macros.push(("__X87__".into(), "1".into()));
}
"aarch64" | "arm64" => {
macros.push(("__aarch64__".into(), "1".into()));
macros.push(("__ARM64__".into(), "1".into()));
macros.push(("__LP64__".into(), "1".into()));
macros.push(("_LP64".into(), "1".into()));
macros.push(("__ARM_NEON".into(), "1".into()));
}
"arm" | "armv7" | "armv7a" | "armv7l" => {
macros.push(("__arm__".into(), "1".into()));
macros.push(("__arm".into(), "1".into()));
macros.push(("__ARM_ARCH_7A__".into(), "1".into()));
}
"armv6" | "armv6k" => {
macros.push(("__arm__".into(), "1".into()));
macros.push(("__ARM_ARCH_6K__".into(), "1".into()));
}
"riscv64" | "riscv64gc" => {
macros.push(("__riscv".into(), "1".into()));
macros.push(("__riscv_xlen".into(), "64".into()));
macros.push(("__riscv64".into(), "1".into()));
macros.push(("__riscv_compressed".into(), "1".into()));
macros.push(("__LP64__".into(), "1".into()));
}
"riscv32" => {
macros.push(("__riscv".into(), "1".into()));
macros.push(("__riscv_xlen".into(), "32".into()));
macros.push(("__riscv32".into(), "1".into()));
}
"mips64" | "mips64el" => {
macros.push(("__mips__".into(), "1".into()));
macros.push(("__mips64".into(), "1".into()));
macros.push(("_mips".into(), "1".into()));
}
"mips" | "mipsel" => {
macros.push(("__mips__".into(), "1".into()));
macros.push(("_mips".into(), "1".into()));
}
"powerpc64" | "powerpc64le" | "ppc64" | "ppc64le" => {
macros.push(("__powerpc64__".into(), "1".into()));
macros.push(("__powerpc__".into(), "1".into()));
macros.push(("__PPC64__".into(), "1".into()));
macros.push(("__LP64__".into(), "1".into()));
}
"powerpc" | "ppc" => {
macros.push(("__powerpc__".into(), "1".into()));
macros.push(("__PPC__".into(), "1".into()));
}
"s390x" | "systemz" => {
macros.push(("__s390x__".into(), "1".into()));
macros.push(("__s390__".into(), "1".into()));
macros.push(("__zarch__".into(), "1".into()));
macros.push(("__LP64__".into(), "1".into()));
}
"sparc64" => {
macros.push(("__sparc64__".into(), "1".into()));
macros.push(("__sparc__".into(), "1".into()));
macros.push(("__LP64__".into(), "1".into()));
}
"sparc" => {
macros.push(("__sparc__".into(), "1".into()));
}
_ => {
macros.push((format!("__{}__", self.arch), "1".into()));
}
}
}
fn add_os_macros(&self, macros: &mut Vec<(String, String)>) {
match self.os.as_str() {
"linux" => {
macros.push(("__linux__".into(), "1".into()));
macros.push(("__linux".into(), "1".into()));
macros.push(("linux".into(), "1".into()));
macros.push(("__gnu_linux__".into(), "1".into()));
macros.push(("__ELF__".into(), "1".into()));
macros.push(("__unix__".into(), "1".into()));
macros.push(("__unix".into(), "1".into()));
}
"darwin" | "macos" | "macosx" => {
macros.push(("__APPLE__".into(), "1".into()));
macros.push(("__MACH__".into(), "1".into()));
macros.push(("__APPLE_CC__".into(), "1".into()));
macros.push(("__MACH".into(), "1".into()));
macros.push(("__unix__".into(), "1".into()));
}
"freebsd" => {
macros.push(("__FreeBSD__".into(), "1".into()));
macros.push(("__FreeBSD".into(), "1".into()));
macros.push(("__unix__".into(), "1".into()));
macros.push(("__ELF__".into(), "1".into()));
}
"openbsd" => {
macros.push(("__OpenBSD__".into(), "1".into()));
macros.push(("__unix__".into(), "1".into()));
macros.push(("__ELF__".into(), "1".into()));
}
"netbsd" => {
macros.push(("__NetBSD__".into(), "1".into()));
macros.push(("__unix__".into(), "1".into()));
macros.push(("__ELF__".into(), "1".into()));
}
"windows" | "win32" | "win64" | "msvc" => {
macros.push(("_WIN32".into(), "1".into()));
if self.arch.contains("64") {
macros.push(("_WIN64".into(), "1".into()));
}
macros.push(("__CYGWIN__".into(), "1".into()));
}
"cygwin" => {
macros.push(("__CYGWIN__".into(), "1".into()));
macros.push(("__unix__".into(), "1".into()));
}
"android" => {
macros.push(("__ANDROID__".into(), "1".into()));
macros.push(("__linux__".into(), "1".into()));
macros.push(("__unix__".into(), "1".into()));
}
"emscripten" => {
macros.push(("__EMSCRIPTEN__".into(), "1".into()));
macros.push(("__wasm__".into(), "1".into()));
}
"wasi" => {
macros.push(("__wasi__".into(), "1".into()));
macros.push(("__wasm__".into(), "1".into()));
}
_ => {}
}
}
fn add_compiler_macros(&self, macros: &mut Vec<(String, String)>) {
macros.push(("__clang__".into(), "1".into()));
macros.push(("__clang_major__".into(), "18".into()));
macros.push(("__clang_minor__".into(), "1".into()));
macros.push(("__clang_patchlevel__".into(), "0".into()));
macros.push(("__clang_version__".into(), "\"18.1.0\"".into()));
macros.push(("__GNUC__".into(), "4".into()));
macros.push(("__GNUC_MINOR__".into(), "2".into()));
macros.push(("__GNUC_PATCHLEVEL__".into(), "1".into()));
macros.push(("__VERSION__".into(), "\"llvm-native 18.1.0\"".into()));
macros.push(("__STDC__".into(), "1".into()));
macros.push(("__STDC_HOSTED__".into(), "1".into()));
macros.push(("__STDC_VERSION__".into(), "201710L".into())); macros.push(("__STDC_UTF_16__".into(), "1".into()));
macros.push(("__STDC_UTF_32__".into(), "1".into()));
macros.push(("__BYTE_ORDER__".into(), "__ORDER_LITTLE_ENDIAN__".into()));
macros.push(("__ORDER_LITTLE_ENDIAN__".into(), "1234".into()));
macros.push(("__ORDER_BIG_ENDIAN__".into(), "4321".into()));
macros.push(("__SIZEOF_INT__".into(), "4".into()));
macros.push((
"__SIZEOF_LONG__".into(),
if self.arch.contains("64") { "8" } else { "4" }.into(),
));
macros.push((
"__SIZEOF_POINTER__".into(),
if self.arch.contains("64") { "8" } else { "4" }.into(),
));
macros.push((
"__SIZEOF_SIZE_T__".into(),
if self.arch.contains("64") { "8" } else { "4" }.into(),
));
macros.push(("__SIZEOF_WCHAR_T__".into(), "4".into()));
macros.push(("__SIZEOF_WINT_T__".into(), "4".into()));
macros.push(("__SIZEOF_FLOAT__".into(), "4".into()));
macros.push(("__SIZEOF_DOUBLE__".into(), "8".into()));
macros.push(("__SIZEOF_LONG_DOUBLE__".into(), "16".into()));
macros.push(("__SIZEOF_SHORT__".into(), "2".into()));
}
}
#[derive(Debug, Clone)]
pub struct FeatureTestRegistry {
pub builtins: Vec<String>,
pub features: Vec<String>,
pub headers: Vec<String>,
pub cpp_attributes: Vec<String>,
pub c_attributes: Vec<String>,
pub declspec_attributes: Vec<String>,
pub extensions: Vec<String>,
}
impl FeatureTestRegistry {
pub fn new() -> Self {
let mut reg = Self {
builtins: Vec::new(),
features: Vec::new(),
headers: Vec::new(),
cpp_attributes: Vec::new(),
c_attributes: Vec::new(),
declspec_attributes: Vec::new(),
extensions: Vec::new(),
};
reg.register_defaults();
reg
}
fn register_defaults(&mut self) {
let builtin_list = [
"__builtin_abs",
"__builtin_alloca",
"__builtin_assume",
"__builtin_bswap16",
"__builtin_bswap32",
"__builtin_bswap64",
"__builtin_clz",
"__builtin_clzl",
"__builtin_clzll",
"__builtin_ctz",
"__builtin_ctzl",
"__builtin_ctzll",
"__builtin_expect",
"__builtin_ffs",
"__builtin_frame_address",
"__builtin_memcmp",
"__builtin_memcpy",
"__builtin_memmove",
"__builtin_memset",
"__builtin_object_size",
"__builtin_popcount",
"__builtin_prefetch",
"__builtin_return_address",
"__builtin_strlen",
"__builtin_trap",
"__builtin_unreachable",
"__builtin_va_copy",
"__builtin_va_end",
"__builtin_va_start",
];
for b in &builtin_list {
self.builtins.push(b.to_string());
}
let feature_list = [
"address_sanitizer",
"thread_sanitizer",
"memory_sanitizer",
"undefined_behavior_sanitizer",
"cxx_exceptions",
"cxx_rtti",
"enumerator_attributes",
"attribute_analyzer_noreturn",
"attribute_availability",
"attribute_overloadable",
"blocks",
"modules",
"c_alignas",
"c_alignof",
"c_atomic",
"c_generic_selections",
"c_static_assert",
"c_thread_local",
"objc_arc",
];
for f in &feature_list {
self.features.push(f.to_string());
}
let header_list = [
"assert.h",
"complex.h",
"ctype.h",
"errno.h",
"fenv.h",
"float.h",
"inttypes.h",
"iso646.h",
"limits.h",
"locale.h",
"math.h",
"setjmp.h",
"signal.h",
"stdalign.h",
"stdarg.h",
"stdatomic.h",
"stdbool.h",
"stddef.h",
"stdint.h",
"stdio.h",
"stdlib.h",
"stdnoreturn.h",
"string.h",
"tgmath.h",
"threads.h",
"time.h",
"uchar.h",
"wchar.h",
"wctype.h",
];
for h in &header_list {
self.headers.push(h.to_string());
}
let cpp_attr_list = [
"carries_dependency",
"deprecated",
"fallthrough",
"likely",
"maybe_unused",
"no_unique_address",
"nodiscard",
"noreturn",
"unlikely",
"assume",
"noinline",
"always_inline",
];
for a in &cpp_attr_list {
self.cpp_attributes.push(a.to_string());
}
let c_attr_list = [
"aligned",
"always_inline",
"cold",
"const",
"constructor",
"deprecated",
"destructor",
"format",
"gnu_inline",
"hot",
"malloc",
"noinline",
"nonnull",
"noreturn",
"nothrow",
"packed",
"pure",
"section",
"sentinel",
"unused",
"used",
"visibility",
"warn_unused_result",
"weak",
];
for a in &c_attr_list {
self.c_attributes.push(a.to_string());
}
let declspec_list = [
"align",
"allocate",
"deprecated",
"dllexport",
"dllimport",
"naked",
"noinline",
"noreturn",
"nothrow",
"novtable",
"property",
"selectany",
"thread",
"uuid",
];
for d in &declspec_list {
self.declspec_attributes.push(d.to_string());
}
let ext_list = [
"c_alignas",
"c_alignof",
"c_atomic",
"c_generic_selections",
"c_static_assert",
"c_thread_local",
"cxx_variadic_templates",
"cxx_default_function_template_args",
"cxx_rvalue_references",
"cxx_static_assert",
"cxx_decltype",
"cxx_auto_type",
"cxx_nullptr",
"cxx_range_for",
"overloadable",
];
for e in &ext_list {
self.extensions.push(e.to_string());
}
}
pub fn has_builtin(&self, name: &str) -> bool {
self.builtins.iter().any(|b| b == name)
}
pub fn has_feature(&self, name: &str) -> bool {
self.features.iter().any(|f| f == name)
}
pub fn has_include(&self, name: &str) -> bool {
self.headers.iter().any(|h| h == name)
}
pub fn has_cpp_attribute(&self, name: &str) -> bool {
self.cpp_attributes.iter().any(|a| a == name)
}
pub fn has_attribute(&self, name: &str) -> bool {
self.c_attributes.iter().any(|a| a == name)
}
pub fn has_declspec_attribute(&self, name: &str) -> bool {
self.declspec_attributes.iter().any(|a| a == name)
}
pub fn has_extension(&self, name: &str) -> bool {
self.extensions.iter().any(|e| e == name)
}
}
impl Default for FeatureTestRegistry {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone)]
pub struct ConstantExprEvaluator {
defines: HashMap<String, MacroDef>,
short_circuit: bool,
}
impl ConstantExprEvaluator {
pub fn new(defines: HashMap<String, MacroDef>) -> Self {
Self {
defines,
short_circuit: true,
}
}
pub fn evaluate(&self, tokens: &[Token]) -> Option<i64> {
self.eval_conditional(tokens, 0).map(|(val, _)| val)
}
fn eval_conditional(&self, tokens: &[Token], pos: usize) -> Option<(i64, usize)> {
self.eval_logical_or(tokens, pos)
}
fn eval_logical_or(&self, tokens: &[Token], pos: usize) -> Option<(i64, usize)> {
let (mut left, mut pos) = self.eval_logical_and(tokens, pos)?;
while pos < tokens.len() && tokens[pos].kind == TokenKind::OrOr {
pos += 1;
if self.short_circuit && left != 0 {
return Some((1, self.skip_rest_of_or(tokens, pos)));
}
let (right, new_pos) = self.eval_logical_and(tokens, pos)?;
left = if left != 0 || right != 0 { 1 } else { 0 };
pos = new_pos;
}
Some((left, pos))
}
fn eval_logical_and(&self, tokens: &[Token], pos: usize) -> Option<(i64, usize)> {
let (mut left, mut pos) = self.eval_bitwise_or(tokens, pos)?;
while pos < tokens.len() && tokens[pos].kind == TokenKind::AndAnd {
pos += 1;
if self.short_circuit && left == 0 {
return Some((0, self.skip_rest_of_and(tokens, pos)));
}
let (right, new_pos) = self.eval_bitwise_or(tokens, pos)?;
left = if left != 0 && right != 0 { 1 } else { 0 };
pos = new_pos;
}
Some((left, pos))
}
fn eval_bitwise_or(&self, tokens: &[Token], pos: usize) -> Option<(i64, usize)> {
let (mut left, mut pos) = self.eval_bitwise_xor(tokens, pos)?;
while pos < tokens.len() && tokens[pos].text == "|" && tokens[pos].kind != TokenKind::OrOr {
pos += 1;
let (right, new_pos) = self.eval_bitwise_xor(tokens, pos)?;
left |= right;
pos = new_pos;
}
Some((left, pos))
}
fn eval_bitwise_xor(&self, tokens: &[Token], pos: usize) -> Option<(i64, usize)> {
let (mut left, mut pos) = self.eval_bitwise_and(tokens, pos)?;
while pos < tokens.len() && tokens[pos].text == "^" {
pos += 1;
let (right, new_pos) = self.eval_bitwise_and(tokens, pos)?;
left ^= right;
pos = new_pos;
}
Some((left, pos))
}
fn eval_bitwise_and(&self, tokens: &[Token], pos: usize) -> Option<(i64, usize)> {
let (mut left, mut pos) = self.eval_equality(tokens, pos)?;
while pos < tokens.len() && tokens[pos].text == "&" && tokens[pos].kind != TokenKind::AndAnd
{
pos += 1;
let (right, new_pos) = self.eval_equality(tokens, pos)?;
left &= right;
pos = new_pos;
}
Some((left, pos))
}
fn eval_equality(&self, tokens: &[Token], pos: usize) -> Option<(i64, usize)> {
let (mut left, mut pos) = self.eval_relational(tokens, pos)?;
while pos < tokens.len() {
let is_eq = tokens[pos].kind == TokenKind::EqualEqual;
let is_ne = tokens[pos].kind == TokenKind::NotEqual;
if !is_eq && !is_ne {
break;
}
pos += 1;
let (right, new_pos) = self.eval_relational(tokens, pos)?;
left = if is_eq {
if left == right {
1
} else {
0
}
} else {
if left != right {
1
} else {
0
}
};
pos = new_pos;
}
Some((left, pos))
}
fn eval_relational(&self, tokens: &[Token], pos: usize) -> Option<(i64, usize)> {
let (mut left, mut pos) = self.eval_shift(tokens, pos)?;
while pos < tokens.len() {
let kind = &tokens[pos].kind;
let is_lt = *kind == TokenKind::Less;
let is_gt = *kind == TokenKind::Greater;
let is_le = *kind == TokenKind::LessEqual;
let is_ge = *kind == TokenKind::GreaterEqual;
if !is_lt && !is_gt && !is_le && !is_ge {
break;
}
pos += 1;
let (right, new_pos) = self.eval_shift(tokens, pos)?;
left = if is_lt {
if left < right {
1
} else {
0
}
} else if is_gt {
if left > right {
1
} else {
0
}
} else if is_le {
if left <= right {
1
} else {
0
}
} else {
if left >= right {
1
} else {
0
}
};
pos = new_pos;
}
Some((left, pos))
}
fn eval_shift(&self, tokens: &[Token], pos: usize) -> Option<(i64, usize)> {
let (mut left, mut pos) = self.eval_additive(tokens, pos)?;
while pos < tokens.len() {
let text = &tokens[pos].text;
let is_shl = text == "<<";
let is_shr = text == ">>";
if !is_shl && !is_shr {
break;
}
pos += 1;
let (right, new_pos) = self.eval_additive(tokens, pos)?;
if right < 0 || right >= 64 {
return None; }
left = if is_shl { left << right } else { left >> right };
pos = new_pos;
}
Some((left, pos))
}
fn eval_additive(&self, tokens: &[Token], pos: usize) -> Option<(i64, usize)> {
let (mut left, mut pos) = self.eval_multiplicative(tokens, pos)?;
while pos < tokens.len() {
let is_plus = tokens[pos].kind == TokenKind::Plus;
let is_minus = tokens[pos].kind == TokenKind::Minus;
if !is_plus && !is_minus {
break;
}
pos += 1;
let (right, new_pos) = self.eval_multiplicative(tokens, pos)?;
left = if is_plus {
left.wrapping_add(right)
} else {
left.wrapping_sub(right)
};
pos = new_pos;
}
Some((left, pos))
}
fn eval_multiplicative(&self, tokens: &[Token], pos: usize) -> Option<(i64, usize)> {
let (mut left, mut pos) = self.eval_unary(tokens, pos)?;
while pos < tokens.len() {
let text = &tokens[pos].text;
let is_mul = tokens[pos].kind == TokenKind::Star;
let is_div = text == "/";
let is_mod = text == "%";
if !is_mul && !is_div && !is_mod {
break;
}
pos += 1;
let (right, new_pos) = self.eval_unary(tokens, pos)?;
if (is_div || is_mod) && right == 0 {
return None; }
left = if is_mul {
left.wrapping_mul(right)
} else if is_div {
left / right
} else {
left % right
};
pos = new_pos;
}
Some((left, pos))
}
fn eval_unary(&self, tokens: &[Token], pos: usize) -> Option<(i64, usize)> {
if pos >= tokens.len() {
return None;
}
let token = &tokens[pos];
if token.kind == TokenKind::Plus {
return self.eval_unary(tokens, pos + 1);
}
if token.kind == TokenKind::Minus {
let (val, new_pos) = self.eval_unary(tokens, pos + 1)?;
return Some((-val, new_pos));
}
if token.text == "~" {
let (val, new_pos) = self.eval_unary(tokens, pos + 1)?;
return Some((!val, new_pos));
}
if token.kind == TokenKind::Exclaim {
let (val, new_pos) = self.eval_unary(tokens, pos + 1)?;
return Some((if val == 0 { 1 } else { 0 }, new_pos));
}
if token.text == "defined" {
return self.eval_defined(tokens, pos);
}
self.eval_primary(tokens, pos)
}
fn eval_defined(&self, tokens: &[Token], pos: usize) -> Option<(i64, usize)> {
let mut pos = pos + 1;
if pos >= tokens.len() {
return None;
}
let has_paren = tokens[pos].kind == TokenKind::LParen;
if has_paren {
pos += 1;
if pos >= tokens.len() {
return None;
}
}
if tokens[pos].kind == TokenKind::Identifier {
let name = &tokens[pos].text;
let is_defined = self.defines.contains_key(name);
pos += 1;
if has_paren {
if pos >= tokens.len() || tokens[pos].kind != TokenKind::RParen {
return None;
}
pos += 1;
}
return Some((if is_defined { 1 } else { 0 }, pos));
}
None
}
fn eval_primary(&self, tokens: &[Token], pos: usize) -> Option<(i64, usize)> {
if pos >= tokens.len() {
return None;
}
let token = &tokens[pos];
if token.kind == TokenKind::LParen {
let (val, pos) = self.eval_conditional(tokens, pos + 1)?;
if pos >= tokens.len() || tokens[pos].kind != TokenKind::RParen {
return None;
}
return Some((val, pos + 1));
}
if token.kind == TokenKind::NumericLiteral {
let val = parse_pp_integer(&token.text)?;
return Some((val, pos + 1));
}
if token.kind == TokenKind::Identifier {
return Some((0, pos + 1));
}
None
}
fn skip_rest_of_or(&self, tokens: &[Token], pos: usize) -> usize {
let mut depth = 0;
let mut p = pos;
while p < tokens.len() {
match tokens[p].kind {
TokenKind::LParen => depth += 1,
TokenKind::RParen => {
if depth == 0 {
return p;
}
depth -= 1;
}
_ => {}
}
p += 1;
}
p
}
fn skip_rest_of_and(&self, tokens: &[Token], pos: usize) -> usize {
let mut depth = 0;
let mut p = pos;
while p < tokens.len() {
match tokens[p].kind {
TokenKind::LParen => depth += 1,
TokenKind::RParen => {
if depth == 0 {
return p;
}
depth -= 1;
}
TokenKind::OrOr => {
if depth == 0 {
return p;
}
}
_ => {}
}
p += 1;
}
p
}
}
pub fn parse_pp_integer(text: &str) -> Option<i64> {
let text = text.trim();
if text.starts_with("0x") || text.starts_with("0X") {
i64::from_str_radix(&text[2..], 16)
.ok()
.or_else(|| u64::from_str_radix(&text[2..], 16).ok().map(|v| v as i64))
} else if text.starts_with('0') && text.len() > 1 && !text.contains('.') {
i64::from_str_radix(&text[1..], 8).ok()
} else {
text.parse::<i64>()
.ok()
.or_else(|| text.parse::<u64>().ok().map(|v| v as i64))
}
}
#[derive(Debug, Clone)]
pub struct IncludeGuardDetector {
pub guard_macro: Option<String>,
pub pragma_once: bool,
guard_range: Option<(usize, usize)>,
detected: bool,
}
impl IncludeGuardDetector {
pub fn new() -> Self {
Self {
guard_macro: None,
pragma_once: false,
guard_range: None,
detected: false,
}
}
pub fn analyse_line(&mut self, line: &str, line_num: usize) -> bool {
let trimmed = line.trim();
if trimmed.starts_with("#pragma once") {
self.pragma_once = true;
self.detected = true;
return true;
}
if !self.detected && self.guard_macro.is_none() {
if let Some(rest) = trimmed.strip_prefix("#ifndef ") {
let macro_name = rest.trim().to_string();
if !macro_name.is_empty() && Self::is_valid_macro_name(¯o_name) {
self.guard_macro = Some(macro_name);
if let Some(ref mut range) = self.guard_range {
range.0 = line_num;
} else {
self.guard_range = Some((line_num, 0));
}
}
}
}
if !self.detected && self.guard_macro.is_some() {
if let Some(rest) = trimmed.strip_prefix("#define ") {
let parts: Vec<&str> = rest.splitn(2, ' ').collect();
if let Some(name) = parts.first() {
if let Some(ref guard) = self.guard_macro {
if *name == *guard {
self.detected = true;
if let Some(ref mut range) = self.guard_range {
range.1 = line_num;
}
return true;
}
}
}
}
}
false
}
pub fn should_skip(&self, defined_macros: &std::collections::HashSet<String>) -> bool {
if self.pragma_once {
return true;
}
if let Some(ref guard) = self.guard_macro {
if defined_macros.contains(guard) {
return true;
}
}
false
}
fn is_valid_macro_name(name: &str) -> bool {
if name.is_empty() {
return false;
}
let first = name.chars().next().unwrap();
if !first.is_ascii_alphabetic() && first != '_' {
return false;
}
name.chars().all(|c| c.is_ascii_alphanumeric() || c == '_')
}
}
impl Default for IncludeGuardDetector {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct FileIdentity {
pub device: u64,
pub inode: u64,
}
impl FileIdentity {
pub fn new(device: u64, inode: u64) -> Self {
Self { device, inode }
}
#[cfg(unix)]
pub fn from_path(path: &std::path::Path) -> Option<Self> {
use std::os::unix::fs::MetadataExt;
std::fs::metadata(path).ok().map(|m| Self {
device: m.dev(),
inode: m.ino(),
})
}
#[cfg(not(unix))]
pub fn from_path(path: &std::path::Path) -> Option<Self> {
path.canonicalize().ok().map(|_| Self {
device: 0,
inode: 0,
})
}
}
#[derive(Debug, Clone)]
pub struct HeaderSearchCache {
entries: std::collections::HashMap<String, (String, Option<FileIdentity>)>,
seen_files: std::collections::HashSet<FileIdentity>,
max_entries: usize,
hits: u64,
misses: u64,
}
impl HeaderSearchCache {
pub fn new() -> Self {
Self {
entries: std::collections::HashMap::new(),
seen_files: std::collections::HashSet::new(),
max_entries: 512,
hits: 0,
misses: 0,
}
}
pub fn lookup(&self, header_name: &str) -> Option<&str> {
self.entries.get(header_name).map(|(path, _)| path.as_str())
}
pub fn insert(&mut self, header_name: &str, file_path: &str, identity: Option<FileIdentity>) {
if self.entries.len() >= self.max_entries {
let keys: Vec<String> = self
.entries
.keys()
.take(self.max_entries / 2)
.cloned()
.collect();
for k in keys {
self.entries.remove(&k);
}
}
self.entries
.insert(header_name.to_string(), (file_path.to_string(), identity));
}
pub fn mark_seen(&mut self, identity: FileIdentity) {
self.seen_files.insert(identity);
}
pub fn is_seen(&self, identity: &FileIdentity) -> bool {
self.seen_files.contains(identity)
}
pub fn record_hit(&mut self) {
self.hits += 1;
}
pub fn record_miss(&mut self) {
self.misses += 1;
}
pub fn stats(&self) -> (u64, u64, f64) {
let total = self.hits + self.misses;
let hit_rate = if total > 0 {
self.hits as f64 / total as f64
} else {
0.0
};
(self.hits, self.misses, hit_rate)
}
pub fn clear(&mut self) {
self.entries.clear();
self.seen_files.clear();
self.hits = 0;
self.misses = 0;
}
pub fn len(&self) -> usize {
self.entries.len()
}
pub fn is_empty(&self) -> bool {
self.entries.is_empty()
}
}
impl Default for HeaderSearchCache {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone)]
pub struct FileRemapper {
remappings: std::collections::HashMap<String, String>,
rewrite_includes: bool,
rewrite_line_directives: bool,
}
impl FileRemapper {
pub fn new() -> Self {
Self {
remappings: std::collections::HashMap::new(),
rewrite_includes: false,
rewrite_line_directives: false,
}
}
pub fn add_remap(&mut self, from: &str, to: &str) {
self.remappings.insert(from.to_string(), to.to_string());
}
pub fn add_remap_from_flag(&mut self, flag_value: &str) {
if let Some(pos) = flag_value.find(';') {
let from = &flag_value[..pos];
let to = &flag_value[pos + 1..];
self.add_remap(from, to);
}
}
pub fn remap<'a>(&'a self, path: &'a str) -> &'a str {
self.remappings
.get(path)
.map(|s| s.as_str())
.unwrap_or(path)
}
pub fn has_remap(&self, path: &str) -> bool {
self.remappings.contains_key(path)
}
pub fn enable_rewrite_includes(mut self) -> Self {
self.rewrite_includes = true;
self
}
pub fn enable_rewrite_lines(mut self) -> Self {
self.rewrite_line_directives = true;
self
}
pub fn is_rewriting_includes(&self) -> bool {
self.rewrite_includes
}
pub fn is_rewriting_lines(&self) -> bool {
self.rewrite_line_directives
}
pub fn remappings(&self) -> &std::collections::HashMap<String, String> {
&self.remappings
}
}
impl Default for FileRemapper {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone)]
pub struct DependencyGenerator {
pub included_files: Vec<String>,
pub output_file: Option<String>,
pub target: Option<String>,
pub include_system_headers: bool,
pub phony_targets: bool,
pub format: DependencyFormat,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum DependencyFormat {
Makefile,
NMake,
Depfile,
}
impl DependencyGenerator {
pub fn new() -> Self {
Self {
included_files: Vec::new(),
output_file: None,
target: None,
include_system_headers: false,
phony_targets: false,
format: DependencyFormat::Makefile,
}
}
pub fn add_included_file(&mut self, path: &str, is_system: bool) {
if !is_system || self.include_system_headers {
self.included_files.push(path.to_string());
}
}
pub fn with_output(mut self, output: &str) -> Self {
self.output_file = Some(output.to_string());
self
}
pub fn with_target(mut self, target: &str) -> Self {
self.target = Some(target.to_string());
self
}
pub fn with_format(mut self, format: DependencyFormat) -> Self {
self.format = format;
self
}
pub fn generate(&self) -> String {
match self.format {
DependencyFormat::Makefile => self.generate_makefile(),
DependencyFormat::NMake => self.generate_nmake(),
DependencyFormat::Depfile => self.generate_depfile(),
}
}
fn generate_makefile(&self) -> String {
let mut s = String::new();
let target = self.target.as_deref().unwrap_or("a.out");
let escaped_target = target.replace(' ', "\\ ");
s.push_str(&escaped_target);
s.push_str(":");
if self.included_files.is_empty() {
s.push('\n');
return s;
}
for (i, file) in self.included_files.iter().enumerate() {
if i > 0 {
s.push_str(" \\\n ");
} else {
s.push(' ');
}
let escaped = file.replace(' ', "\\ ");
s.push_str(&escaped);
}
s.push('\n');
if self.phony_targets {
s.push('\n');
for file in &self.included_files {
let escaped = file.replace(' ', "\\ ");
s.push_str(&format!("{}:\n", escaped));
}
}
s
}
fn generate_nmake(&self) -> String {
let mut s = String::new();
let target = self.target.as_deref().unwrap_or("a.out");
s.push_str(&format!("\"{}\":", target));
for file in &self.included_files {
s.push_str(&format!(" \"{}\"", file));
}
s.push('\n');
if self.phony_targets {
for file in &self.included_files {
s.push_str(&format!("\"{}\":\n", file));
}
}
s
}
fn generate_depfile(&self) -> String {
let mut s = String::new();
for file in &self.included_files {
s.push_str(file);
s.push('\n');
}
s
}
pub fn write_to_file(&self) -> std::io::Result<()> {
let content = self.generate();
if let Some(ref output) = self.output_file {
std::fs::write(output, content)?;
}
Ok(())
}
pub fn file_count(&self) -> usize {
self.included_files.len()
}
pub fn clear(&mut self) {
self.included_files.clear();
}
pub fn dedup(&mut self) {
let mut seen = std::collections::HashSet::new();
self.included_files.retain(|f| seen.insert(f.clone()));
}
}
impl Default for DependencyGenerator {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone)]
pub struct ModuleMapDiscovery {
pub enabled: bool,
discovered: std::collections::HashMap<String, String>,
scanned_dirs: std::collections::HashSet<String>,
}
impl ModuleMapDiscovery {
pub fn new() -> Self {
Self {
enabled: true,
discovered: std::collections::HashMap::new(),
scanned_dirs: std::collections::HashSet::new(),
}
}
pub fn discover_for_header(&mut self, header_path: &str) -> Option<String> {
if !self.enabled {
return None;
}
let path = std::path::Path::new(header_path);
let parent = path.parent()?;
let parent_str = parent.to_string_lossy().to_string();
if let Some(cached) = self.discovered.get(&parent_str) {
return Some(cached.clone());
}
if self.scanned_dirs.contains(&parent_str) {
return None;
}
let candidates: [Option<std::path::PathBuf>; 3] = [
Some(parent.join("module.modulemap")),
Some(parent.join("Modules").join("module.modulemap")),
parent.parent().map(|p| p.join("module.modulemap")),
];
for candidate in candidates.iter().flatten() {
if candidate.is_file() {
let candidate_str = candidate.to_string_lossy().to_string();
self.discovered
.insert(parent_str.clone(), candidate_str.clone());
return Some(candidate_str);
}
}
self.scanned_dirs.insert(parent_str);
None
}
pub fn has_module_map(&self, directory: &str) -> bool {
self.discovered.contains_key(directory)
}
pub fn get_module_map(&self, directory: &str) -> Option<&str> {
self.discovered.get(directory).map(|s| s.as_str())
}
pub fn clear(&mut self) {
self.discovered.clear();
self.scanned_dirs.clear();
}
}
impl Default for ModuleMapDiscovery {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone)]
pub struct PragmaOnceTracker {
once_by_path: std::collections::HashSet<String>,
seen_by_identity: std::collections::HashSet<FileIdentity>,
identity_tracking: bool,
}
impl PragmaOnceTracker {
pub fn new() -> Self {
Self {
once_by_path: std::collections::HashSet::new(),
seen_by_identity: std::collections::HashSet::new(),
identity_tracking: true,
}
}
pub fn mark_pragma_once(&mut self, path: &str) {
self.once_by_path.insert(path.to_string());
}
pub fn mark_processed(&mut self, path: &str, identity: Option<FileIdentity>) {
self.once_by_path.insert(path.to_string());
if let Some(id) = identity {
self.seen_by_identity.insert(id);
}
}
pub fn should_skip(&self, path: &str, identity: Option<FileIdentity>) -> bool {
if self.once_by_path.contains(path) {
return true;
}
if self.identity_tracking {
if let Some(id) = identity {
if self.seen_by_identity.contains(&id) {
return true;
}
}
}
false
}
pub fn is_pragma_once(&self, path: &str) -> bool {
self.once_by_path.contains(path)
}
pub fn clear(&mut self) {
self.once_by_path.clear();
self.seen_by_identity.clear();
}
pub fn count(&self) -> usize {
self.once_by_path.len()
}
pub fn set_identity_tracking(&mut self, enabled: bool) {
self.identity_tracking = enabled;
}
}
impl Default for PragmaOnceTracker {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone)]
pub struct IncludePathResolver {
pub include_paths: Vec<super::preprocessor::IncludePath>,
cache: HeaderSearchCache,
pub max_depth: usize,
}
impl IncludePathResolver {
pub fn new(include_paths: Vec<super::preprocessor::IncludePath>) -> Self {
Self {
include_paths,
cache: HeaderSearchCache::new(),
max_depth: 200,
}
}
pub fn resolve(
&mut self,
name: &str,
is_quoted: bool,
current_file_dir: Option<&std::path::Path>,
) -> Option<String> {
if let Some(cached) = self.cache.lookup(name) {
let result = cached.to_string();
self.cache.record_hit();
return Some(result);
}
self.cache.record_miss();
if is_quoted {
if let Some(dir) = current_file_dir {
let candidate = dir.join(name);
if candidate.is_file() {
let result = candidate.to_string_lossy().to_string();
let identity = FileIdentity::from_path(&candidate);
self.cache.insert(name, &result, identity);
return Some(result);
}
}
}
for ip in &self.include_paths {
let candidate = std::path::Path::new(&ip.path).join(name);
let candidate_str = candidate.to_string_lossy().to_string();
if candidate.is_file() {
let identity = FileIdentity::from_path(&candidate);
self.cache.insert(name, &candidate_str, identity);
return Some(candidate_str);
}
if ip.is_framework {
if let Some(stripped) = name.strip_suffix(".h") {
let parent = std::path::Path::new(&ip.path);
if let Some(parent_parent) = parent.parent() {
let framework_header = parent_parent
.join(format!("{}.framework", stripped))
.join("Headers")
.join(name);
if framework_header.is_file() {
let result = framework_header.to_string_lossy().to_string();
let identity = FileIdentity::from_path(&framework_header);
self.cache.insert(name, &result, identity);
return Some(result);
}
}
}
}
}
None
}
pub fn cache_stats(&self) -> (u64, u64, f64) {
self.cache.stats()
}
pub fn clear_cache(&mut self) {
self.cache.clear();
}
}
#[derive(Debug, Clone)]
pub struct MacroExpansionCache {
expansions: std::collections::HashMap<(String, Vec<String>), Vec<String>>,
hits: u64,
misses: u64,
max_entries: usize,
}
impl MacroExpansionCache {
pub fn new() -> Self {
Self {
expansions: std::collections::HashMap::new(),
hits: 0,
misses: 0,
max_entries: 256,
}
}
pub fn lookup(&self, name: &str, args: &[String]) -> Option<&Vec<String>> {
let key = (name.to_string(), args.to_vec());
self.expansions.get(&key)
}
pub fn insert(&mut self, name: &str, args: &[String], result: Vec<String>) {
if self.expansions.len() >= self.max_entries {
let keys: Vec<_> = self
.expansions
.keys()
.take(self.max_entries / 2)
.cloned()
.collect();
for k in keys {
self.expansions.remove(&k);
}
}
self.expansions
.insert((name.to_string(), args.to_vec()), result);
}
pub fn record_hit(&mut self) {
self.hits += 1;
}
pub fn record_miss(&mut self) {
self.misses += 1;
}
pub fn hit_rate(&self) -> f64 {
let total = self.hits + self.misses;
if total == 0 {
0.0
} else {
self.hits as f64 / total as f64
}
}
pub fn clear(&mut self) {
self.expansions.clear();
self.hits = 0;
self.misses = 0;
}
}
impl Default for MacroExpansionCache {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone)]
pub struct TokenBufferPool {
free_buffers: Vec<Vec<crate::clang::token::Token>>,
allocated: usize,
reused: usize,
max_pool_size: usize,
}
impl TokenBufferPool {
pub fn new() -> Self {
Self {
free_buffers: Vec::new(),
allocated: 0,
reused: 0,
max_pool_size: 32,
}
}
pub fn acquire(&mut self) -> Vec<crate::clang::token::Token> {
if let Some(mut buf) = self.free_buffers.pop() {
buf.clear();
self.reused += 1;
buf
} else {
self.allocated += 1;
Vec::with_capacity(256)
}
}
pub fn release(&mut self, mut buf: Vec<crate::clang::token::Token>) {
if self.free_buffers.len() < self.max_pool_size {
buf.clear();
self.free_buffers.push(buf);
}
}
pub fn stats(&self) -> (usize, usize) {
(self.allocated, self.reused)
}
pub fn clear(&mut self) {
self.free_buffers.clear();
self.allocated = 0;
self.reused = 0;
}
}
impl Default for TokenBufferPool {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone, Default)]
pub struct PreprocessingStats {
pub files_processed: usize,
pub files_skipped_guard: usize,
pub files_skipped_pragma_once: usize,
pub files_skipped_multiple_include: usize,
pub total_lines: usize,
pub total_tokens: usize,
pub macros_defined: usize,
pub macros_undefined: usize,
pub macros_expanded: usize,
pub macro_expansion_depth_max: usize,
pub conditionals_evaluated: usize,
pub includes_resolved: usize,
pub includes_not_found: usize,
pub warnings_emitted: usize,
pub errors_emitted: usize,
pub time_ns: u64,
pub cache_hits: u64,
pub cache_misses: u64,
}
impl PreprocessingStats {
pub fn new() -> Self {
Self::default()
}
pub fn record_file_processed(&mut self) {
self.files_processed += 1;
}
pub fn record_skip_guard(&mut self) {
self.files_skipped_guard += 1;
}
pub fn record_skip_pragma_once(&mut self) {
self.files_skipped_pragma_once += 1;
}
pub fn record_skip_multiple(&mut self) {
self.files_skipped_multiple_include += 1;
}
pub fn record_lines(&mut self, count: usize) {
self.total_lines += count;
}
pub fn record_tokens(&mut self, count: usize) {
self.total_tokens += count;
}
pub fn record_macro_defined(&mut self) {
self.macros_defined += 1;
}
pub fn record_macro_undefined(&mut self) {
self.macros_undefined += 1;
}
pub fn record_macro_expanded(&mut self, depth: usize) {
self.macros_expanded += 1;
if depth > self.macro_expansion_depth_max {
self.macro_expansion_depth_max = depth;
}
}
pub fn record_conditional_evaluated(&mut self) {
self.conditionals_evaluated += 1;
}
pub fn record_include_resolved(&mut self) {
self.includes_resolved += 1;
}
pub fn record_include_not_found(&mut self) {
self.includes_not_found += 1;
}
pub fn record_warning(&mut self) {
self.warnings_emitted += 1;
}
pub fn record_error(&mut self) {
self.errors_emitted += 1;
}
pub fn set_time_ns(&mut self, ns: u64) {
self.time_ns = ns;
}
pub fn hit_rate(&self) -> f64 {
let total = self.cache_hits + self.cache_misses;
if total == 0 {
0.0
} else {
self.cache_hits as f64 / total as f64
}
}
pub fn summary(&self) -> String {
format!(
"Preprocessed {} files ({} skipped guard, {} pragma once, {} multiple), {} lines, {} tokens, {} macros defined, {} expanded, {} includes resolved ({} not found), {} errors, {} warnings",
self.files_processed,
self.files_skipped_guard,
self.files_skipped_pragma_once,
self.files_skipped_multiple_include,
self.total_lines,
self.total_tokens,
self.macros_defined,
self.macros_expanded,
self.includes_resolved,
self.includes_not_found,
self.errors_emitted,
self.warnings_emitted
)
}
pub fn clear(&mut self) {
*self = Self::default();
}
}
#[derive(Debug, Clone)]
pub struct IncludeGraph {
edges: Vec<(String, String)>,
node_files: std::collections::HashSet<String>,
current_chain: Vec<String>,
max_depth_reached: usize,
cycles_detected: usize,
}
impl IncludeGraph {
pub fn new() -> Self {
Self {
edges: Vec::new(),
node_files: std::collections::HashSet::new(),
current_chain: Vec::new(),
max_depth_reached: 0,
cycles_detected: 0,
}
}
pub fn record_include(&mut self, includer: &str, included: &str) -> bool {
self.edges
.push((includer.to_string(), included.to_string()));
self.node_files.insert(includer.to_string());
self.node_files.insert(included.to_string());
let has_cycle = self.current_chain.contains(&included.to_string());
if has_cycle {
self.cycles_detected += 1;
}
!has_cycle
}
pub fn push_file(&mut self, file: &str) {
self.current_chain.push(file.to_string());
if self.current_chain.len() > self.max_depth_reached {
self.max_depth_reached = self.current_chain.len();
}
}
pub fn pop_file(&mut self) {
self.current_chain.pop();
}
pub fn include_depth(&self) -> usize {
self.current_chain.len()
}
pub fn max_depth(&self) -> usize {
self.max_depth_reached
}
pub fn total_files(&self) -> usize {
self.node_files.len()
}
pub fn total_edges(&self) -> usize {
self.edges.len()
}
pub fn cycles_found(&self) -> usize {
self.cycles_detected
}
pub fn generate_dot(&self) -> String {
let mut s = String::from("digraph IncludeGraph {\n");
s.push_str(" rankdir=LR;\n");
s.push_str(" node [shape=box, style=rounded];\n");
for node in &self.node_files {
let label = node.rsplit('/').next().unwrap_or(node);
s.push_str(&format!(" \"{}\" [label=\"{}\"];\n", node, label));
}
for (from, to) in &self.edges {
s.push_str(&format!(" \"{}\" -> \"{}\";\n", from, to));
}
s.push_str("}\n");
s
}
pub fn clear(&mut self) {
self.edges.clear();
self.node_files.clear();
self.current_chain.clear();
self.max_depth_reached = 0;
self.cycles_detected = 0;
}
pub fn list_files_by_deps(&self) -> Vec<(String, usize)> {
let mut dep_counts: std::collections::HashMap<String, usize> =
std::collections::HashMap::new();
for (from, _to) in &self.edges {
*dep_counts.entry(from.clone()).or_insert(0) += 1;
}
let mut result: Vec<(String, usize)> = dep_counts.into_iter().collect();
result.sort_by(|a, b| b.1.cmp(&a.1));
result
}
}
impl Default for IncludeGraph {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone)]
pub struct ConditionalInclusionTracker {
stack: Vec<CondInclusionState>,
skip_depth: usize,
branch_taken: Vec<bool>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum CondInclusionState {
Active,
Skipping,
SawElse,
}
impl ConditionalInclusionTracker {
pub fn new() -> Self {
Self {
stack: Vec::new(),
skip_depth: 0,
branch_taken: Vec::new(),
}
}
pub fn is_active(&self) -> bool {
self.skip_depth == 0
}
pub fn push_if(&mut self, condition_true: bool) {
if self.is_active() {
if condition_true {
self.stack.push(CondInclusionState::Active);
self.branch_taken.push(true);
} else {
self.stack.push(CondInclusionState::Skipping);
self.skip_depth += 1;
self.branch_taken.push(false);
}
} else {
self.stack.push(CondInclusionState::Skipping);
self.skip_depth += 1;
self.branch_taken.push(false);
}
}
pub fn push_else(&mut self) -> bool {
if self.stack.is_empty() {
return false;
}
let top = self.stack.last_mut().unwrap();
if *top == CondInclusionState::Skipping && self.skip_depth == 1 {
*top = CondInclusionState::Active;
self.skip_depth -= 1;
true
} else if *top == CondInclusionState::Active && self.skip_depth == 0 {
*top = CondInclusionState::SawElse;
self.skip_depth += 1;
false
} else {
false
}
}
pub fn push_elif(&mut self, condition_true: bool) -> bool {
if self.stack.is_empty() {
return false;
}
let top = self.stack.last_mut().unwrap();
if *top == CondInclusionState::Skipping && self.skip_depth == 1 {
if condition_true {
*top = CondInclusionState::Active;
self.skip_depth -= 1;
true
} else {
false
}
} else if *top == CondInclusionState::Active || *top == CondInclusionState::SawElse {
*top = CondInclusionState::SawElse;
if self.skip_depth == 0 {
self.skip_depth += 1;
}
false
} else {
false
}
}
pub fn pop(&mut self) {
if let Some(state) = self.stack.pop() {
if state == CondInclusionState::Skipping || state == CondInclusionState::SawElse {
if self.skip_depth > 0 {
self.skip_depth -= 1;
}
}
self.branch_taken.pop();
}
}
pub fn depth(&self) -> usize {
self.stack.len()
}
pub fn current_state(&self) -> CondInclusionState {
self.stack
.last()
.copied()
.unwrap_or(CondInclusionState::Active)
}
}
impl Default for ConditionalInclusionTracker {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
fn tok(kind: TokenKind, text: &str) -> Token {
Token::new(kind, text, SourceLoc::unknown())
}
fn ident(name: &str) -> Token {
tok(TokenKind::Identifier, name)
}
fn num(val: &str) -> Token {
tok(TokenKind::NumericLiteral, val)
}
#[test]
fn test_macro_def_object_like() {
let body = vec![num("42")];
let m = MacroDef::object_like("FOO", body.clone());
assert_eq!(m.name, "FOO");
assert!(m.params.is_empty());
assert!(!m.is_function_like);
assert!(!m.is_variadic);
assert_eq!(m.body.len(), 1);
}
#[test]
fn test_macro_def_function_like() {
let body = vec![ident("x"), tok(TokenKind::Plus, "+"), ident("y")];
let params = vec!["x".into(), "y".into()];
let m = MacroDef::function_like("MAX", params.clone(), body.clone(), false);
assert_eq!(m.name, "MAX");
assert_eq!(m.params.len(), 2);
assert!(m.is_function_like);
assert!(!m.is_variadic);
}
#[test]
fn test_macro_def_variadic() {
let body = vec![ident("__VA_ARGS__")];
let params = vec!["__VA_ARGS__".into()];
let m = MacroDef::function_like("LOG", params, body, true);
assert!(m.is_variadic);
}
#[test]
fn test_preprocessor_new() {
let pp = Preprocessor::new(CLangStandard::C11);
assert!(pp.defines.contains_key("__STDC__"));
assert!(pp.defines.contains_key("__STDC_VERSION__"));
assert!(pp.include_paths.is_empty());
assert!(pp.errors.is_empty());
}
#[test]
fn test_add_include_path() {
let mut pp = Preprocessor::new(CLangStandard::C99);
pp.add_include_path("/usr/include");
pp.add_include_path("/usr/local/include");
assert_eq!(pp.include_paths.len(), 2);
assert_eq!(pp.include_paths[0], "/usr/include");
}
#[test]
fn test_define_simple() {
let mut pp = Preprocessor::new(CLangStandard::C11);
pp.define("VERSION", "3");
assert!(pp.defines.contains_key("VERSION"));
let mac = pp.defines.get("VERSION").unwrap();
assert!(!mac.is_function_like);
assert_eq!(mac.body[0].text, "3");
}
#[test]
fn test_gnu_builtins() {
let pp = Preprocessor::new(CLangStandard::Gnu11);
assert!(pp.defines.contains_key("__GNUC__"));
assert!(pp.defines.contains_key("__GNUC_MINOR__"));
}
#[test]
fn test_c89_no_gnu_builtins() {
let pp = Preprocessor::new(CLangStandard::C89);
assert!(!pp.defines.contains_key("__GNUC__"));
}
#[test]
fn test_handle_define_object_like() {
let tokens = vec![
tok(TokenKind::Hash, "#"),
ident("define"),
ident("ANSWER"),
num("42"),
tok(TokenKind::Newline, "\n"),
ident("ANSWER"),
];
let mut pp = Preprocessor::new(CLangStandard::C11);
let result = pp.process(&tokens);
let has_42 = result.iter().any(|t| t.text == "42");
assert!(has_42);
}
#[test]
fn test_handle_define_then_use() {
let source = "#define FOO 99\nFOO";
let result = preprocess(source, CLangStandard::C11, &[]);
assert!(!result.is_empty());
let texts: Vec<String> = result.iter().map(|t| t.text.clone()).collect();
assert!(texts.contains(&"99".to_string()));
}
#[test]
fn test_handle_undef() {
let source = "#define FOO 1\n#undef FOO\nFOO";
let result = preprocess(source, CLangStandard::C11, &[]);
let texts: Vec<String> = result.iter().map(|t| t.text.clone()).collect();
assert!(texts.contains(&"FOO".to_string()));
}
#[test]
fn test_function_like_macro() {
let source = "#define SQUARE(x) ((x) * (x))\nSQUARE(5)";
let result = preprocess(source, CLangStandard::C11, &[]);
let text_concat: String = result.iter().map(|t| t.text.as_str()).collect();
assert!(text_concat.contains("5"));
assert!(text_concat.contains("*"));
}
#[test]
fn test_ifdef_true() {
let source = "#define FOO\n#ifdef FOO\n42\n#endif\n99";
let result = preprocess(source, CLangStandard::C11, &[]);
let texts: Vec<String> = result.iter().map(|t| t.text.clone()).collect();
assert!(texts.contains(&"42".to_string()));
assert!(texts.contains(&"99".to_string()));
}
#[test]
fn test_ifdef_false() {
let source = "#ifdef FOO\n42\n#endif\n99";
let result = preprocess(source, CLangStandard::C11, &[]);
let texts: Vec<String> = result.iter().map(|t| t.text.clone()).collect();
assert!(!texts.contains(&"42".to_string()));
assert!(texts.contains(&"99".to_string()));
}
#[test]
fn test_ifndef_true() {
let source = "#ifndef FOO\n42\n#endif\n99";
let result = preprocess(source, CLangStandard::C11, &[]);
let texts: Vec<String> = result.iter().map(|t| t.text.clone()).collect();
assert!(texts.contains(&"42".to_string()));
}
#[test]
fn test_ifndef_false() {
let source = "#define FOO\n#ifndef FOO\n42\n#endif\n99";
let result = preprocess(source, CLangStandard::C11, &[]);
let texts: Vec<String> = result.iter().map(|t| t.text.clone()).collect();
assert!(!texts.contains(&"42".to_string()));
}
#[test]
fn test_if_else() {
let source = "#define FOO 1\n#if FOO\nfirst\n#else\nsecond\n#endif";
let result = preprocess(source, CLangStandard::C11, &[]);
let texts: Vec<String> = result.iter().map(|t| t.text.clone()).collect();
assert!(texts.contains(&"first".to_string()));
assert!(!texts.contains(&"second".to_string()));
}
#[test]
fn test_if_else_false_branch() {
let source = "#define FOO 0\n#if FOO\nfirst\n#else\nsecond\n#endif";
let result = preprocess(source, CLangStandard::C11, &[]);
let texts: Vec<String> = result.iter().map(|t| t.text.clone()).collect();
assert!(!texts.contains(&"first".to_string()));
assert!(texts.contains(&"second".to_string()));
}
#[test]
fn test_nested_ifdef() {
let source =
"#define A\n#define B\n#ifdef A\n #ifdef B\n inner\n #endif\nouter\n#endif";
let result = preprocess(source, CLangStandard::C11, &[]);
let texts: Vec<String> = result.iter().map(|t| t.text.clone()).collect();
assert!(texts.contains(&"inner".to_string()));
assert!(texts.contains(&"outer".to_string()));
}
#[test]
fn test_eval_simple_integer() {
let pp = Preprocessor::new(CLangStandard::C11);
let tokens = vec![num("42")];
assert_eq!(pp.eval_expr(&tokens).unwrap(), 42);
}
#[test]
fn test_eval_addition() {
let pp = Preprocessor::new(CLangStandard::C11);
let tokens = vec![num("1"), tok(TokenKind::Plus, "+"), num("2")];
assert_eq!(pp.eval_expr(&tokens).unwrap(), 3);
}
#[test]
fn test_eval_unary_minus() {
let pp = Preprocessor::new(CLangStandard::C11);
let tokens = vec![tok(TokenKind::Minus, "-"), num("5")];
assert_eq!(pp.eval_expr(&tokens).unwrap(), -5);
}
#[test]
fn test_eval_parentheses() {
let pp = Preprocessor::new(CLangStandard::C11);
let tokens = vec![
tok(TokenKind::LParen, "("),
num("1"),
tok(TokenKind::Plus, "+"),
num("2"),
tok(TokenKind::RParen, ")"),
tok(TokenKind::Star, "*"),
num("3"),
];
assert_eq!(pp.eval_expr(&tokens).unwrap(), 9);
}
#[test]
fn test_eval_logical_and() {
let pp = Preprocessor::new(CLangStandard::C11);
let tokens = vec![num("1"), tok(TokenKind::AndAnd, "&&"), num("1")];
assert_eq!(pp.eval_expr(&tokens).unwrap(), 1);
}
#[test]
fn test_eval_defined() {
let mut pp = Preprocessor::new(CLangStandard::C11);
pp.define("FOO", "1");
let tokens = vec![
ident("defined"),
tok(TokenKind::LParen, "("),
ident("FOO"),
tok(TokenKind::RParen, ")"),
];
assert_eq!(pp.eval_expr(&tokens).unwrap(), 1);
}
#[test]
fn test_token_paste_simple() {
let pp = Preprocessor::new(CLangStandard::C11);
let a = ident("foo");
let b = ident("bar");
let result = pp.token_paste(&a, &b);
assert_eq!(result.text, "foobar");
}
#[test]
fn test_stringify() {
let pp = Preprocessor::new(CLangStandard::C11);
let tokens = vec![ident("hello")];
let result = pp.stringify(&tokens);
assert_eq!(result.kind, TokenKind::StringLiteral);
assert!(result.text.contains("hello"));
}
#[test]
fn test_error_directive() {
let source = "#error \"something went wrong\"";
let mut pp = Preprocessor::new(CLangStandard::C11);
let tokens = lexer::tokenize(source, CLangStandard::C11);
pp.process(&tokens);
assert!(!pp.errors.is_empty());
assert!(pp.errors[0].contains("something went wrong"));
}
#[test]
fn test_pragma_ignored() {
let source = "#pragma once\nint x;";
let result = preprocess(source, CLangStandard::C11, &[]);
let texts: Vec<String> = result.iter().map(|t| t.text.clone()).collect();
assert!(texts.contains(&"x".to_string()));
}
#[test]
fn test_builtin_file() {
let mut pp = Preprocessor::new(CLangStandard::C11);
pp.current_file = "test.c".to_string();
let result = pp.expand_builtin_macro("__FILE__");
assert!(result.is_some());
let tokens = result.unwrap();
assert_eq!(tokens[0].kind, TokenKind::StringLiteral);
assert!(tokens[0].text.contains("test.c"));
}
#[test]
fn test_builtin_counter() {
let mut pp = Preprocessor::new(CLangStandard::C11);
let r1 = pp.expand_builtin_macro("__COUNTER__").unwrap();
let r2 = pp.expand_builtin_macro("__COUNTER__").unwrap();
let r3 = pp.expand_builtin_macro("__COUNTER__").unwrap();
assert_eq!(r1[0].text, "0");
assert_eq!(r2[0].text, "1");
assert_eq!(r3[0].text, "2");
}
#[test]
fn test_builtin_date() {
let mut pp = Preprocessor::new(CLangStandard::C11);
let result = pp.expand_builtin_macro("__DATE__");
assert!(result.is_some());
assert_eq!(result.unwrap()[0].kind, TokenKind::StringLiteral);
}
#[test]
fn test_builtin_time() {
let mut pp = Preprocessor::new(CLangStandard::C11);
let result = pp.expand_builtin_macro("__TIME__");
assert!(result.is_some());
assert_eq!(result.unwrap()[0].kind, TokenKind::StringLiteral);
}
#[test]
fn test_builtin_unknown_name() {
let mut pp = Preprocessor::new(CLangStandard::C11);
let result = pp.expand_builtin_macro("__UNKNOWN__");
assert!(result.is_none());
}
#[test]
fn test_resolve_include_not_found() {
let pp = Preprocessor::new(CLangStandard::C11);
let result = pp.resolve_include("nonexistent_file_xyz.h", false);
assert!(result.is_none());
}
#[test]
fn test_read_file_nonexistent() {
let pp = Preprocessor::new(CLangStandard::C11);
let result = pp.read_file("/nonexistent/file_xyz.h");
assert!(result.is_none());
}
#[test]
fn test_preprocess_empty() {
let result = preprocess("", CLangStandard::C11, &[]);
assert!(result.is_empty());
}
#[test]
fn test_preprocess_plain_code() {
let result = preprocess("int x = 42;", CLangStandard::C11, &[]);
assert!(result.len() > 1);
let texts: Vec<String> = result.iter().map(|t| t.text.clone()).collect();
assert!(texts.contains(&"int".to_string()) || texts.iter().any(|t| t == "int"));
assert!(texts.contains(&"x".to_string()));
}
#[test]
fn test_empty_define() {
let source = "#define EMPTY\nEMPTY";
let result = preprocess(source, CLangStandard::C11, &[]);
let texts: Vec<String> = result.iter().map(|t| t.text.clone()).collect();
assert!(!texts.contains(&"EMPTY".to_string()));
}
#[test]
fn test_recursive_macro_guard() {
let source = "#define REC REC\nREC";
let result = preprocess(source, CLangStandard::C11, &[]);
assert!(result.len() >= 0);
}
#[test]
fn test_if_with_arithmetic() {
let source = "#if 3 * 4 + 2 == 14\ninside\n#endif";
let result = preprocess(source, CLangStandard::C11, &[]);
let texts: Vec<String> = result.iter().map(|t| t.text.clone()).collect();
assert!(texts.contains(&"inside".to_string()));
}
}