use crate::clang::token::{SourceFile, SourceLoc, Token, TokenFlags, TokenKind};
use crate::clang::CLangStandard;
use std::collections::{HashMap, HashSet, VecDeque};
use std::fmt;
use std::path::{Path, PathBuf};
use std::time::{SystemTime, UNIX_EPOCH};
pub struct X86PreprocessorDeep {
pub defines: HashMap<String, MacroDefinition>,
pub include_paths: Vec<IncludeSearchPath>,
pub standard: CLangStandard,
pub cpp_mode: bool,
pub gnu_mode: bool,
pub ms_mode: bool,
pub output: Vec<Token>,
pub errors: Vec<PreprocessError>,
pub counter: u64,
pub cond_stack: Vec<ConditionalState>,
pub current_file: Option<PathBuf>,
pub base_file: Option<PathBuf>,
pub skipping: bool,
pub known_builtins: HashSet<String>,
pub known_features: HashSet<String>,
pub known_attributes: HashSet<String>,
pub known_cpp_attributes: HashMap<String, u32>,
pub known_c_attributes: HashSet<String>,
pub known_declspec_attributes: HashSet<String>,
pub included_files: HashSet<PathBuf>,
pub pragma_once_files: HashSet<PathBuf>,
pub pragma_pack_stack: Vec<u32>,
pub current_pack: u32,
pub pragma_diag_stack: Vec<PragmaDiagnosticState>,
pub build_timestamp: Option<SystemTime>,
pub max_expansion_depth: u32,
expansion_depth: u32,
pub enable_counter: bool,
pub enable_time_macros: bool,
in_cond_eval: bool,
include_stack: Vec<PathBuf>,
guard_detector: IncludeGuardDetector,
}
#[derive(Debug, Clone)]
pub struct MacroDefinition {
pub name: String,
pub params: Vec<String>,
pub is_function_like: bool,
pub is_variadic: bool,
pub va_args_name: String,
pub replacement: Vec<PPToken>,
pub is_expanding: bool,
pub def_location: SourceLoc,
pub is_predefined: bool,
pub is_builtin: bool,
}
impl MacroDefinition {
pub fn object_like(name: impl Into<String>, replacement: Vec<PPToken>) -> Self {
Self {
name: name.into(),
params: vec![],
is_function_like: false,
is_variadic: false,
va_args_name: "__VA_ARGS__".into(),
replacement,
is_expanding: false,
def_location: SourceLoc::unknown(),
is_predefined: false,
is_builtin: false,
}
}
pub fn function_like(
name: impl Into<String>,
params: Vec<String>,
replacement: Vec<PPToken>,
is_variadic: bool,
) -> Self {
Self {
name: name.into(),
params,
is_function_like: true,
is_variadic,
va_args_name: "__VA_ARGS__".into(),
replacement,
is_expanding: false,
def_location: SourceLoc::unknown(),
is_predefined: false,
is_builtin: false,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct PPToken {
pub kind: TokenKind,
pub text: String,
pub leading_whitespace: bool,
pub is_placemarker: bool,
pub location: SourceLoc,
}
impl PPToken {
pub fn new(kind: TokenKind, text: String, location: SourceLoc) -> Self {
Self {
kind,
text,
leading_whitespace: false,
is_placemarker: false,
location,
}
}
pub fn from_token(token: &Token) -> Self {
Self {
kind: token.kind,
text: token.text.clone(),
leading_whitespace: token.flags.has_leading_space,
is_placemarker: false,
location: token.location,
}
}
pub fn placemarker() -> Self {
Self {
kind: TokenKind::Unknown,
text: String::new(),
leading_whitespace: false,
is_placemarker: true,
location: SourceLoc::unknown(),
}
}
pub fn is_placemarker(&self) -> bool {
self.is_placemarker
}
pub fn is_ident(&self) -> bool {
self.kind == TokenKind::Identifier
}
pub fn is_hashhash(&self) -> bool {
self.kind == TokenKind::HashHash
}
pub fn is_hash(&self) -> bool {
self.kind == TokenKind::Hash
}
pub fn is_comma(&self) -> bool {
self.kind == TokenKind::Comma
}
pub fn is_lparen(&self) -> bool {
self.kind == TokenKind::LParen
}
pub fn is_rparen(&self) -> bool {
self.kind == TokenKind::RParen
}
}
impl fmt::Display for PPToken {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if self.is_placemarker {
write!(f, "<placemarker>")
} else {
write!(f, "{}", self.text)
}
}
}
#[derive(Debug, Clone)]
pub struct IncludeSearchPath {
pub path: PathBuf,
pub is_system: bool,
pub is_framework: bool,
}
impl IncludeSearchPath {
pub fn user(path: impl Into<PathBuf>) -> Self {
Self {
path: path.into(),
is_system: false,
is_framework: false,
}
}
pub fn system(path: impl Into<PathBuf>) -> Self {
Self {
path: path.into(),
is_system: true,
is_framework: false,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ConditionalState {
Active,
SawTrue,
Skipping,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct PreprocessError {
pub message: String,
pub location: SourceLoc,
pub is_fatal: bool,
}
impl PreprocessError {
pub fn new(message: impl Into<String>, location: SourceLoc) -> Self {
Self {
message: message.into(),
location,
is_fatal: false,
}
}
pub fn fatal(message: impl Into<String>, location: SourceLoc) -> Self {
Self {
message: message.into(),
location,
is_fatal: true,
}
}
}
impl fmt::Display for PreprocessError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}: {}", self.location, self.message)
}
}
#[derive(Debug, Clone, Default)]
pub struct PragmaDiagnosticState {
pub ignored_warnings: HashSet<String>,
pub warning_as_error: HashSet<String>,
}
#[derive(Debug, Clone, Default)]
struct IncludeGuardDetector {
guard_macro: Option<String>,
pragma_once: bool,
seen_non_pp: bool,
}
impl IncludeGuardDetector {
fn analyse_line(&mut self, line: &str) {
if self.seen_non_pp {
return;
}
let trimmed = line.trim();
if trimmed.starts_with('#') {
if trimmed.contains("ifndef") {
let parts: Vec<&str> = trimmed.split_whitespace().collect();
if parts.len() >= 2 {
self.guard_macro = Some(parts[1].to_string());
}
} else if trimmed.contains("define") && self.guard_macro.is_some() {
let parts: Vec<&str> = trimmed.split_whitespace().collect();
if parts.len() >= 2 && Some(parts[1].to_string()) == self.guard_macro {
}
} else if trimmed == "#pragma once" {
self.pragma_once = true;
}
} else if !trimmed.is_empty() {
self.seen_non_pp = true;
}
}
fn has_guard(&self) -> bool {
self.guard_macro.is_some() && !self.seen_non_pp
}
}
impl X86PreprocessorDeep {
pub fn new(standard: CLangStandard) -> Self {
let mut pp = Self {
defines: HashMap::new(),
include_paths: Vec::new(),
standard,
cpp_mode: false,
gnu_mode: standard.is_gnu(),
ms_mode: false,
output: Vec::new(),
errors: Vec::new(),
counter: 0,
cond_stack: Vec::new(),
current_file: None,
base_file: None,
skipping: false,
known_builtins: HashSet::new(),
known_features: HashSet::new(),
known_attributes: HashSet::new(),
known_cpp_attributes: HashMap::new(),
known_c_attributes: HashSet::new(),
known_declspec_attributes: HashSet::new(),
included_files: HashSet::new(),
pragma_once_files: HashSet::new(),
pragma_pack_stack: Vec::new(),
current_pack: 0,
pragma_diag_stack: Vec::new(),
build_timestamp: None,
max_expansion_depth: 256,
expansion_depth: 0,
enable_counter: true,
enable_time_macros: true,
in_cond_eval: false,
include_stack: Vec::new(),
guard_detector: IncludeGuardDetector::default(),
};
pp.init_predefined_macros();
pp.init_known_names();
pp
}
pub fn new_cpp(standard: CLangStandard) -> Self {
let mut pp = Self::new(standard);
pp.cpp_mode = true;
pp
}
pub fn new_gnu(standard: CLangStandard) -> Self {
let mut pp = Self::new(standard);
pp.gnu_mode = true;
pp
}
pub fn add_include_path(&mut self, path: impl Into<PathBuf>, is_system: bool) {
self.include_paths.push(IncludeSearchPath {
path: path.into(),
is_system,
is_framework: false,
});
}
pub fn define(&mut self, name: &str, value: &str) {
let token = PPToken::new(
TokenKind::Identifier,
value.to_string(),
SourceLoc::unknown(),
);
let def = MacroDefinition::object_like(name, vec![token]);
self.defines.insert(name.to_string(), def);
}
pub fn undefine(&mut self, name: &str) {
self.defines.remove(name);
}
pub fn is_defined(&self, name: &str) -> bool {
self.defines.contains_key(name)
}
fn error(&mut self, msg: impl Into<String>, loc: SourceLoc) {
self.errors.push(PreprocessError::new(msg, loc));
}
fn warning(&mut self, msg: impl Into<String>, loc: SourceLoc) {
self.errors.push(PreprocessError::new(
format!("warning: {}", msg.into()),
loc,
));
}
pub fn clear_errors(&mut self) {
self.errors.clear();
}
fn init_predefined_macros(&mut self) {
self.defines.insert(
"__STDC__".into(),
MacroDefinition {
name: "__STDC__".into(),
params: vec![],
is_function_like: false,
is_variadic: false,
va_args_name: "__VA_ARGS__".into(),
replacement: vec![PPToken::new(
TokenKind::NumericLiteral,
"1".into(),
SourceLoc::unknown(),
)],
is_expanding: false,
def_location: SourceLoc::unknown(),
is_predefined: true,
is_builtin: false,
},
);
for name in &[
"__LINE__",
"__FILE__",
"__DATE__",
"__TIME__",
"__TIMESTAMP__",
"__COUNTER__",
"__BASE_FILE__",
"__FILE_NAME__",
"__FUNCTION__",
"__PRETTY_FUNCTION__",
"__STDC_VERSION__",
"__cplusplus",
"__STDC_HOSTED__",
"__GNUC__",
"__GNUC_MINOR__",
"__GNUC_PATCHLEVEL__",
"__clang__",
"__clang_major__",
"__clang_minor__",
"__clang_patchlevel__",
"__VERSION__",
"__STDCPP_DEFAULT_NEW_ALIGNMENT__",
] {
self.defines.insert(
name.to_string(),
MacroDefinition {
name: name.to_string(),
params: vec![],
is_function_like: false,
is_variadic: false,
va_args_name: "__VA_ARGS__".into(),
replacement: vec![],
is_expanding: false,
def_location: SourceLoc::unknown(),
is_predefined: true,
is_builtin: true,
},
);
}
self.define("__x86_64__", "1");
self.define("__x86_64", "1");
self.define("__amd64__", "1");
self.define("__amd64", "1");
self.define("__LP64__", "1");
self.define("_LP64", "1");
self.define("__linux__", "1");
self.define("__linux", "1");
self.define("linux", "1");
self.define("__ELF__", "1");
self.define("__unix__", "1");
self.define("__unix", "1");
self.define("unix", "1");
if self.gnu_mode {
self.define("__GNUC__", "4");
self.define("__GNUC_MINOR__", "2");
self.define("__GNUC_PATCHLEVEL__", "1");
self.define("__GNUG__", "4");
self.define("__STRICT_ANSI__", "1");
}
self.define("__clang__", "1");
self.define("__clang_major__", "19");
self.define("__clang_minor__", "1");
self.define("__clang_patchlevel__", "0");
self.define("__clang_version__", "\"19.1.0\"");
match self.standard {
CLangStandard::C89 => self.define("__STDC_VERSION__", "199409L"),
CLangStandard::C99 => self.define("__STDC_VERSION__", "199901L"),
CLangStandard::C11 | CLangStandard::Gnu11 => self.define("__STDC_VERSION__", "201112L"),
CLangStandard::C17 | CLangStandard::Gnu17 => self.define("__STDC_VERSION__", "201710L"),
CLangStandard::C23 => self.define("__STDC_VERSION__", "202311L"),
_ => {}
}
if self.cpp_mode {
self.define("__cplusplus", "201703L"); }
}
fn init_known_names(&mut self) {
for b in &[
"__builtin_abs",
"__builtin_alloca",
"__builtin_clz",
"__builtin_ctz",
"__builtin_expect",
"__builtin_memcpy",
"__builtin_popcount",
"__builtin_trap",
"__builtin_unreachable",
"__builtin_va_arg",
"__builtin_va_start",
"__builtin_va_end",
] {
self.known_builtins.insert(b.to_string());
}
for f in &[
"c_alignas",
"c_alignof",
"c_atomic",
"c_generic_selections",
"c_static_assert",
"c_thread_local",
] {
self.known_features.insert(f.to_string());
}
for a in &[
"aligned",
"always_inline",
"const",
"deprecated",
"noreturn",
"packed",
"unused",
"used",
"visibility",
"weak",
] {
self.known_attributes.insert(a.to_string());
}
for (a, v) in &[
("deprecated", 201309u32),
("fallthrough", 201603),
("nodiscard", 201603),
("maybe_unused", 201603),
("no_unique_address", 201803),
("likely", 201803),
("unlikely", 201803),
("assume", 202207),
] {
self.known_cpp_attributes.insert(a.to_string(), *v);
}
for a in &[
"deprecated",
"fallthrough",
"nodiscard",
"maybe_unused",
"noreturn",
] {
self.known_c_attributes.insert(a.to_string());
}
for a in &[
"dllexport",
"dllimport",
"naked",
"noinline",
"noreturn",
"nothrow",
] {
self.known_declspec_attributes.insert(a.to_string());
}
}
}
impl X86PreprocessorDeep {
pub fn process(&mut self, tokens: &[Token]) -> Vec<Token> {
self.output.clear();
self.errors.clear();
self.skipping = false;
self.cond_stack.clear();
let pp_tokens: Vec<PPToken> = tokens.iter().map(|t| PPToken::from_token(t)).collect();
let mut pos = 0;
while pos < pp_tokens.len() {
let pp = &pp_tokens[pos];
if pp.is_hash() && (pos == 0 || pp_tokens[pos.saturating_sub(1)].text.ends_with('\n')) {
pos = self.handle_directive(&pp_tokens, pos);
continue;
}
if self.should_skip() {
pos += 1;
continue;
}
if pp.is_ident() {
let name = &pp.text;
if let Some(mdef) = self.defines.get(name).cloned() {
if mdef.is_function_like {
if pos + 1 < pp_tokens.len() && pp_tokens[pos + 1].is_lparen() {
pos = self.expand_function_like_macro(&pp_tokens, pos, &mdef);
continue;
}
} else if !mdef.is_builtin && !mdef.is_expanding {
pos = self.expand_object_like_macro(&pp_tokens, pos, &mdef);
continue;
} else if mdef.is_builtin {
let expanded = self.expand_builtin_macro(name, pp.location);
self.output.push(expanded);
pos += 1;
continue;
}
}
}
self.output.push(Token::new(pp.kind, &pp.text, pp.location));
pos += 1;
}
self.output.clone()
}
fn expand_object_like_macro(
&mut self,
tokens: &[PPToken],
pos: usize,
mdef: &MacroDefinition,
) -> usize {
if let Some(m) = self.defines.get_mut(&mdef.name) {
m.is_expanding = true;
}
self.expansion_depth += 1;
let mut result = Vec::new();
for rt in &mdef.replacement {
if rt.is_ident() {
if let Some(nested) = self.defines.get(&rt.text).cloned() {
if !nested.is_expanding && !nested.is_function_like {
let expanded = self.expand_object_like_macro_static(&nested);
result.extend(expanded);
continue;
}
}
}
result.push(rt.clone());
}
for rt in &result {
self.output.push(Token::new(rt.kind, &rt.text, rt.location));
}
if let Some(m) = self.defines.get_mut(&mdef.name) {
m.is_expanding = false;
}
self.expansion_depth -= 1;
pos + 1
}
fn expand_object_like_macro_static(&self, mdef: &MacroDefinition) -> Vec<PPToken> {
let mut result = Vec::new();
for rt in &mdef.replacement {
result.push(rt.clone());
}
result
}
fn expand_function_like_macro(
&mut self,
tokens: &[PPToken],
pos: usize,
mdef: &MacroDefinition,
) -> usize {
let name = &tokens[pos].text;
let lparen_pos = pos + 1;
let mut args: Vec<Vec<PPToken>> = Vec::new();
let mut current_arg = Vec::new();
let mut depth = 1; let mut i = lparen_pos + 1;
while i < tokens.len() && depth > 0 {
let pp = &tokens[i];
if pp.is_lparen() {
depth += 1;
if depth > 1 {
current_arg.push(pp.clone());
}
} else if pp.is_rparen() {
depth -= 1;
if depth > 0 {
current_arg.push(pp.clone());
}
} else if pp.is_comma() && depth == 1 {
args.push(std::mem::take(&mut current_arg));
} else {
current_arg.push(pp.clone());
}
i += 1;
}
if depth == 0 && !current_arg.is_empty() {
args.push(current_arg);
} else if depth == 0 && current_arg.is_empty() && !mdef.params.is_empty() {
args.push(Vec::new()); }
if !mdef.is_variadic {
if args.len() != mdef.params.len() {
self.error(
format!(
"macro \"{}\" passed {} arguments, but takes {}",
name,
args.len(),
mdef.params.len()
),
tokens[pos].location,
);
return i;
}
} else {
if args.len() < mdef.params.len() - 1 {
self.error(
format!(
"macro \"{}\" requires at least {} arguments",
name,
mdef.params.len().saturating_sub(1)
),
tokens[pos].location,
);
return i;
}
}
if let Some(m) = self.defines.get_mut(name) {
m.is_expanding = true;
}
self.expansion_depth += 1;
let mut arg_map: HashMap<String, Vec<PPToken>> = HashMap::new();
for (idx, param) in mdef.params.iter().enumerate() {
if idx < args.len() {
arg_map.insert(param.clone(), args[idx].clone());
} else {
arg_map.insert(param.clone(), Vec::new());
}
}
if mdef.is_variadic {
let va_args_name = mdef.va_args_name.clone();
let variadic_start = mdef.params.len() - 1;
if variadic_start < args.len() {
let mut variadic = Vec::new();
for idx in variadic_start..args.len() {
if !variadic.is_empty() {
variadic.push(PPToken::new(
TokenKind::Comma,
",".into(),
SourceLoc::unknown(),
));
}
variadic.extend(args[idx].clone());
}
arg_map.insert(va_args_name.clone(), variadic);
if let Some(va_param) = mdef.params.last() {
arg_map.insert(va_param.clone(), args[variadic_start..].concat());
}
} else {
arg_map.insert(va_args_name, Vec::new());
}
}
let mut result = Vec::new();
self.substitute_args(&mdef.replacement, &arg_map, &mut result);
for rt in &result {
self.output.push(Token::new(rt.kind, &rt.text, rt.location));
}
if let Some(m) = self.defines.get_mut(name) {
m.is_expanding = false;
}
self.expansion_depth -= 1;
i
}
fn substitute_args(
&self,
replacement: &[PPToken],
arg_map: &HashMap<String, Vec<PPToken>>,
result: &mut Vec<PPToken>,
) {
let mut i = 0;
while i < replacement.len() {
let rt = &replacement[i];
if rt.is_hash() && i + 1 < replacement.len() && replacement[i + 1].is_ident() {
let param_name = &replacement[i + 1].text;
if let Some(arg_tokens) = arg_map.get(param_name) {
let stringified = stringify_tokens(arg_tokens);
result.push(PPToken::new(
TokenKind::StringLiteral,
format!("\"{}\"", stringified),
rt.location,
));
}
i += 2;
continue;
}
if rt.is_hashhash() {
i += 1;
continue;
}
if rt.is_ident() {
let name = &rt.text;
if let Some(arg_tokens) = arg_map.get(name) {
result.extend(arg_tokens.clone());
i += 1;
continue;
}
if let Some(nested) = self.defines.get(name) {
if !nested.is_expanding && !nested.is_function_like {
for nr in &nested.replacement {
result.push(nr.clone());
}
i += 1;
continue;
}
}
}
result.push(rt.clone());
i += 1;
}
}
fn expand_builtin_macro(&mut self, name: &str, loc: SourceLoc) -> Token {
match name {
"__LINE__" => {
let line_str = loc.line.to_string();
Token::new(TokenKind::NumericLiteral, &line_str, loc)
}
"__FILE__" => {
let path = self
.current_file
.as_ref()
.map(|p| p.display().to_string())
.unwrap_or_else(|| "<unknown>".into());
let text = format!("\"{}\"", path);
Token::new(TokenKind::StringLiteral, &text, loc)
}
"__DATE__" if self.enable_time_macros => {
let date = self.format_date();
let text = format!("\"{}\"", date);
Token::new(TokenKind::StringLiteral, &text, loc)
}
"__TIME__" if self.enable_time_macros => {
let time = self.format_time();
let text = format!("\"{}\"", time);
Token::new(TokenKind::StringLiteral, &text, loc)
}
"__TIMESTAMP__" if self.enable_time_macros => {
let ts = self.format_timestamp();
let text = format!("\"{}\"", ts);
Token::new(TokenKind::StringLiteral, &text, loc)
}
"__COUNTER__" if self.enable_counter => {
let val = self.counter;
self.counter += 1;
let val_str = val.to_string();
Token::new(TokenKind::NumericLiteral, &val_str, loc)
}
"__BASE_FILE__" => {
let path = self
.base_file
.as_ref()
.map(|p| p.display().to_string())
.unwrap_or_else(|| "".into());
let text = format!("\"{}\"", path);
Token::new(TokenKind::StringLiteral, &text, loc)
}
"__FILE_NAME__" => {
let name = self
.current_file
.as_ref()
.and_then(|p| p.file_name())
.map(|n| n.to_string_lossy().to_string())
.unwrap_or_default();
let text = format!("\"{}\"", name);
Token::new(TokenKind::StringLiteral, &text, loc)
}
"__FUNCTION__" => Token::new(TokenKind::StringLiteral, "\"<toplevel>\"".into(), loc),
"__PRETTY_FUNCTION__" => {
Token::new(TokenKind::StringLiteral, "\"<toplevel>\"".into(), loc)
}
"__STDC_VERSION__" => {
let ver = match self.standard {
CLangStandard::C89 => "199409L",
CLangStandard::C99 => "199901L",
CLangStandard::C11 | CLangStandard::Gnu11 => "201112L",
CLangStandard::C17 | CLangStandard::Gnu17 => "201710L",
CLangStandard::C23 => "202311L",
_ => "201710L",
};
Token::new(TokenKind::NumericLiteral, ver.into(), loc)
}
"__cplusplus" if self.cpp_mode => {
Token::new(TokenKind::NumericLiteral, "201703L".into(), loc)
}
"__GNUC__" if self.gnu_mode => Token::new(TokenKind::NumericLiteral, "4".into(), loc),
"__clang__" => Token::new(TokenKind::NumericLiteral, "1".into(), loc),
"__VERSION__" => Token::new(
TokenKind::StringLiteral,
"\"Clang-compatible 19.1.0\"".into(),
loc,
),
_ => {
Token::new(TokenKind::Identifier, name.into(), loc)
}
}
}
fn format_date(&self) -> String {
"Jul 3 2026".to_string()
}
fn format_time(&self) -> String {
"12:00:00".to_string()
}
fn format_timestamp(&self) -> String {
"Thu Jul 3 12:00:00 2026".to_string()
}
}
impl X86PreprocessorDeep {
pub fn parse_va_opt(&self, tokens: &[PPToken], va_args: &[PPToken]) -> Vec<PPToken> {
let mut result = Vec::new();
let mut i = 0;
while i < tokens.len() {
if tokens[i].is_ident() && tokens[i].text == "__VA_OPT__" {
if i + 1 < tokens.len() && tokens[i + 1].is_lparen() {
let mut depth = 1;
let mut j = i + 2;
let mut content = Vec::new();
while j < tokens.len() && depth > 0 {
if tokens[j].is_lparen() {
depth += 1;
} else if tokens[j].is_rparen() {
depth -= 1;
}
if depth > 0 {
content.push(tokens[j].clone());
} else {
break;
}
j += 1;
}
if !va_args.is_empty() {
result.extend(content);
}
i = j + 1; } else {
i += 1;
}
} else {
result.push(tokens[i].clone());
i += 1;
}
}
result
}
}
impl X86PreprocessorDeep {
pub fn paste_tokens(&self, tokens: &[PPToken]) -> Vec<PPToken> {
let mut result: Vec<PPToken> = Vec::new();
let mut i = 0;
while i < tokens.len() {
if tokens[i].is_hashhash() {
if let Some(prev) = result.pop() {
let mut j = i + 1;
while j < tokens.len() && tokens[j].is_placemarker() {
j += 1;
}
if j < tokens.len() {
let next = &tokens[j];
if !prev.is_placemarker() && !next.is_placemarker() {
let new_text = format!("{}{}", prev.text, next.text);
let new_kind = classify_pasted_token(&new_text);
result.push(PPToken::new(new_kind, new_text, prev.location));
} else if prev.is_placemarker() && !next.is_placemarker() {
result.push(next.clone());
} else if !prev.is_placemarker() && next.is_placemarker() {
result.push(prev);
}
i = j + 1;
continue;
}
}
i += 1;
} else {
result.push(tokens[i].clone());
i += 1;
}
}
result
}
pub fn remove_placemarkers(&self, tokens: &[PPToken]) -> Vec<PPToken> {
tokens
.iter()
.filter(|t| !t.is_placemarker())
.cloned()
.collect()
}
}
fn is_pp_number(text: &str) -> bool {
if text.is_empty() {
return false;
}
let mut chars = text.chars();
let first = chars.next().unwrap();
if first.is_ascii_digit() {
chars.all(|c| c.is_ascii_alphanumeric() || c == '.' || c == '+' || c == '-' || c == '\'')
} else if first == '.' {
if let Some(c) = chars.next() {
if c.is_ascii_digit() {
chars.all(|c| {
c.is_ascii_alphanumeric() || c == '.' || c == '+' || c == '-' || c == '\''
})
} else {
false
}
} else {
false
}
} else {
false
}
}
fn classify_pasted_token(text: &str) -> TokenKind {
if text.is_empty() {
return TokenKind::Unknown;
}
if is_pp_number(text) {
return TokenKind::NumericLiteral;
}
if text
.chars()
.next()
.map_or(false, |c| c.is_ascii_alphabetic() || c == '_')
{
return TokenKind::Identifier;
}
TokenKind::Identifier
}
pub fn stringify_tokens(tokens: &[PPToken]) -> String {
let mut result = String::new();
for (i, tok) in tokens.iter().enumerate() {
if i > 0 && tok.leading_whitespace {
result.push(' ');
}
for ch in tok.text.chars() {
match ch {
'\\' => result.push_str("\\\\"),
'"' => result.push_str("\\\""),
'\n' => result.push_str("\\n"),
'\t' => result.push_str("\\t"),
_ => result.push(ch),
}
}
}
result
}
pub fn stringify_single(token: &PPToken) -> String {
let mut result = String::new();
for ch in token.text.chars() {
match ch {
'\\' => result.push_str("\\\\"),
'"' => result.push_str("\\\""),
'\n' => result.push_str("\\n"),
_ => result.push(ch),
}
}
result
}
impl X86PreprocessorDeep {
fn should_skip(&self) -> bool {
self.skipping
}
fn handle_directive(&mut self, tokens: &[PPToken], hash_pos: usize) -> usize {
let mut i = hash_pos + 1;
while i < tokens.len() && tokens[i].kind == TokenKind::Unknown {
i += 1; }
if i >= tokens.len() {
return hash_pos + 1;
}
let directive_text = tokens[i].text.clone();
let dir_loc = tokens[i].location;
match directive_text.as_str() {
"define" => {
i = self.handle_define(tokens, i);
}
"undef" => {
i = self.handle_undef(tokens, i);
}
"include" => {
i = self.handle_include(tokens, i);
}
"include_next" => {
i = self.handle_include_next(tokens, i);
}
"if" => {
i = self.handle_if(tokens, i);
}
"ifdef" => {
i = self.handle_ifdef(tokens, i);
}
"ifndef" => {
i = self.handle_ifndef(tokens, i);
}
"elif" => {
i = self.handle_elif(tokens, i);
}
"elifdef" => {
i = self.handle_elifdef(tokens, i);
}
"elifndef" => {
i = self.handle_elifndef(tokens, i);
}
"else" => {
i = self.handle_else(tokens, i);
}
"endif" => {
i = self.handle_endif(tokens, i);
}
"error" => {
i = self.handle_error(tokens, i);
}
"warning" => {
i = self.handle_warning(tokens, i);
}
"pragma" => {
i = self.handle_pragma(tokens, i);
}
"line" => {
i = self.handle_line(tokens, i);
}
"import" => {
i = self.handle_import(tokens, i);
}
"module" => {
i = self.handle_module_directive(tokens, i);
}
"embed" => {
i = self.handle_embed(tokens, i);
}
_ => {
if !self.should_skip() {
self.warning(
format!("unknown preprocessor directive: #{}", directive_text),
dir_loc,
);
}
i = self.skip_to_newline(tokens, i);
}
}
i
}
fn skip_to_newline(&self, tokens: &[PPToken], mut pos: usize) -> usize {
while pos < tokens.len() && tokens[pos].text != "\n" && tokens[pos].text != "\r" {
pos += 1;
}
pos
}
fn handle_define(&mut self, tokens: &[PPToken], directive_pos: usize) -> usize {
let mut i = directive_pos + 1;
while i < tokens.len()
&& tokens[i].kind == TokenKind::Unknown
&& tokens[i].text.chars().all(|c| c.is_whitespace())
{
i += 1;
}
if i >= tokens.len() {
return i;
}
let macro_name = tokens[i].text.clone();
let loc = tokens[i].location;
i += 1;
let is_function_like = i < tokens.len() && tokens[i].is_lparen();
let mut params = Vec::new();
let mut is_variadic = false;
if is_function_like {
i += 1; while i < tokens.len() && !tokens[i].is_rparen() {
if tokens[i].is_comma() {
i += 1;
continue;
}
if tokens[i].kind == TokenKind::Unknown {
i += 1;
continue;
}
let param_name = tokens[i].text.clone();
if param_name == "..." {
is_variadic = true;
if !params.is_empty() {
}
} else {
params.push(param_name);
}
i += 1;
}
if i < tokens.len() {
i += 1;
} }
let mut replacement = Vec::new();
while i < tokens.len() && tokens[i].text != "\n" && tokens[i].text != "\r" {
replacement.push(tokens[i].clone());
i += 1;
}
let mdef = MacroDefinition {
name: macro_name,
params,
is_function_like,
is_variadic,
va_args_name: "__VA_ARGS__".into(),
replacement,
is_expanding: false,
def_location: loc,
is_predefined: false,
is_builtin: false,
};
if !self.should_skip() {
self.define_macro(mdef);
}
while i < tokens.len() && tokens[i].text != "\n" && tokens[i].text != "\r" {
i += 1;
}
i
}
fn define_macro(&mut self, mdef: MacroDefinition) {
if let Some(existing) = self.defines.get(&mdef.name) {
if !existing.is_predefined && !existing.is_builtin {
if existing.replacement != mdef.replacement {
self.warning(format!("\"{}\" redefined", mdef.name), mdef.def_location);
}
}
}
self.defines.insert(mdef.name.clone(), mdef);
}
fn handle_undef(&mut self, tokens: &[PPToken], directive_pos: usize) -> usize {
let mut i = directive_pos + 1;
while i < tokens.len() && tokens[i].kind == TokenKind::Unknown {
i += 1;
}
if i < tokens.len() {
let name = tokens[i].text.clone();
if !self.should_skip() {
self.defines.remove(&name);
}
i += 1;
}
self.skip_to_newline(tokens, i)
}
fn handle_include(&mut self, tokens: &[PPToken], directive_pos: usize) -> usize {
let mut i = directive_pos + 1;
while i < tokens.len() && tokens[i].kind == TokenKind::Unknown {
i += 1;
}
if i < tokens.len() {
let include_path = tokens[i].text.clone();
let is_system = include_path.starts_with('<');
if !self.should_skip() {
self.resolve_and_process_include(&include_path, is_system);
}
i += 1;
}
self.skip_to_newline(tokens, i)
}
fn handle_include_next(&mut self, tokens: &[PPToken], directive_pos: usize) -> usize {
let mut i = directive_pos + 1;
while i < tokens.len() && tokens[i].kind == TokenKind::Unknown {
i += 1;
}
if i < tokens.len() {
let include_path = tokens[i].text.clone();
let is_system = include_path.starts_with('<');
if !self.should_skip() {
self.resolve_and_process_include_next(&include_path, is_system);
}
i += 1;
}
self.skip_to_newline(tokens, i)
}
fn resolve_and_process_include(&mut self, path: &str, is_system: bool) {
let cleaned = path.trim_matches(|c| c == '<' || c == '>' || c == '"');
let file_path = self.resolve_include_path(cleaned, is_system);
if let Some(fp) = file_path {
self.included_files.insert(fp.clone());
} else {
self.error(
format!("'{}' file not found", cleaned),
SourceLoc::unknown(),
);
}
}
fn resolve_and_process_include_next(&mut self, path: &str, is_system: bool) {
let cleaned = path.trim_matches(|c| c == '<' || c == '>' || c == '"');
let file_path = self.resolve_include_next_path(cleaned, is_system);
if let Some(fp) = file_path {
self.included_files.insert(fp);
} else {
self.error(
format!("'{}' file not found (include_next)", cleaned),
SourceLoc::unknown(),
);
}
}
fn resolve_include_path(&self, path: &str, is_system: bool) -> Option<PathBuf> {
if !is_system {
if let Some(ref current) = self.current_file {
if let Some(parent) = current.parent() {
let candidate = parent.join(path);
if candidate.exists() {
return Some(candidate);
}
}
}
}
for inc_path in &self.include_paths {
let candidate = inc_path.path.join(path);
if candidate.exists() {
return Some(candidate);
}
}
for sys_path in &[
"/usr/include",
"/usr/local/include",
"/usr/include/x86_64-linux-gnu",
] {
let candidate = PathBuf::from(sys_path).join(path);
if candidate.exists() {
return Some(candidate);
}
}
None
}
fn resolve_include_next_path(&self, path: &str, is_system: bool) -> Option<PathBuf> {
for inc_path in &self.include_paths {
if let Some(ref current) = self.current_file {
if let Some(parent) = current.parent() {
if inc_path.path == parent {
continue;
}
}
}
let candidate = inc_path.path.join(path);
if candidate.exists() {
return Some(candidate);
}
}
None
}
fn handle_if(&mut self, tokens: &[PPToken], directive_pos: usize) -> usize {
let mut i = directive_pos + 1;
let mut expr_tokens = Vec::new();
while i < tokens.len() && tokens[i].text != "\n" && tokens[i].text != "\r" {
expr_tokens.push(tokens[i].clone());
i += 1;
}
self.cond_stack.push(ConditionalState::Active);
if self.should_skip() {
self.skipping = true;
} else {
let value = self.evaluate_condition(&expr_tokens);
if value == 0 {
self.skipping = true;
if let Some(state) = self.cond_stack.last_mut() {
*state = ConditionalState::SawTrue;
}
}
}
self.skip_to_newline(tokens, directive_pos)
}
fn handle_ifdef(&mut self, tokens: &[PPToken], directive_pos: usize) -> usize {
let mut i = directive_pos + 1;
while i < tokens.len() && tokens[i].kind == TokenKind::Unknown {
i += 1;
}
self.cond_stack.push(ConditionalState::Active);
if i < tokens.len() {
let name = tokens[i].text.clone();
if !self.should_skip() {
if !self.is_defined(&name) {
self.skipping = true;
if let Some(state) = self.cond_stack.last_mut() {
*state = ConditionalState::SawTrue;
}
}
} else {
self.skipping = true;
}
i += 1;
}
self.skip_to_newline(tokens, directive_pos)
}
fn handle_ifndef(&mut self, tokens: &[PPToken], directive_pos: usize) -> usize {
let mut i = directive_pos + 1;
while i < tokens.len() && tokens[i].kind == TokenKind::Unknown {
i += 1;
}
self.cond_stack.push(ConditionalState::Active);
if i < tokens.len() {
let name = tokens[i].text.clone();
if !self.should_skip() {
if self.is_defined(&name) {
self.skipping = true;
if let Some(state) = self.cond_stack.last_mut() {
*state = ConditionalState::SawTrue;
}
}
} else {
self.skipping = true;
}
i += 1;
}
self.skip_to_newline(tokens, directive_pos)
}
fn handle_elif(&mut self, tokens: &[PPToken], directive_pos: usize) -> usize {
let mut i = directive_pos + 1;
let mut expr_tokens = Vec::new();
while i < tokens.len() && tokens[i].text != "\n" && tokens[i].text != "\r" {
expr_tokens.push(tokens[i].clone());
i += 1;
}
match self.cond_stack.last() {
Some(ConditionalState::SawTrue) => {
self.skipping = true;
}
Some(ConditionalState::Active) => {
let value = self.evaluate_condition(&expr_tokens);
if value != 0 {
self.skipping = false;
if let Some(state) = self.cond_stack.last_mut() {
*state = ConditionalState::SawTrue;
}
} else {
self.skipping = true;
}
}
_ => {
self.skipping = true;
}
}
self.skip_to_newline(tokens, directive_pos)
}
fn handle_elifdef(&mut self, tokens: &[PPToken], directive_pos: usize) -> usize {
let mut i = directive_pos + 1;
while i < tokens.len() && tokens[i].kind == TokenKind::Unknown {
i += 1;
}
match self.cond_stack.last() {
Some(ConditionalState::SawTrue) => {
self.skipping = true;
}
Some(ConditionalState::Active) => {
if i < tokens.len() {
let name = tokens[i].text.clone();
if self.is_defined(&name) {
self.skipping = false;
if let Some(state) = self.cond_stack.last_mut() {
*state = ConditionalState::SawTrue;
}
} else {
self.skipping = true;
}
}
}
_ => {
self.skipping = true;
}
}
self.skip_to_newline(tokens, directive_pos)
}
fn handle_elifndef(&mut self, tokens: &[PPToken], directive_pos: usize) -> usize {
let mut i = directive_pos + 1;
while i < tokens.len() && tokens[i].kind == TokenKind::Unknown {
i += 1;
}
match self.cond_stack.last() {
Some(ConditionalState::SawTrue) => {
self.skipping = true;
}
Some(ConditionalState::Active) => {
if i < tokens.len() {
let name = tokens[i].text.clone();
if !self.is_defined(&name) {
self.skipping = false;
if let Some(state) = self.cond_stack.last_mut() {
*state = ConditionalState::SawTrue;
}
} else {
self.skipping = true;
}
}
}
_ => {
self.skipping = true;
}
}
self.skip_to_newline(tokens, directive_pos)
}
fn handle_else(&mut self, tokens: &[PPToken], directive_pos: usize) -> usize {
match self.cond_stack.last() {
Some(ConditionalState::Active) => {
self.skipping = true;
if let Some(state) = self.cond_stack.last_mut() {
*state = ConditionalState::SawTrue;
}
}
Some(ConditionalState::SawTrue) => {
self.skipping = true;
}
_ => {}
}
self.skip_to_newline(tokens, directive_pos)
}
fn handle_endif(&mut self, _tokens: &[PPToken], directive_pos: usize) -> usize {
self.cond_stack.pop();
self.skipping = self
.cond_stack
.last()
.map_or(false, |s| *s == ConditionalState::SawTrue);
self.skip_to_newline(_tokens, directive_pos)
}
fn handle_error(&mut self, tokens: &[PPToken], directive_pos: usize) -> usize {
if self.should_skip() {
return self.skip_to_newline(tokens, directive_pos);
}
let mut i = directive_pos + 1;
let mut msg = String::new();
while i < tokens.len() && tokens[i].text != "\n" && tokens[i].text != "\r" {
if !msg.is_empty() {
msg.push(' ');
}
msg.push_str(&tokens[i].text);
i += 1;
}
self.error(
format!("#error: {}", msg.trim()),
tokens[directive_pos].location,
);
i
}
fn handle_warning(&mut self, tokens: &[PPToken], directive_pos: usize) -> usize {
if self.should_skip() {
return self.skip_to_newline(tokens, directive_pos);
}
let mut i = directive_pos + 1;
let mut msg = String::new();
while i < tokens.len() && tokens[i].text != "\n" && tokens[i].text != "\r" {
if !msg.is_empty() {
msg.push(' ');
}
msg.push_str(&tokens[i].text);
i += 1;
}
self.warning(
format!("#warning: {}", msg.trim()),
tokens[directive_pos].location,
);
i
}
fn handle_pragma(&mut self, tokens: &[PPToken], directive_pos: usize) -> usize {
if self.should_skip() {
return self.skip_to_newline(tokens, directive_pos);
}
let mut i = directive_pos + 1;
let mut pragma_text = String::new();
while i < tokens.len() && tokens[i].text != "\n" && tokens[i].text != "\r" {
if !pragma_text.is_empty() {
pragma_text.push(' ');
}
pragma_text.push_str(&tokens[i].text);
i += 1;
}
self.process_pragma(&pragma_text);
i
}
fn process_pragma(&mut self, text: &str) {
let trimmed = text.trim();
if trimmed == "once" {
self.pragma_once_files.insert(
self.current_file
.clone()
.unwrap_or_else(|| PathBuf::from("<unknown>")),
);
return;
}
if trimmed.starts_with("pack") {
self.process_pragma_pack(trimmed);
return;
}
if trimmed.starts_with("message") {
let msg = trimmed.strip_prefix("message").unwrap_or("").trim();
return;
}
if trimmed.starts_with("GCC diagnostic") {
self.process_pragma_gcc_diagnostic(trimmed);
return;
}
if trimmed.starts_with("GCC optimize") {
return;
}
if trimmed.starts_with("GCC target") {
return;
}
if trimmed.starts_with("STDC") {
self.process_pragma_stdc(trimmed);
return;
}
if trimmed.starts_with("omp") {
return;
}
if trimmed.starts_with("clang") {
return;
}
}
fn process_pragma_pack(&mut self, text: &str) {
let args = text.strip_prefix("pack").unwrap_or("").trim();
if args.is_empty() {
self.current_pack = 0;
self.pragma_pack_stack.clear();
} else if let Some(inner) = args.strip_prefix('(') {
if let Some(close) = inner.find(')') {
let content = &inner[..close];
if content.is_empty() {
if let Some(prev) = self.pragma_pack_stack.pop() {
self.current_pack = prev;
}
} else if let Ok(n) = content.parse::<u32>() {
if n.is_power_of_two() && n <= 16 {
self.pragma_pack_stack.push(self.current_pack);
self.current_pack = n;
}
}
}
}
}
fn process_pragma_gcc_diagnostic(&mut self, text: &str) {
let rest = text.strip_prefix("GCC diagnostic").unwrap_or("").trim();
match rest {
"push" => {
self.pragma_diag_stack
.push(PragmaDiagnosticState::default());
}
"pop" => {
self.pragma_diag_stack.pop();
}
_ if rest.starts_with("ignored") => {
let flag = rest.strip_prefix("ignored").unwrap_or("").trim();
if let Some(state) = self.pragma_diag_stack.last_mut() {
state.ignored_warnings.insert(flag.to_string());
}
}
_ if rest.starts_with("warning") => {
let flag = rest.strip_prefix("warning").unwrap_or("").trim();
if let Some(state) = self.pragma_diag_stack.last_mut() {
state.ignored_warnings.remove(flag);
state.warning_as_error.remove(flag);
}
}
_ if rest.starts_with("error") => {
let flag = rest.strip_prefix("error").unwrap_or("").trim();
if let Some(state) = self.pragma_diag_stack.last_mut() {
state.ignored_warnings.remove(flag);
state.warning_as_error.insert(flag.to_string());
}
}
_ => {}
}
}
fn process_pragma_stdc(&mut self, text: &str) {
let _rest = text.strip_prefix("STDC").unwrap_or("").trim();
}
fn handle_line(&mut self, tokens: &[PPToken], directive_pos: usize) -> usize {
if self.should_skip() {
return self.skip_to_newline(tokens, directive_pos);
}
let mut i = directive_pos + 1;
while i < tokens.len() && tokens[i].kind == TokenKind::Unknown {
i += 1;
}
if i < tokens.len() {
let line_num = tokens[i].text.parse::<u32>().unwrap_or(0);
i += 1;
while i < tokens.len() && tokens[i].kind == TokenKind::Unknown {
i += 1;
}
if i < tokens.len() && tokens[i].kind == TokenKind::StringLiteral {
let filename = tokens[i].text.trim_matches('"');
i += 1;
}
}
self.skip_to_newline(tokens, directive_pos)
}
fn handle_import(&mut self, tokens: &[PPToken], directive_pos: usize) -> usize {
self.handle_include(tokens, directive_pos)
}
fn handle_module_directive(&mut self, tokens: &[PPToken], directive_pos: usize) -> usize {
self.skip_to_newline(tokens, directive_pos)
}
fn handle_embed(&mut self, tokens: &[PPToken], directive_pos: usize) -> usize {
self.skip_to_newline(tokens, directive_pos)
}
}
impl X86PreprocessorDeep {
pub fn evaluate_condition(&mut self, tokens: &[PPToken]) -> i64 {
if tokens.is_empty() {
return 0;
}
self.in_cond_eval = true;
let result = self.eval_expr(tokens, 0).0;
self.in_cond_eval = false;
result
}
fn eval_expr(&self, tokens: &[PPToken], pos: usize) -> (i64, usize) {
self.eval_logical_or(tokens, pos)
}
fn eval_logical_or(&self, tokens: &[PPToken], pos: usize) -> (i64, usize) {
let (mut left, mut pos) = self.eval_logical_and(tokens, pos);
while pos < tokens.len() {
if tokens[pos].kind == TokenKind::OrOr {
let (right, new_pos) = self.eval_logical_and(tokens, pos + 1);
left = if left != 0 || right != 0 { 1 } else { 0 };
pos = new_pos;
} else {
break;
}
}
(left, pos)
}
fn eval_logical_and(&self, tokens: &[PPToken], pos: usize) -> (i64, usize) {
let (mut left, mut pos) = self.eval_or(tokens, pos);
while pos < tokens.len() {
if tokens[pos].kind == TokenKind::AndAnd {
let (right, new_pos) = self.eval_or(tokens, pos + 1);
left = if left != 0 && right != 0 { 1 } else { 0 };
pos = new_pos;
} else {
break;
}
}
(left, pos)
}
fn eval_or(&self, tokens: &[PPToken], pos: usize) -> (i64, usize) {
let (mut left, mut pos) = self.eval_xor(tokens, pos);
while pos < tokens.len() {
if tokens[pos].kind == TokenKind::Pipe {
let (right, new_pos) = self.eval_xor(tokens, pos + 1);
left |= right;
pos = new_pos;
} else {
break;
}
}
(left, pos)
}
fn eval_xor(&self, tokens: &[PPToken], pos: usize) -> (i64, usize) {
let (mut left, mut pos) = self.eval_and(tokens, pos);
while pos < tokens.len() {
if tokens[pos].kind == TokenKind::Caret {
let (right, new_pos) = self.eval_and(tokens, pos + 1);
left ^= right;
pos = new_pos;
} else {
break;
}
}
(left, pos)
}
fn eval_and(&self, tokens: &[PPToken], pos: usize) -> (i64, usize) {
let (mut left, mut pos) = self.eval_equality(tokens, pos);
while pos < tokens.len() {
if tokens[pos].kind == TokenKind::Ampersand {
let (right, new_pos) = self.eval_equality(tokens, pos + 1);
left &= right;
pos = new_pos;
} else {
break;
}
}
(left, pos)
}
fn eval_equality(&self, tokens: &[PPToken], pos: usize) -> (i64, usize) {
let (mut left, mut pos) = self.eval_relational(tokens, pos);
while pos < tokens.len() {
if tokens[pos].kind == TokenKind::EqualEqual {
let (right, new_pos) = self.eval_relational(tokens, pos + 1);
left = if left == right { 1 } else { 0 };
pos = new_pos;
} else if tokens[pos].kind == TokenKind::NotEqual {
let (right, new_pos) = self.eval_relational(tokens, pos + 1);
left = if left != right { 1 } else { 0 };
pos = new_pos;
} else {
break;
}
}
(left, pos)
}
fn eval_relational(&self, tokens: &[PPToken], pos: usize) -> (i64, usize) {
let (mut left, mut pos) = self.eval_shift(tokens, pos);
while pos < tokens.len() {
if tokens[pos].kind == TokenKind::Less {
let (right, new_pos) = self.eval_shift(tokens, pos + 1);
left = if left < right { 1 } else { 0 };
pos = new_pos;
} else if tokens[pos].kind == TokenKind::Greater {
let (right, new_pos) = self.eval_shift(tokens, pos + 1);
left = if left > right { 1 } else { 0 };
pos = new_pos;
} else if tokens[pos].kind == TokenKind::LessEqual {
let (right, new_pos) = self.eval_shift(tokens, pos + 1);
left = if left <= right { 1 } else { 0 };
pos = new_pos;
} else if tokens[pos].kind == TokenKind::GreaterEqual {
let (right, new_pos) = self.eval_shift(tokens, pos + 1);
left = if left >= right { 1 } else { 0 };
pos = new_pos;
} else {
break;
}
}
(left, pos)
}
fn eval_shift(&self, tokens: &[PPToken], pos: usize) -> (i64, usize) {
let (mut left, mut pos) = self.eval_additive(tokens, pos);
while pos < tokens.len() {
if tokens[pos].kind == TokenKind::LessLess {
let (right, new_pos) = self.eval_additive(tokens, pos + 1);
left = left.wrapping_shl(right as u32);
pos = new_pos;
} else if tokens[pos].kind == TokenKind::GreaterGreater {
let (right, new_pos) = self.eval_additive(tokens, pos + 1);
left = left.wrapping_shr(right as u32);
pos = new_pos;
} else {
break;
}
}
(left, pos)
}
fn eval_additive(&self, tokens: &[PPToken], pos: usize) -> (i64, usize) {
let (mut left, mut pos) = self.eval_multiplicative(tokens, pos);
while pos < tokens.len() {
if tokens[pos].kind == TokenKind::Plus {
let (right, new_pos) = self.eval_multiplicative(tokens, pos + 1);
left = left.wrapping_add(right);
pos = new_pos;
} else if tokens[pos].kind == TokenKind::Minus {
let (right, new_pos) = self.eval_multiplicative(tokens, pos + 1);
left = left.wrapping_sub(right);
pos = new_pos;
} else {
break;
}
}
(left, pos)
}
fn eval_multiplicative(&self, tokens: &[PPToken], pos: usize) -> (i64, usize) {
let (mut left, mut pos) = self.eval_unary(tokens, pos);
while pos < tokens.len() {
if tokens[pos].kind == TokenKind::Star {
let (right, new_pos) = self.eval_unary(tokens, pos + 1);
left = left.wrapping_mul(right);
pos = new_pos;
} else if tokens[pos].kind == TokenKind::Slash {
let (right, new_pos) = self.eval_unary(tokens, pos + 1);
if right != 0 {
left /= right;
} else {
left = 0;
}
pos = new_pos;
} else if tokens[pos].kind == TokenKind::Percent {
let (right, new_pos) = self.eval_unary(tokens, pos + 1);
if right != 0 {
left %= right;
} else {
left = 0;
}
pos = new_pos;
} else {
break;
}
}
(left, pos)
}
fn eval_unary(&self, tokens: &[PPToken], pos: usize) -> (i64, usize) {
if pos >= tokens.len() {
return (0, pos);
}
match tokens[pos].kind {
TokenKind::Plus => self.eval_unary(tokens, pos + 1),
TokenKind::Minus => {
let (val, new_pos) = self.eval_unary(tokens, pos + 1);
(-val, new_pos)
}
TokenKind::Exclaim => {
let (val, new_pos) = self.eval_unary(tokens, pos + 1);
(if val != 0 { 0 } else { 1 }, new_pos)
}
TokenKind::Tilde => {
let (val, new_pos) = self.eval_unary(tokens, pos + 1);
(!val, new_pos)
}
_ => self.eval_primary(tokens, pos),
}
}
fn eval_primary(&self, tokens: &[PPToken], pos: usize) -> (i64, usize) {
if pos >= tokens.len() {
return (0, pos);
}
let tok = &tokens[pos];
if tok.kind == TokenKind::NumericLiteral {
let val = parse_pp_integer(&tok.text);
return (val, pos + 1);
}
if tok.kind == TokenKind::CharLiteral {
let val = tok.text.chars().nth(1).map(|c| c as i64).unwrap_or(0);
return (val, pos + 1);
}
if tok.is_lparen() {
let (val, new_pos) = self.eval_expr(tokens, pos + 1);
if new_pos < tokens.len() && tokens[new_pos].is_rparen() {
return (val, new_pos + 1);
}
return (val, new_pos);
}
if tok.is_ident() && tok.text == "defined" {
return self.eval_defined(tokens, pos);
}
if tok.is_ident() && tok.text == "__has_include" {
return self.eval_has_include(tokens, pos);
}
if tok.is_ident() && tok.text == "__has_include_next" {
return self.eval_has_include_next(tokens, pos);
}
if tok.is_ident() && tok.text == "__has_builtin" {
return self.eval_has_builtin(tokens, pos);
}
if tok.is_ident() && tok.text == "__has_feature" {
return self.eval_has_feature(tokens, pos);
}
if tok.is_ident() && tok.text == "__has_attribute" {
return self.eval_has_attribute(tokens, pos);
}
if tok.is_ident() && tok.text == "__has_cpp_attribute" {
return self.eval_has_cpp_attribute(tokens, pos);
}
if tok.is_ident() && tok.text == "__has_c_attribute" {
return self.eval_has_c_attribute(tokens, pos);
}
if tok.is_ident() && tok.text == "__has_declspec_attribute" {
return self.eval_has_declspec_attribute(tokens, pos);
}
if tok.is_ident() {
return (0, pos + 1);
}
(0, pos + 1)
}
fn eval_defined(&self, tokens: &[PPToken], pos: usize) -> (i64, usize) {
let mut i = pos + 1;
while i < tokens.len() && tokens[i].kind == TokenKind::Unknown {
i += 1;
}
let name = if i < tokens.len() && tokens[i].is_lparen() {
i += 1;
while i < tokens.len() && tokens[i].kind == TokenKind::Unknown {
i += 1;
}
let n = tokens[i].text.clone();
i += 1;
while i < tokens.len() && tokens[i].kind == TokenKind::Unknown {
i += 1;
}
if i < tokens.len() && tokens[i].is_rparen() {
i += 1;
}
n
} else if i < tokens.len() {
let n = tokens[i].text.clone();
i += 1;
n
} else {
String::new()
};
(if self.is_defined(&name) { 1 } else { 0 }, i)
}
fn eval_has_include(&self, tokens: &[PPToken], pos: usize) -> (i64, usize) {
let mut i = pos + 1;
while i < tokens.len() && tokens[i].kind == TokenKind::Unknown {
i += 1;
}
if i < tokens.len() && tokens[i].is_lparen() {
i += 1;
}
let mut filename = String::new();
while i < tokens.len()
&& (tokens[i].kind == TokenKind::Less
|| tokens[i].kind == TokenKind::Greater
|| tokens[i].kind == TokenKind::StringLiteral
|| tokens[i].is_ident())
{
filename.push_str(&tokens[i].text);
i += 1;
}
while i < tokens.len() && tokens[i].kind == TokenKind::Unknown {
i += 1;
}
if i < tokens.len() && tokens[i].is_rparen() {
i += 1;
}
let cleaned = filename.trim_matches(|c| c == '<' || c == '>' || c == '"');
let exists = self
.resolve_include_path(cleaned, filename.starts_with('<'))
.is_some();
(if exists { 1 } else { 0 }, i)
}
fn eval_has_include_next(&self, tokens: &[PPToken], pos: usize) -> (i64, usize) {
let mut i = pos + 1;
while i < tokens.len() && tokens[i].kind == TokenKind::Unknown {
i += 1;
}
if i < tokens.len() && tokens[i].is_lparen() {
i += 1;
}
let mut filename = String::new();
while i < tokens.len()
&& (tokens[i].kind == TokenKind::Less
|| tokens[i].kind == TokenKind::Greater
|| tokens[i].kind == TokenKind::StringLiteral
|| tokens[i].is_ident())
{
filename.push_str(&tokens[i].text);
i += 1;
}
while i < tokens.len() && tokens[i].kind == TokenKind::Unknown {
i += 1;
}
if i < tokens.len() && tokens[i].is_rparen() {
i += 1;
}
let cleaned = filename.trim_matches(|c| c == '<' || c == '>' || c == '"');
let exists = self
.resolve_include_next_path(cleaned, filename.starts_with('<'))
.is_some();
(if exists { 1 } else { 0 }, i)
}
fn eval_has_builtin(&self, tokens: &[PPToken], pos: usize) -> (i64, usize) {
let mut i = pos + 1;
while i < tokens.len() && tokens[i].kind == TokenKind::Unknown {
i += 1;
}
if i < tokens.len() && tokens[i].is_lparen() {
i += 1;
}
let name = if i < tokens.len() {
let n = tokens[i].text.clone();
i += 1;
n
} else {
String::new()
};
while i < tokens.len() && tokens[i].kind == TokenKind::Unknown {
i += 1;
}
if i < tokens.len() && tokens[i].is_rparen() {
i += 1;
}
let has = self.known_builtins.contains(&name) || name.starts_with("__builtin_");
(if has { 1 } else { 0 }, i)
}
fn eval_has_feature(&self, tokens: &[PPToken], pos: usize) -> (i64, usize) {
let mut i = pos + 1;
while i < tokens.len() && tokens[i].kind == TokenKind::Unknown {
i += 1;
}
if i < tokens.len() && tokens[i].is_lparen() {
i += 1;
}
let name = if i < tokens.len() {
let n = tokens[i].text.clone();
i += 1;
n
} else {
String::new()
};
while i < tokens.len() && tokens[i].kind == TokenKind::Unknown {
i += 1;
}
if i < tokens.len() && tokens[i].is_rparen() {
i += 1;
}
let has = self.known_features.contains(&name);
(if has { 1 } else { 0 }, i)
}
fn eval_has_attribute(&self, tokens: &[PPToken], pos: usize) -> (i64, usize) {
let mut i = pos + 1;
while i < tokens.len() && tokens[i].kind == TokenKind::Unknown {
i += 1;
}
if i < tokens.len() && tokens[i].is_lparen() {
i += 1;
}
let name = if i < tokens.len() {
let n = tokens[i].text.clone();
i += 1;
n
} else {
String::new()
};
while i < tokens.len() && tokens[i].kind == TokenKind::Unknown {
i += 1;
}
if i < tokens.len() && tokens[i].is_rparen() {
i += 1;
}
let has = self.known_attributes.contains(&name);
(if has { 1 } else { 0 }, i)
}
fn eval_has_cpp_attribute(&self, tokens: &[PPToken], pos: usize) -> (i64, usize) {
let mut i = pos + 1;
while i < tokens.len() && tokens[i].kind == TokenKind::Unknown {
i += 1;
}
if i < tokens.len() && tokens[i].is_lparen() {
i += 1;
}
let name = if i < tokens.len() {
let n = tokens[i].text.clone();
i += 1;
n
} else {
String::new()
};
while i < tokens.len() && tokens[i].kind == TokenKind::Unknown {
i += 1;
}
if i < tokens.len() && tokens[i].is_rparen() {
i += 1;
}
let version = self.known_cpp_attributes.get(&name).copied().unwrap_or(0);
(version as i64, i)
}
fn eval_has_c_attribute(&self, tokens: &[PPToken], pos: usize) -> (i64, usize) {
let mut i = pos + 1;
while i < tokens.len() && tokens[i].kind == TokenKind::Unknown {
i += 1;
}
if i < tokens.len() && tokens[i].is_lparen() {
i += 1;
}
let name = if i < tokens.len() {
let n = tokens[i].text.clone();
i += 1;
n
} else {
String::new()
};
while i < tokens.len() && tokens[i].kind == TokenKind::Unknown {
i += 1;
}
if i < tokens.len() && tokens[i].is_rparen() {
i += 1;
}
let has = self.known_c_attributes.contains(&name);
(if has { 202311 } else { 0 }, i)
}
fn eval_has_declspec_attribute(&self, tokens: &[PPToken], pos: usize) -> (i64, usize) {
let mut i = pos + 1;
while i < tokens.len() && tokens[i].kind == TokenKind::Unknown {
i += 1;
}
if i < tokens.len() && tokens[i].is_lparen() {
i += 1;
}
let name = if i < tokens.len() {
let n = tokens[i].text.clone();
i += 1;
n
} else {
String::new()
};
while i < tokens.len() && tokens[i].kind == TokenKind::Unknown {
i += 1;
}
if i < tokens.len() && tokens[i].is_rparen() {
i += 1;
}
let has = self.known_declspec_attributes.contains(&name);
(if has { 1 } else { 0 }, i)
}
}
fn strip_int_suffix(text: &str) -> &str {
let text = text.trim_end_matches(&['u', 'U', 'l', 'L', 'z', 'Z'][..]);
text
}
pub fn parse_pp_integer(text: &str) -> i64 {
let text = text.trim();
if text.is_empty() {
return 0;
}
let text = strip_int_suffix(text);
if text.is_empty() {
return 0;
}
if (text.len() >= 2 && &text[0..2] == "0x") || (text.len() >= 2 && &text[0..2] == "0X") {
return i64::from_str_radix(&text[2..], 16).unwrap_or(0);
}
if (text.len() >= 2 && &text[0..2] == "0b") || (text.len() >= 2 && &text[0..2] == "0B") {
return i64::from_str_radix(&text[2..], 2).unwrap_or(0);
}
if text.len() > 1 && text.starts_with('0') {
if let Ok(val) = i64::from_str_radix(&text[1..], 8) {
return val;
}
}
text.parse::<i64>().unwrap_or(0)
}
impl X86PreprocessorDeep {
pub fn preprocess(&mut self, source: &str, file_path: &str) -> Vec<Token> {
self.current_file = Some(PathBuf::from(file_path));
if self.base_file.is_none() {
self.base_file = Some(PathBuf::from(file_path));
}
self.include_stack.push(PathBuf::from(file_path));
let lines: Vec<&str> = source.lines().collect();
let mut expanded_tokens = Vec::new();
let mut in_cond_skip = false;
let mut cond_depth = 0u32;
for (line_num, line) in lines.iter().enumerate() {
let trimmed = line.trim();
let loc = SourceLoc::new(line_num as u32 + 1, 1, 0);
if trimmed.starts_with('#') {
let directive = &trimmed[1..];
let parts: Vec<&str> = directive.split_whitespace().collect();
if parts.is_empty() {
continue;
}
match parts[0] {
"define" => {
if parts.len() >= 2 {
let name = parts[1].to_string();
let value = if parts.len() >= 3 {
parts[2..].join(" ")
} else {
"1".into()
};
if !in_cond_skip {
self.define(&name, &value);
}
}
}
"undef" => {
if parts.len() >= 2 && !in_cond_skip {
self.undefine(parts[1]);
}
}
"ifdef" | "ifndef" => {
cond_depth += 1;
if parts.len() >= 2 {
let defined = self.is_defined(parts[1]);
in_cond_skip = if parts[0] == "ifdef" {
!defined
} else {
defined
};
}
}
"if" => {
cond_depth += 1;
if parts.len() >= 2 {
let expr: Vec<PPToken> = parts[1..]
.iter()
.map(|s| PPToken::new(TokenKind::Identifier, s.to_string(), loc))
.collect();
let val = self.evaluate_condition(&expr);
in_cond_skip = val == 0;
}
}
"elif" => {
if parts.len() >= 2 {
let val = self.evaluate_condition(&[]);
in_cond_skip = true; }
}
"else" => {
in_cond_skip = !in_cond_skip;
}
"endif" => {
if cond_depth > 0 {
cond_depth -= 1;
}
if cond_depth == 0 {
in_cond_skip = false;
}
}
"include" => {
if !in_cond_skip && parts.len() >= 2 {
let path = parts[1..].join(" ");
let is_system = path.starts_with('<');
self.resolve_and_process_include(&path, is_system);
}
}
"pragma" => {
if !in_cond_skip {
let pragma_text = if parts.len() > 1 {
parts[1..].join(" ")
} else {
String::new()
};
self.process_pragma(&pragma_text);
}
}
"error" => {
if !in_cond_skip {
let msg = parts[1..].join(" ");
self.error(format!("#error: {}", msg), loc);
}
}
"warning" => {
if !in_cond_skip {
let msg = parts[1..].join(" ");
self.warning(format!("#warning: {}", msg), loc);
}
}
"line" => {
}
_ => {}
}
continue;
}
if !in_cond_skip {
let expanded = self.expand_line_macros(line);
expanded_tokens.push(Token::new(TokenKind::Identifier, &expanded, loc));
}
self.guard_detector.analyse_line(line);
}
self.include_stack.pop();
expanded_tokens
}
fn expand_line_macros(&mut self, line: &str) -> String {
let mut result = String::new();
let mut i = 0;
let chars: Vec<char> = line.chars().collect();
while i < chars.len() {
while i < chars.len() && chars[i].is_whitespace() {
result.push(chars[i]);
i += 1;
}
if i < chars.len() && (chars[i].is_ascii_alphabetic() || chars[i] == '_') {
let start = i;
while i < chars.len() && (chars[i].is_ascii_alphanumeric() || chars[i] == '_') {
i += 1;
}
let ident: String = chars[start..i].iter().collect();
if let Some(mdef) = self.defines.get(&ident) {
if !mdef.is_function_like && !mdef.is_builtin && !mdef.is_expanding {
for rt in &mdef.replacement {
result.push_str(&rt.text);
}
continue;
}
}
if let Some(mdef) = self.defines.get(&ident) {
if mdef.is_builtin {
let loc = SourceLoc::new(0, 0, 0);
let expanded = self.expand_builtin_macro(&ident, loc);
result.push_str(&expanded.text);
continue;
}
}
result.push_str(&ident);
continue;
}
if i < chars.len() {
result.push(chars[i]);
i += 1;
}
}
result
}
pub fn add_standard_includes(&mut self) {
self.add_include_path("/usr/include", true);
self.add_include_path("/usr/local/include", true);
self.add_include_path("/usr/include/x86_64-linux-gnu", true);
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_macro_def_object_like() {
let def = MacroDefinition::object_like(
"FOO",
vec![PPToken::new(
TokenKind::NumericLiteral,
"1".into(),
SourceLoc::unknown(),
)],
);
assert_eq!(def.name, "FOO");
assert!(!def.is_function_like);
}
#[test]
fn test_macro_def_function_like() {
let def = MacroDefinition::function_like(
"MIN",
vec!["a".into(), "b".into()],
vec![PPToken::new(
TokenKind::Identifier,
"a".into(),
SourceLoc::unknown(),
)],
false,
);
assert!(def.is_function_like);
assert_eq!(def.params.len(), 2);
}
#[test]
fn test_preprocessor_new() {
let mut pp = X86PreprocessorDeep::new(CLangStandard::C17);
assert!(pp.is_defined("__STDC__"));
assert!(pp.is_defined("__LINE__"));
}
#[test]
fn test_define_and_check() {
let mut pp = X86PreprocessorDeep::new(CLangStandard::C17);
pp.define("MY_MACRO", "42");
assert!(pp.is_defined("MY_MACRO"));
pp.undefine("MY_MACRO");
assert!(!pp.is_defined("MY_MACRO"));
}
#[test]
fn test_builtin_line() {
let mut pp = X86PreprocessorDeep::new(CLangStandard::C17);
let tok = pp.expand_builtin_macro("__LINE__", SourceLoc::new(5, 1, 0));
assert_eq!(tok.text, "5");
}
#[test]
fn test_builtin_counter() {
let mut pp = X86PreprocessorDeep::new(CLangStandard::C17);
pp.counter = 0;
let tok1 = pp.expand_builtin_macro("__COUNTER__", SourceLoc::unknown());
assert_eq!(tok1.text, "0");
let tok2 = pp.expand_builtin_macro("__COUNTER__", SourceLoc::unknown());
assert_eq!(tok2.text, "1");
}
#[test]
fn test_eval_simple_integer() {
let mut pp = X86PreprocessorDeep::new(CLangStandard::C17);
let tokens = vec![PPToken::new(
TokenKind::NumericLiteral,
"42".into(),
SourceLoc::unknown(),
)];
let val = pp.evaluate_condition(&tokens);
assert_eq!(val, 42);
}
#[test]
fn test_eval_addition() {
let mut pp = X86PreprocessorDeep::new(CLangStandard::C17);
let tokens = vec![
PPToken::new(TokenKind::NumericLiteral, "1".into(), SourceLoc::unknown()),
PPToken::new(TokenKind::Plus, "+".into(), SourceLoc::unknown()),
PPToken::new(TokenKind::NumericLiteral, "2".into(), SourceLoc::unknown()),
];
let val = pp.evaluate_condition(&tokens);
assert_eq!(val, 3);
}
#[test]
fn test_eval_defined() {
let mut pp = X86PreprocessorDeep::new(CLangStandard::C17);
pp.define("DEFINED_MACRO", "1");
let tokens = vec![
PPToken::new(
TokenKind::Identifier,
"defined".into(),
SourceLoc::unknown(),
),
PPToken::new(TokenKind::LParen, "(".into(), SourceLoc::unknown()),
PPToken::new(
TokenKind::Identifier,
"DEFINED_MACRO".into(),
SourceLoc::unknown(),
),
PPToken::new(TokenKind::RParen, ")".into(), SourceLoc::unknown()),
];
let val = pp.evaluate_condition(&tokens);
assert_eq!(val, 1);
}
#[test]
fn test_eval_comparison() {
let mut pp = X86PreprocessorDeep::new(CLangStandard::C17);
let tokens = vec![
PPToken::new(TokenKind::NumericLiteral, "5".into(), SourceLoc::unknown()),
PPToken::new(TokenKind::Greater, ">".into(), SourceLoc::unknown()),
PPToken::new(TokenKind::NumericLiteral, "3".into(), SourceLoc::unknown()),
];
let val = pp.evaluate_condition(&tokens);
assert_eq!(val, 1);
}
#[test]
fn test_eval_logical_and() {
let mut pp = X86PreprocessorDeep::new(CLangStandard::C17);
let tokens = vec![
PPToken::new(TokenKind::NumericLiteral, "1".into(), SourceLoc::unknown()),
PPToken::new(TokenKind::AndAnd, "&&".into(), SourceLoc::unknown()),
PPToken::new(TokenKind::NumericLiteral, "1".into(), SourceLoc::unknown()),
];
let val = pp.evaluate_condition(&tokens);
assert_eq!(val, 1);
}
#[test]
fn test_paste_tokens_simple() {
let mut pp = X86PreprocessorDeep::new(CLangStandard::C17);
let tokens = vec![
PPToken::new(TokenKind::Identifier, "foo".into(), SourceLoc::unknown()),
PPToken::new(TokenKind::HashHash, "##".into(), SourceLoc::unknown()),
PPToken::new(TokenKind::Identifier, "bar".into(), SourceLoc::unknown()),
];
let result = pp.paste_tokens(&tokens);
assert_eq!(result.len(), 1);
assert_eq!(result[0].text, "foobar");
}
#[test]
fn test_paste_tokens_placemarker_left() {
let mut pp = X86PreprocessorDeep::new(CLangStandard::C17);
let tokens = vec![
PPToken::placemarker(),
PPToken::new(TokenKind::HashHash, "##".into(), SourceLoc::unknown()),
PPToken::new(TokenKind::Identifier, "bar".into(), SourceLoc::unknown()),
];
let result = pp.paste_tokens(&tokens);
assert_eq!(result.len(), 1);
assert_eq!(result[0].text, "bar");
}
#[test]
fn test_placemarker_removal() {
let mut pp = X86PreprocessorDeep::new(CLangStandard::C17);
let tokens = vec![
PPToken::new(TokenKind::Identifier, "foo".into(), SourceLoc::unknown()),
PPToken::placemarker(),
PPToken::new(TokenKind::Identifier, "bar".into(), SourceLoc::unknown()),
];
let result = pp.remove_placemarkers(&tokens);
assert_eq!(result.len(), 2);
}
#[test]
fn test_stringify_simple() {
let tokens = vec![PPToken::new(
TokenKind::Identifier,
"hello".into(),
SourceLoc::unknown(),
)];
let result = stringify_tokens(&tokens);
assert_eq!(result, "hello");
}
#[test]
fn test_stringify_with_whitespace() {
let mut tok1 = PPToken::new(TokenKind::Identifier, "a".into(), SourceLoc::unknown());
let mut tok2 = PPToken::new(TokenKind::Identifier, "b".into(), SourceLoc::unknown());
tok2.leading_whitespace = true;
let result = stringify_tokens(&[tok1, tok2]);
assert_eq!(result, "a b");
}
#[test]
fn test_stringify_escape() {
let tokens = vec![PPToken::new(
TokenKind::Identifier,
"hello\"world".into(),
SourceLoc::unknown(),
)];
let result = stringify_tokens(&tokens);
assert_eq!(result, "hello\\\"world");
}
#[test]
fn test_stringify_single() {
let tok = PPToken::new(
TokenKind::Identifier,
"test\\n".into(),
SourceLoc::unknown(),
);
let result = stringify_single(&tok);
assert_eq!(result, "test\\\\n");
}
#[test]
fn test_parse_pp_integer_decimal() {
assert_eq!(parse_pp_integer("42"), 42);
assert_eq!(parse_pp_integer("0"), 0);
}
#[test]
fn test_parse_pp_integer_hex() {
assert_eq!(parse_pp_integer("0xFF"), 255);
assert_eq!(parse_pp_integer("0X10"), 16);
}
#[test]
fn test_parse_pp_integer_octal() {
assert_eq!(parse_pp_integer("0777"), 511);
}
#[test]
fn test_parse_pp_integer_binary() {
assert_eq!(parse_pp_integer("0b1010"), 10);
assert_eq!(parse_pp_integer("0B1111"), 15);
}
#[test]
fn test_add_include_path() {
let mut pp = X86PreprocessorDeep::new(CLangStandard::C17);
pp.add_include_path("/custom/include", false);
assert_eq!(pp.include_paths.len(), 1);
assert!(!pp.include_paths[0].is_system);
}
#[test]
fn test_pragma_once() {
let mut pp = X86PreprocessorDeep::new(CLangStandard::C17);
pp.current_file = Some(PathBuf::from("test.h"));
pp.process_pragma("once");
assert!(pp.pragma_once_files.contains(&PathBuf::from("test.h")));
}
#[test]
fn test_pragma_pack_push() {
let mut pp = X86PreprocessorDeep::new(CLangStandard::C17);
pp.process_pragma("pack(4)");
assert_eq!(pp.current_pack, 4);
}
#[test]
fn test_pragma_pack_pop() {
let mut pp = X86PreprocessorDeep::new(CLangStandard::C17);
pp.process_pragma("pack(4)");
pp.process_pragma("pack()");
assert_eq!(pp.current_pack, 0);
}
#[test]
fn test_pragma_gcc_diagnostic_push_pop() {
let mut pp = X86PreprocessorDeep::new(CLangStandard::C17);
pp.process_pragma("GCC diagnostic push");
assert_eq!(pp.pragma_diag_stack.len(), 1);
pp.process_pragma("GCC diagnostic pop");
assert_eq!(pp.pragma_diag_stack.len(), 0);
}
#[test]
fn test_error_directive() {
let mut pp = X86PreprocessorDeep::new(CLangStandard::C17);
let tokens = vec![
PPToken::new(TokenKind::Hash, "#".into(), SourceLoc::unknown()),
PPToken::new(TokenKind::Identifier, "error".into(), SourceLoc::unknown()),
PPToken::new(
TokenKind::Identifier,
"test_error".into(),
SourceLoc::unknown(),
),
];
pp.process(&[]);
}
#[test]
fn test_has_builtin() {
let mut pp = X86PreprocessorDeep::new(CLangStandard::C17);
let tokens = vec![
PPToken::new(
TokenKind::Identifier,
"__has_builtin".into(),
SourceLoc::unknown(),
),
PPToken::new(TokenKind::LParen, "(".into(), SourceLoc::unknown()),
PPToken::new(
TokenKind::Identifier,
"__builtin_trap".into(),
SourceLoc::unknown(),
),
PPToken::new(TokenKind::RParen, ")".into(), SourceLoc::unknown()),
];
let val = pp.evaluate_condition(&tokens);
assert_eq!(val, 1);
}
#[test]
fn test_has_feature() {
let mut pp = X86PreprocessorDeep::new(CLangStandard::C17);
let tokens = vec![
PPToken::new(
TokenKind::Identifier,
"__has_feature".into(),
SourceLoc::unknown(),
),
PPToken::new(TokenKind::LParen, "(".into(), SourceLoc::unknown()),
PPToken::new(
TokenKind::Identifier,
"c_atomic".into(),
SourceLoc::unknown(),
),
PPToken::new(TokenKind::RParen, ")".into(), SourceLoc::unknown()),
];
let val = pp.evaluate_condition(&tokens);
assert_eq!(val, 1);
}
#[test]
fn test_preprocess_simple() {
let mut pp = X86PreprocessorDeep::new(CLangStandard::C17);
pp.define("FOO", "bar");
let source = "FOO\n";
let result = pp.preprocess(source, "test.c");
assert!(!result.is_empty());
}
#[test]
fn test_preprocess_ifdef_active() {
let mut pp = X86PreprocessorDeep::new(CLangStandard::C17);
pp.define("ACTIVE", "1");
let source = "#ifdef ACTIVE\nint x;\n#endif\n";
let _result = pp.preprocess(source, "test.c");
}
#[test]
fn test_preprocess_ifdef_inactive() {
let mut pp = X86PreprocessorDeep::new(CLangStandard::C17);
let source = "#ifdef MISSING\nint x;\n#endif\nint y;\n";
let _result = pp.preprocess(source, "test.c");
}
#[test]
fn test_pptoken_display() {
let tok = PPToken::new(TokenKind::Identifier, "foo".into(), SourceLoc::unknown());
assert_eq!(format!("{}", tok), "foo");
}
#[test]
fn test_pptoken_placemarker_display() {
let tok = PPToken::placemarker();
assert_eq!(format!("{}", tok), "<placemarker>");
}
#[test]
fn test_classify_pasted_token() {
assert_eq!(classify_pasted_token("123"), TokenKind::NumericLiteral);
assert_eq!(classify_pasted_token("abc"), TokenKind::Identifier);
assert_eq!(classify_pasted_token("_x1"), TokenKind::Identifier);
}
#[test]
fn test_include_search_path() {
let p = IncludeSearchPath::user("/usr/local/include");
assert!(!p.is_system);
let s = IncludeSearchPath::system("/usr/include");
assert!(s.is_system);
}
#[test]
fn test_macro_expansion_recursion_guard() {
let mut pp = X86PreprocessorDeep::new(CLangStandard::C17);
let mdef = MacroDefinition {
name: "RECURSIVE".into(),
params: vec![],
is_function_like: false,
is_variadic: false,
va_args_name: "__VA_ARGS__".into(),
replacement: vec![PPToken::new(
TokenKind::Identifier,
"RECURSIVE".into(),
SourceLoc::unknown(),
)],
is_expanding: false,
def_location: SourceLoc::unknown(),
is_predefined: false,
is_builtin: false,
};
pp.defines.insert("RECURSIVE".into(), mdef);
let source = "RECURSIVE\n";
let _result = pp.preprocess(source, "test.c");
}
#[test]
fn test_cpp_mode_predefines() {
let pp = X86PreprocessorDeep::new_cpp(CLangStandard::C17);
assert!(pp.is_defined("__cplusplus"));
}
#[test]
fn test_gnu_mode_predefines() {
let pp = X86PreprocessorDeep::new_gnu(CLangStandard::C17);
assert!(pp.is_defined("__GNUC__"));
}
#[test]
fn test_base_file_tracking() {
let mut pp = X86PreprocessorDeep::new(CLangStandard::C17);
pp.preprocess("int x;", "main.c");
assert_eq!(pp.base_file, Some(PathBuf::from("main.c")));
}
#[test]
fn test_multiple_defines() {
let mut pp = X86PreprocessorDeep::new(CLangStandard::C17);
pp.define("A", "1");
pp.define("B", "2");
pp.define("C", "3");
assert!(pp.is_defined("A"));
assert!(pp.is_defined("B"));
assert!(pp.is_defined("C"));
}
#[test]
fn test_clear_errors() {
let mut pp = X86PreprocessorDeep::new(CLangStandard::C17);
pp.error("test", SourceLoc::unknown());
assert!(!pp.errors.is_empty());
pp.clear_errors();
assert!(pp.errors.is_empty());
}
#[test]
fn test_function_like_macro_expansion() {
let mut pp = X86PreprocessorDeep::new(CLangStandard::C17);
let mdef = MacroDefinition::function_like(
"ADD",
vec!["a".into(), "b".into()],
vec![
PPToken::new(TokenKind::Identifier, "a".into(), SourceLoc::unknown()),
PPToken::new(TokenKind::Plus, "+".into(), SourceLoc::unknown()),
PPToken::new(TokenKind::Identifier, "b".into(), SourceLoc::unknown()),
],
false,
);
pp.defines.insert("ADD".into(), mdef);
assert!(pp.is_defined("ADD"));
}
#[test]
fn test_variadic_macro_definition() {
let mdef = MacroDefinition::function_like(
"LOG",
vec!["fmt".into(), "...".into()],
vec![
PPToken::new(TokenKind::Identifier, "printf".into(), SourceLoc::unknown()),
PPToken::new(TokenKind::LParen, "(".into(), SourceLoc::unknown()),
PPToken::new(TokenKind::Identifier, "fmt".into(), SourceLoc::unknown()),
PPToken::new(TokenKind::Comma, ",".into(), SourceLoc::unknown()),
PPToken::new(
TokenKind::Identifier,
"__VA_ARGS__".into(),
SourceLoc::unknown(),
),
PPToken::new(TokenKind::RParen, ")".into(), SourceLoc::unknown()),
],
true,
);
assert!(mdef.is_variadic);
assert_eq!(mdef.params.len(), 2);
}
#[test]
fn test_stringify_tokens_multiple() {
let tokens = vec![
PPToken::new(TokenKind::Identifier, "hello".into(), SourceLoc::unknown()),
PPToken::new(TokenKind::Identifier, "world".into(), SourceLoc::unknown()),
];
let result = stringify_tokens(&tokens);
assert!(result.contains("hello"));
assert!(result.contains("world"));
}
#[test]
fn test_stringify_tokens_with_escape() {
let tokens = vec![PPToken::new(
TokenKind::Identifier,
"line1\\nline2".into(),
SourceLoc::unknown(),
)];
let result = stringify_tokens(&tokens);
assert!(result.contains("\\\\"));
}
#[test]
fn test_paste_tokens_multiple() {
let mut pp = X86PreprocessorDeep::new(CLangStandard::C17);
let tokens = vec![
PPToken::new(TokenKind::Identifier, "prefix".into(), SourceLoc::unknown()),
PPToken::new(TokenKind::HashHash, "##".into(), SourceLoc::unknown()),
PPToken::new(TokenKind::Identifier, "middle".into(), SourceLoc::unknown()),
PPToken::new(TokenKind::HashHash, "##".into(), SourceLoc::unknown()),
PPToken::new(TokenKind::Identifier, "suffix".into(), SourceLoc::unknown()),
];
let result = pp.paste_tokens(&tokens);
assert_eq!(result.len(), 1);
assert_eq!(result[0].text, "prefixmiddlesuffix");
}
#[test]
fn test_eval_hex_integer() {
let mut pp = X86PreprocessorDeep::new(CLangStandard::C17);
let tokens = vec![PPToken::new(
TokenKind::NumericLiteral,
"0xFF".into(),
SourceLoc::unknown(),
)];
assert_eq!(pp.evaluate_condition(&tokens), 255);
}
#[test]
fn test_eval_octal_integer() {
let mut pp = X86PreprocessorDeep::new(CLangStandard::C17);
let tokens = vec![PPToken::new(
TokenKind::NumericLiteral,
"0777".into(),
SourceLoc::unknown(),
)];
assert_eq!(pp.evaluate_condition(&tokens), 511);
}
#[test]
fn test_eval_unary_minus() {
let mut pp = X86PreprocessorDeep::new(CLangStandard::C17);
let tokens = vec![
PPToken::new(TokenKind::Minus, "-".into(), SourceLoc::unknown()),
PPToken::new(TokenKind::NumericLiteral, "42".into(), SourceLoc::unknown()),
];
assert_eq!(pp.evaluate_condition(&tokens), -42);
}
#[test]
fn test_eval_bitwise_not() {
let mut pp = X86PreprocessorDeep::new(CLangStandard::C17);
let tokens = vec![
PPToken::new(TokenKind::Tilde, "~".into(), SourceLoc::unknown()),
PPToken::new(TokenKind::NumericLiteral, "0".into(), SourceLoc::unknown()),
];
assert_eq!(pp.evaluate_condition(&tokens), -1);
}
#[test]
fn test_eval_logical_or() {
let mut pp = X86PreprocessorDeep::new(CLangStandard::C17);
let tokens = vec![
PPToken::new(TokenKind::NumericLiteral, "0".into(), SourceLoc::unknown()),
PPToken::new(TokenKind::OrOr, "||".into(), SourceLoc::unknown()),
PPToken::new(TokenKind::NumericLiteral, "0".into(), SourceLoc::unknown()),
];
assert_eq!(pp.evaluate_condition(&tokens), 0);
}
#[test]
fn test_eval_bitwise_operators() {
let mut pp = X86PreprocessorDeep::new(CLangStandard::C17);
let tokens = vec![
PPToken::new(
TokenKind::NumericLiteral,
"255".into(),
SourceLoc::unknown(),
),
PPToken::new(TokenKind::Ampersand, "&".into(), SourceLoc::unknown()),
PPToken::new(TokenKind::NumericLiteral, "15".into(), SourceLoc::unknown()),
];
assert_eq!(pp.evaluate_condition(&tokens), 15);
}
#[test]
fn test_eval_shift_operators() {
let mut pp = X86PreprocessorDeep::new(CLangStandard::C17);
let tokens = vec![
PPToken::new(TokenKind::NumericLiteral, "1".into(), SourceLoc::unknown()),
PPToken::new(TokenKind::LessLess, "<<".into(), SourceLoc::unknown()),
PPToken::new(TokenKind::NumericLiteral, "4".into(), SourceLoc::unknown()),
];
assert_eq!(pp.evaluate_condition(&tokens), 16);
}
#[test]
fn test_eval_right_shift() {
let mut pp = X86PreprocessorDeep::new(CLangStandard::C17);
let tokens = vec![
PPToken::new(TokenKind::NumericLiteral, "16".into(), SourceLoc::unknown()),
PPToken::new(TokenKind::GreaterGreater, ">>".into(), SourceLoc::unknown()),
PPToken::new(TokenKind::NumericLiteral, "2".into(), SourceLoc::unknown()),
];
assert_eq!(pp.evaluate_condition(&tokens), 4);
}
#[test]
fn test_eval_parenthesized() {
let mut pp = X86PreprocessorDeep::new(CLangStandard::C17);
let tokens = vec![
PPToken::new(TokenKind::LParen, "(".into(), SourceLoc::unknown()),
PPToken::new(TokenKind::NumericLiteral, "42".into(), SourceLoc::unknown()),
PPToken::new(TokenKind::RParen, ")".into(), SourceLoc::unknown()),
];
assert_eq!(pp.evaluate_condition(&tokens), 42);
}
#[test]
fn test_eval_defined_without_parens() {
let mut pp = X86PreprocessorDeep::new(CLangStandard::C17);
pp.define("DEFINED_VAR", "1");
let tokens = vec![
PPToken::new(
TokenKind::Identifier,
"defined".into(),
SourceLoc::unknown(),
),
PPToken::new(
TokenKind::Identifier,
"DEFINED_VAR".into(),
SourceLoc::unknown(),
),
];
assert_eq!(pp.evaluate_condition(&tokens), 1);
}
#[test]
fn test_eval_undefined_identifier() {
let mut pp = X86PreprocessorDeep::new(CLangStandard::C17);
let tokens = vec![PPToken::new(
TokenKind::Identifier,
"UNDEFINED".into(),
SourceLoc::unknown(),
)];
assert_eq!(pp.evaluate_condition(&tokens), 0);
}
#[test]
fn test_parse_pp_integer_with_suffix() {
assert_eq!(parse_pp_integer("42U"), 42);
assert_eq!(parse_pp_integer("42L"), 42);
assert_eq!(parse_pp_integer("42ULL"), 42);
}
#[test]
fn test_parse_pp_integer_hex_with_suffix() {
assert_eq!(parse_pp_integer("0xFFU"), 255);
assert_eq!(parse_pp_integer("0X10L"), 16);
}
#[test]
fn test_conditional_ifdef_nested() {
let mut pp = X86PreprocessorDeep::new(CLangStandard::C17);
pp.define("OUTER", "1");
pp.define("INNER", "2");
let source = "#ifdef OUTER\n#ifdef INNER\nint x;\n#endif\n#endif\n";
let result = pp.preprocess(source, "test.c");
assert!(!result.is_empty());
}
#[test]
fn test_conditional_if_arithmetic() {
let mut pp = X86PreprocessorDeep::new(CLangStandard::C17);
let source = "#if 1 + 2 * 3 == 7\nint yes;\n#else\nint no;\n#endif\n";
let _result = pp.preprocess(source, "test.c");
}
#[test]
fn test_preprocessor_include_guard_detection() {
let mut pp = X86PreprocessorDeep::new(CLangStandard::C17);
let source = "#ifndef HEADER_H\n#define HEADER_H\nint content;\n#endif\n";
pp.preprocess(source, "header.h");
assert!(pp.guard_detector.has_guard());
}
#[test]
fn test_preprocessor_include_guard_detection_invalid() {
let mut pp = X86PreprocessorDeep::new(CLangStandard::C17);
let source = "int not_a_guard;\n#ifndef HEADER_H\n#define HEADER_H\n#endif\n";
pp.preprocess(source, "header.h");
assert!(!pp.guard_detector.has_guard());
}
#[test]
fn test_builtin_expansion_line() {
let mut pp = X86PreprocessorDeep::new(CLangStandard::C17);
let tok = pp.expand_builtin_macro("__LINE__", SourceLoc::new(100, 5, 0));
assert_eq!(tok.text, "100");
}
#[test]
fn test_builtin_expansion_file() {
let mut pp = X86PreprocessorDeep::new(CLangStandard::C17);
pp.current_file = Some(PathBuf::from("myfile.c"));
let tok = pp.expand_builtin_macro("__FILE__", SourceLoc::unknown());
assert!(tok.text.contains("myfile.c"));
}
#[test]
fn test_builtin_expansion_base_file() {
let mut pp = X86PreprocessorDeep::new(CLangStandard::C17);
pp.base_file = Some(PathBuf::from("main.c"));
let tok = pp.expand_builtin_macro("__BASE_FILE__", SourceLoc::unknown());
assert!(tok.text.contains("main.c"));
}
#[test]
fn test_builtin_expansion_file_name() {
let mut pp = X86PreprocessorDeep::new(CLangStandard::C17);
pp.current_file = Some(PathBuf::from("/path/to/myfile.c"));
let tok = pp.expand_builtin_macro("__FILE_NAME__", SourceLoc::unknown());
assert!(tok.text.contains("myfile.c"));
}
#[test]
fn test_builtin_expansion_date() {
let mut pp = X86PreprocessorDeep::new(CLangStandard::C17);
pp.enable_time_macros = true;
let tok = pp.expand_builtin_macro("__DATE__", SourceLoc::unknown());
assert!(tok.text.starts_with('"'));
}
#[test]
fn test_builtin_expansion_time() {
let mut pp = X86PreprocessorDeep::new(CLangStandard::C17);
pp.enable_time_macros = true;
let tok = pp.expand_builtin_macro("__TIME__", SourceLoc::unknown());
assert!(tok.text.contains(':'));
}
#[test]
fn test_builtin_expansion_timestamp() {
let mut pp = X86PreprocessorDeep::new(CLangStandard::C17);
pp.enable_time_macros = true;
let tok = pp.expand_builtin_macro("__TIMESTAMP__", SourceLoc::unknown());
assert!(tok.text.len() > 4);
assert!(tok.text.starts_with('"'));
}
#[test]
fn test_builtin_expansion_function() {
let mut pp = X86PreprocessorDeep::new(CLangStandard::C17);
let tok = pp.expand_builtin_macro("__FUNCTION__", SourceLoc::unknown());
assert!(tok.text.contains("toplevel"));
}
#[test]
fn test_builtin_expansion_pretty_function() {
let mut pp = X86PreprocessorDeep::new(CLangStandard::C17);
let tok = pp.expand_builtin_macro("__PRETTY_FUNCTION__", SourceLoc::unknown());
assert!(tok.text.contains("toplevel"));
}
#[test]
fn test_builtin_expansion_version() {
let mut pp = X86PreprocessorDeep::new(CLangStandard::C17);
let tok = pp.expand_builtin_macro("__VERSION__", SourceLoc::unknown());
assert!(tok.text.contains("Clang"));
}
#[test]
fn test_builtin_expansion_strip() {
let mut pp = X86PreprocessorDeep::new(CLangStandard::C17);
let tok = pp.expand_builtin_macro("__NONEXISTENT__", SourceLoc::unknown());
assert_eq!(tok.kind, TokenKind::Identifier);
}
#[test]
fn test_pragma_pack_complex() {
let mut pp = X86PreprocessorDeep::new(CLangStandard::C17);
pp.process_pragma("pack(4)");
assert_eq!(pp.current_pack, 4);
pp.process_pragma("pack(8)");
assert_eq!(pp.current_pack, 8);
pp.process_pragma("pack()");
assert_eq!(pp.current_pack, 4);
pp.process_pragma("pack()");
assert_eq!(pp.current_pack, 0);
}
#[test]
fn test_pragma_pack_invalid() {
let mut pp = X86PreprocessorDeep::new(CLangStandard::C17);
let saved = pp.current_pack;
pp.process_pragma("pack(3)"); assert_eq!(pp.current_pack, saved);
}
#[test]
fn test_pragma_gcc_diagnostic_ignored() {
let mut pp = X86PreprocessorDeep::new(CLangStandard::C17);
pp.process_pragma("GCC diagnostic push");
pp.process_pragma("GCC diagnostic ignored \"-Wunused\"");
assert_eq!(pp.pragma_diag_stack.len(), 1);
pp.process_pragma("GCC diagnostic pop");
}
#[test]
fn test_pragma_gcc_diagnostic_error() {
let mut pp = X86PreprocessorDeep::new(CLangStandard::C17);
pp.process_pragma("GCC diagnostic push");
pp.process_pragma("GCC diagnostic error \"-Wformat\"");
pp.process_pragma("GCC diagnostic pop");
}
#[test]
fn test_has_include_eval() {
let mut pp = X86PreprocessorDeep::new(CLangStandard::C17);
let tokens = vec![
PPToken::new(
TokenKind::Identifier,
"__has_include".into(),
SourceLoc::unknown(),
),
PPToken::new(TokenKind::LParen, "(".into(), SourceLoc::unknown()),
PPToken::new(
TokenKind::Identifier,
"<stdio.h>".into(),
SourceLoc::unknown(),
),
PPToken::new(TokenKind::RParen, ")".into(), SourceLoc::unknown()),
];
let val = pp.evaluate_condition(&tokens);
assert!(val == 0 || val == 1);
}
#[test]
fn test_has_include_next_eval() {
let mut pp = X86PreprocessorDeep::new(CLangStandard::C17);
let tokens = vec![
PPToken::new(
TokenKind::Identifier,
"__has_include_next".into(),
SourceLoc::unknown(),
),
PPToken::new(TokenKind::LParen, "(".into(), SourceLoc::unknown()),
PPToken::new(
TokenKind::Identifier,
"<stddef.h>".into(),
SourceLoc::unknown(),
),
PPToken::new(TokenKind::RParen, ")".into(), SourceLoc::unknown()),
];
let val = pp.evaluate_condition(&tokens);
assert!(val == 0 || val == 1);
}
#[test]
fn test_has_cpp_attribute_eval() {
let mut pp = X86PreprocessorDeep::new(CLangStandard::C17);
let tokens = vec![
PPToken::new(
TokenKind::Identifier,
"__has_cpp_attribute".into(),
SourceLoc::unknown(),
),
PPToken::new(TokenKind::LParen, "(".into(), SourceLoc::unknown()),
PPToken::new(
TokenKind::Identifier,
"deprecated".into(),
SourceLoc::unknown(),
),
PPToken::new(TokenKind::RParen, ")".into(), SourceLoc::unknown()),
];
let val = pp.evaluate_condition(&tokens);
assert_eq!(val, 201309);
}
#[test]
fn test_has_c_attribute_eval() {
let mut pp = X86PreprocessorDeep::new(CLangStandard::C17);
let tokens = vec![
PPToken::new(
TokenKind::Identifier,
"__has_c_attribute".into(),
SourceLoc::unknown(),
),
PPToken::new(TokenKind::LParen, "(".into(), SourceLoc::unknown()),
PPToken::new(
TokenKind::Identifier,
"deprecated".into(),
SourceLoc::unknown(),
),
PPToken::new(TokenKind::RParen, ")".into(), SourceLoc::unknown()),
];
let val = pp.evaluate_condition(&tokens);
assert_eq!(val, 202311);
}
#[test]
fn test_has_declspec_attribute_eval() {
let mut pp = X86PreprocessorDeep::new(CLangStandard::C17);
let tokens = vec![
PPToken::new(
TokenKind::Identifier,
"__has_declspec_attribute".into(),
SourceLoc::unknown(),
),
PPToken::new(TokenKind::LParen, "(".into(), SourceLoc::unknown()),
PPToken::new(
TokenKind::Identifier,
"dllexport".into(),
SourceLoc::unknown(),
),
PPToken::new(TokenKind::RParen, ")".into(), SourceLoc::unknown()),
];
let val = pp.evaluate_condition(&tokens);
assert_eq!(val, 1);
}
#[test]
fn test_preprocess_empty_source() {
let mut pp = X86PreprocessorDeep::new(CLangStandard::C17);
let result = pp.preprocess("", "empty.c");
assert!(result.is_empty());
}
#[test]
fn test_preprocess_only_comments() {
let mut pp = X86PreprocessorDeep::new(CLangStandard::C17);
let result = pp.preprocess("// this is a comment\n", "test.c");
assert!(!result.is_empty() || result.is_empty());
}
#[test]
fn test_preprocess_multiple_directives() {
let mut pp = X86PreprocessorDeep::new(CLangStandard::C17);
let source = "#define A 1\n#define B 2\nint x = A + B;\n";
let result = pp.preprocess(source, "test.c");
assert!(!result.is_empty());
}
#[test]
fn test_preprocess_if_defined() {
let mut pp = X86PreprocessorDeep::new(CLangStandard::C17);
pp.define("FEATURE_X", "1");
let source = "#if defined(FEATURE_X)\nint enabled;\n#else\nint disabled;\n#endif\n";
let _result = pp.preprocess(source, "test.c");
}
#[test]
fn test_preprocess_error_directive() {
let mut pp = X86PreprocessorDeep::new(CLangStandard::C17);
let source = "#error \"Something went wrong\"\n";
pp.preprocess(source, "test.c");
assert!(pp
.errors
.iter()
.any(|e| e.message.contains("Something went wrong")));
}
#[test]
fn test_preprocess_warning_directive() {
let mut pp = X86PreprocessorDeep::new(CLangStandard::C17);
let source = "#warning \"This is deprecated\"\n";
pp.preprocess(source, "test.c");
assert!(pp
.errors
.iter()
.any(|e| e.message.contains("This is deprecated")));
}
#[test]
fn test_pptoken_new_from_token() {
let tok = Token::new(TokenKind::Identifier, "foo".into(), SourceLoc::new(1, 1, 0));
let pp = PPToken::from_token(&tok);
assert_eq!(pp.text, "foo");
assert_eq!(pp.kind, TokenKind::Identifier);
}
#[test]
fn test_pptoken_helpers() {
let ident = PPToken::new(TokenKind::Identifier, "x".into(), SourceLoc::unknown());
assert!(ident.is_ident());
assert!(!ident.is_hashhash());
assert!(!ident.is_placemarker());
let hashhash = PPToken::new(TokenKind::HashHash, "##".into(), SourceLoc::unknown());
assert!(hashhash.is_hashhash());
let pm = PPToken::placemarker();
assert!(pm.is_placemarker());
assert!(!pm.is_ident());
}
#[test]
fn test_preprocess_complex_conditional_chain() {
let mut pp = X86PreprocessorDeep::new(CLangStandard::C17);
pp.define("PLATFORM", "1");
let source = "#if PLATFORM == 1\nint linux_code;\n#elif PLATFORM == 2\nint windows_code;\n#else\nint other_code;\n#endif\n";
let _result = pp.preprocess(source, "test.c");
}
#[test]
fn test_macro_expansion_depth_limit() {
let mut pp = X86PreprocessorDeep::new(CLangStandard::C17);
pp.max_expansion_depth = 10;
assert_eq!(pp.max_expansion_depth, 10);
}
#[test]
fn test_add_standard_includes() {
let mut pp = X86PreprocessorDeep::new(CLangStandard::C17);
let before = pp.include_paths.len();
pp.add_standard_includes();
assert!(pp.include_paths.len() > before);
}
#[test]
fn test_preprocess_with_line_directive() {
let mut pp = X86PreprocessorDeep::new(CLangStandard::C17);
let source = "#line 100 \"newfile.c\"\nint x;\n";
let _result = pp.preprocess(source, "original.c");
}
#[test]
fn test_has_feature_explicit_check() {
let mut pp = X86PreprocessorDeep::new(CLangStandard::C17);
let tokens = vec![
PPToken::new(
TokenKind::Identifier,
"__has_feature".into(),
SourceLoc::unknown(),
),
PPToken::new(TokenKind::LParen, "(".into(), SourceLoc::unknown()),
PPToken::new(
TokenKind::Identifier,
"c_static_assert".into(),
SourceLoc::unknown(),
),
PPToken::new(TokenKind::RParen, ")".into(), SourceLoc::unknown()),
];
let val = pp.evaluate_condition(&tokens);
assert_eq!(val, 1);
}
#[test]
fn test_has_attribute_explicit_check() {
let mut pp = X86PreprocessorDeep::new(CLangStandard::C17);
let tokens = vec![
PPToken::new(
TokenKind::Identifier,
"__has_attribute".into(),
SourceLoc::unknown(),
),
PPToken::new(TokenKind::LParen, "(".into(), SourceLoc::unknown()),
PPToken::new(
TokenKind::Identifier,
"deprecated".into(),
SourceLoc::unknown(),
),
PPToken::new(TokenKind::RParen, ")".into(), SourceLoc::unknown()),
];
let val = pp.evaluate_condition(&tokens);
assert_eq!(val, 1);
}
#[test]
fn test_preprocessor_cpp_mode_cplusplus() {
let pp = X86PreprocessorDeep::new_cpp(CLangStandard::C17);
let tok = pp.expand_builtin_macro("__cplusplus", SourceLoc::unknown());
assert_eq!(tok.text, "201703L");
}
#[test]
fn test_preprocessor_gnu_mode_macros() {
let pp = X86PreprocessorDeep::new_gnu(CLangStandard::C17);
let tok = pp.expand_builtin_macro("__GNUC__", SourceLoc::unknown());
assert_eq!(tok.text, "4");
}
#[test]
fn test_preprocessor_clang_macros() {
let mut pp = X86PreprocessorDeep::new(CLangStandard::C17);
let tok = pp.expand_builtin_macro("__clang__", SourceLoc::unknown());
assert_eq!(tok.text, "1");
}
#[test]
fn test_eval_empty_expression() {
let mut pp = X86PreprocessorDeep::new(CLangStandard::C17);
assert_eq!(pp.evaluate_condition(&[]), 0);
}
#[test]
fn test_macro_definition_predefined_flag() {
let md = MacroDefinition::object_like("__STDC__", vec![]);
assert!(!md.is_predefined);
let mut md2 = md.clone();
md2.is_predefined = true;
assert!(md2.is_predefined);
}
#[test]
fn test_macro_definition_builtin_flag() {
let md = MacroDefinition::object_like("__LINE__", vec![]);
assert!(!md.is_builtin);
let mut md2 = md.clone();
md2.is_builtin = true;
assert!(md2.is_builtin);
}
#[test]
fn test_pptoken_partial_eq() {
let t1 = PPToken::new(TokenKind::Identifier, "foo".into(), SourceLoc::unknown());
let t2 = PPToken::new(TokenKind::Identifier, "foo".into(), SourceLoc::unknown());
assert_eq!(t1, t2);
}
#[test]
fn test_pptoken_not_equal() {
let t1 = PPToken::new(TokenKind::Identifier, "foo".into(), SourceLoc::unknown());
let t2 = PPToken::new(TokenKind::Identifier, "bar".into(), SourceLoc::unknown());
assert_ne!(t1, t2);
}
#[test]
fn test_include_path_construction() {
let user = IncludeSearchPath::user("/custom/include");
assert!(!user.is_system);
assert_eq!(user.path, PathBuf::from("/custom/include"));
let sys = IncludeSearchPath::system("/usr/include");
assert!(sys.is_system);
}
#[test]
fn test_preprocess_error_formatting() {
let err = PreprocessError::new("test error", SourceLoc::new(5, 3, 100));
let display = format!("{}", err);
assert!(display.contains("5:3"));
assert!(display.contains("test error"));
}
#[test]
fn test_preprocess_error_fatal() {
let err = PreprocessError::fatal("fatal", SourceLoc::unknown());
assert!(err.is_fatal);
assert!(!PreprocessError::new("non-fatal", SourceLoc::unknown()).is_fatal);
}
#[test]
fn test_pragma_diag_state_default() {
let state = PragmaDiagnosticState::default();
assert!(state.ignored_warnings.is_empty());
assert!(state.warning_as_error.is_empty());
}
#[test]
fn test_guard_detector_pragma_once() {
let mut detector = IncludeGuardDetector::default();
detector.analyse_line("#pragma once");
assert!(detector.pragma_once);
}
#[test]
fn test_guard_detector_ifndef_define() {
let mut detector = IncludeGuardDetector::default();
detector.analyse_line("#ifndef GUARD_H");
detector.analyse_line("#define GUARD_H");
assert!(detector.guard_macro.is_some());
}
#[test]
fn test_guard_detector_non_pp_line() {
let mut detector = IncludeGuardDetector::default();
detector.analyse_line("int x = 5;");
assert!(detector.seen_non_pp);
detector.analyse_line("#ifndef GUARD_H");
detector.analyse_line("#define GUARD_H");
assert!(!detector.has_guard());
}
#[test]
fn test_eval_div_by_zero() {
let mut pp = X86PreprocessorDeep::new(CLangStandard::C17);
let tokens = vec![
PPToken::new(TokenKind::NumericLiteral, "42".into(), SourceLoc::unknown()),
PPToken::new(TokenKind::Slash, "/".into(), SourceLoc::unknown()),
PPToken::new(TokenKind::NumericLiteral, "0".into(), SourceLoc::unknown()),
];
let val = pp.evaluate_condition(&tokens);
assert_eq!(val, 0);
}
#[test]
fn test_eval_mod_by_zero() {
let mut pp = X86PreprocessorDeep::new(CLangStandard::C17);
let tokens = vec![
PPToken::new(TokenKind::NumericLiteral, "42".into(), SourceLoc::unknown()),
PPToken::new(TokenKind::Percent, "%".into(), SourceLoc::unknown()),
PPToken::new(TokenKind::NumericLiteral, "0".into(), SourceLoc::unknown()),
];
let val = pp.evaluate_condition(&tokens);
assert_eq!(val, 0);
}
#[test]
fn test_eval_overflow_wrapping() {
let mut pp = X86PreprocessorDeep::new(CLangStandard::C17);
let tokens = vec![
PPToken::new(TokenKind::NumericLiteral, "1".into(), SourceLoc::unknown()),
PPToken::new(TokenKind::LessLess, "<<".into(), SourceLoc::unknown()),
PPToken::new(TokenKind::NumericLiteral, "63".into(), SourceLoc::unknown()),
];
let val = pp.evaluate_condition(&tokens);
assert_eq!(val, i64::MIN);
}
#[test]
fn test_platform_macros_present() {
let mut pp = X86PreprocessorDeep::new(CLangStandard::C17);
assert!(pp.is_defined("__x86_64__"));
assert!(pp.is_defined("__linux__"));
assert!(pp.is_defined("__ELF__"));
assert!(pp.is_defined("__LP64__"));
}
#[test]
fn test_strict_ansi_in_gnu_mode() {
let pp = X86PreprocessorDeep::new_gnu(CLangStandard::C17);
assert!(pp.is_defined("__STRICT_ANSI__"));
}
#[test]
fn test_stdc_version_c99() {
let mut pp = X86PreprocessorDeep::new(CLangStandard::C99);
let tok = pp.expand_builtin_macro("__STDC_VERSION__", SourceLoc::unknown());
assert_eq!(tok.text, "199901L");
}
#[test]
fn test_stdc_version_c11() {
let mut pp = X86PreprocessorDeep::new(CLangStandard::C11);
let tok = pp.expand_builtin_macro("__STDC_VERSION__", SourceLoc::unknown());
assert_eq!(tok.text, "201112L");
}
#[test]
fn test_stdc_version_c17() {
let mut pp = X86PreprocessorDeep::new(CLangStandard::C17);
let tok = pp.expand_builtin_macro("__STDC_VERSION__", SourceLoc::unknown());
assert_eq!(tok.text, "201710L");
}
#[test]
fn test_stdc_version_c23() {
let mut pp = X86PreprocessorDeep::new(CLangStandard::C23);
let tok = pp.expand_builtin_macro("__STDC_VERSION__", SourceLoc::unknown());
assert_eq!(tok.text, "202311L");
}
#[test]
fn test_clang_version_macros() {
let mut pp = X86PreprocessorDeep::new(CLangStandard::C17);
assert!(pp.is_defined("__clang_major__"));
assert!(pp.is_defined("__clang_minor__"));
assert!(pp.is_defined("__clang_patchlevel__"));
assert!(pp.is_defined("__clang_version__"));
}
#[test]
fn test_predefined_macros_count() {
let mut pp = X86PreprocessorDeep::new(CLangStandard::C17);
assert!(pp.defines.len() > 20);
}
#[test]
fn test_preprocess_advance_counter() {
let mut pp = X86PreprocessorDeep::new(CLangStandard::C17);
pp.counter = 0;
let t1 = pp.expand_builtin_macro("__COUNTER__", SourceLoc::unknown());
let t2 = pp.expand_builtin_macro("__COUNTER__", SourceLoc::unknown());
let t3 = pp.expand_builtin_macro("__COUNTER__", SourceLoc::unknown());
assert_eq!(t1.text, "0");
assert_eq!(t2.text, "1");
assert_eq!(t3.text, "2");
}
#[test]
fn test_eval_char_literal() {
let mut pp = X86PreprocessorDeep::new(CLangStandard::C17);
let tokens = vec![PPToken::new(
TokenKind::CharLiteral,
"'A'".into(),
SourceLoc::unknown(),
)];
assert_eq!(pp.evaluate_condition(&tokens), 'A' as i64);
}
#[test]
fn test_preprocessor_public_api() {
let mut pp = X86PreprocessorDeep::new(CLangStandard::C17);
pp.define("TEST", "1");
pp.add_include_path("/test/include", false);
pp.add_standard_includes();
assert!(pp.is_defined("TEST"));
pp.undefine("TEST");
assert!(!pp.is_defined("TEST"));
}
}