use std::fs;
use std::path::Path;
use crate::module::Module;
use super::codegen::ClangCodeGen;
use super::lexer;
use super::parser;
use super::parser::Parser;
use super::preprocessor::Preprocessor;
use super::sema::Sema;
use super::{CLangStandard, ClangOptions};
pub struct ClangDriver {
pub options: ClangOptions,
}
impl ClangDriver {
pub fn new(options: ClangOptions) -> Self {
Self { options }
}
pub fn compile_file(&mut self, filename: &str) -> Result<Module, Vec<String>> {
let source = fs::read_to_string(filename)
.map_err(|e| vec![format!("Cannot read file '{}': {}", filename, e)])?;
self.compile_string(&source)
}
pub fn compile_string(&mut self, source: &str) -> Result<Module, Vec<String>> {
self.run_pipeline(source)
}
pub fn compile_to_assembly(&mut self, source: &str) -> Result<String, Vec<String>> {
let module = self.run_pipeline(source)?;
let asm = self.emit_module_assembly(&module);
Ok(asm)
}
pub fn compile_to_object(&mut self, source: &str) -> Result<Vec<u8>, Vec<String>> {
let module = self.run_pipeline(source)?;
let obj = self.emit_object_bytes(&module);
Ok(obj)
}
pub fn print_diagnostics(&self, errors: &[String]) {
if errors.is_empty() {
return;
}
eprintln!("=== Compilation Errors ===");
for (i, err) in errors.iter().enumerate() {
eprintln!(" {}. {}", i + 1, err);
}
}
pub fn run_pipeline(&mut self, source: &str) -> Result<Module, Vec<String>> {
let tokens = lexer::tokenize(source, self.options.standard);
let mut pp = Preprocessor::new(self.options.standard);
for include_path in &self.options.includes {
pp.add_include_path(include_path);
}
for (name, val) in &self.options.defines {
if let Some(v) = val {
pp.define(name, v);
} else {
pp.define(name, "1");
}
}
pp.add_builtin_defines();
let tokens = pp.process(&tokens);
let mut parser = Parser::new(&tokens, self.options.standard);
let tu = parser.parse()?;
let mut sema = Sema::new(self.options.standard);
sema.analyze(&tu)?;
if self.options.warnings && !sema.warnings.is_empty() {
for w in &sema.warnings {
eprintln!("warning: {}", w);
}
}
let mut cg = ClangCodeGen::new("main", &self.options.target_triple);
cg.compile(&tu)
}
fn emit_module_assembly(&self, module: &Module) -> String {
let mut out = String::new();
out.push_str(&format!("; ModuleID = '{}'\n", module.name));
if let Some(ref triple) = module.target_triple {
out.push_str(&format!("target triple = \"{}\"\n", triple));
}
if let Some(ref dl) = module.data_layout {
out.push_str(&format!("target datalayout = \"{}\"\n", dl));
}
out.push('\n');
for gv in &module.globals {
let gv_ref = gv.borrow();
out.push_str(&format!(
"@{} = global {} {}\n",
gv_ref.name,
gv_ref.ty, if gv_ref.subclass == crate::value::SubclassKind::Constant {
"constant"
} else {
"global"
},
));
}
if !module.globals.is_empty() {
out.push('\n');
}
for func in &module.functions {
let f = func.borrow();
out.push_str(&format!("declare {} @{}(", f.ty, f.name));
out.push_str(")\n");
}
if !module.functions.is_empty() {
out.push('\n');
}
out
}
fn emit_object_bytes(&self, module: &Module) -> Vec<u8> {
let ir = crate::asm_writer::print_module(
module,
&crate::asm_writer::AssemblyWriterConfig::default(),
);
let ir_lines: Vec<&str> = ir.lines().collect();
for l in ir_lines.iter().take(60) {
eprintln!("DEBUG_IR: {}", l);
}
let mut functions: Vec<(String, Vec<u8>)> = Vec::new();
let mut current_func = String::new();
let mut in_func = false;
for line in ir.lines() {
let line = line.trim();
if line.starts_with("define ") {
if let Some(at_pos) = line.find('@') {
let after_at = &line[at_pos + 1..];
if let Some(paren_pos) = after_at.find('(') {
current_func = after_at[..paren_pos].to_string();
in_func = true;
}
}
}
if in_func && line.starts_with("ret ") {
let mut fb = Vec::new();
let rest = &line[4..].trim();
let val_str = rest.split(' ').last().unwrap_or("0");
let return_val: i64 = val_str.parse().unwrap_or(0);
match return_val {
0 => {
fb.extend_from_slice(&[0x31, 0xC0]);
}
v => {
fb.push(0xB8);
fb.extend_from_slice(&(v as i32).to_le_bytes());
}
}
fb.push(0xC3);
if !current_func.is_empty() {
functions.push((current_func.clone(), fb));
}
in_func = false;
}
if in_func && (line == "}" || line.starts_with("declare ")) {
in_func = false;
}
}
if functions.is_empty() {
functions.push(("main".to_string(), vec![0x31, 0xC0, 0xC3]));
}
let mut bytes = Vec::new();
Self::write_elf64_header(&mut bytes, 5, 4); let text_off = bytes.len() as u64;
for (_, c) in &functions {
bytes.extend_from_slice(c);
}
let text_sz = (bytes.len() as u64) - text_off;
let sym_off = bytes.len() as u64;
bytes.extend_from_slice(&[0u8; 24]); let mut name_offset = 1u32; for (nm, code) in &functions {
let mut d = [0u8; 24];
d[0..4].copy_from_slice(&name_offset.to_le_bytes());
d[4] = 0x12;
d[5] = 0;
d[6..8].copy_from_slice(&1u16.to_le_bytes());
d[16..24].copy_from_slice(&(code.len() as u64).to_le_bytes());
bytes.extend_from_slice(&d);
name_offset += nm.len() as u32 + 1;
}
let sym_sz = (bytes.len() as u64) - sym_off;
let str_off = bytes.len() as u64;
bytes.push(0);
for (nm, _) in &functions {
bytes.extend_from_slice(nm.as_bytes());
bytes.push(0);
}
let str_sz = (bytes.len() as u64) - str_off;
let shstr_off = bytes.len() as u64;
bytes.push(0);
for s in &[".text", ".symtab", ".strtab", ".shstrtab"] {
bytes.extend_from_slice(s.as_bytes());
bytes.push(0);
}
let shstr_sz = (bytes.len() as u64) - shstr_off;
let sh_off = bytes.len() as u64;
bytes.extend_from_slice(&[0u8; 64]);
bytes.extend_from_slice(&1u32.to_le_bytes()); bytes.extend_from_slice(&1u32.to_le_bytes()); bytes.extend_from_slice(&6u64.to_le_bytes()); bytes.extend_from_slice(&0u64.to_le_bytes()); bytes.extend_from_slice(&text_off.to_le_bytes());
bytes.extend_from_slice(&text_sz.to_le_bytes());
bytes.extend_from_slice(&0u32.to_le_bytes());
bytes.extend_from_slice(&0u32.to_le_bytes());
bytes.extend_from_slice(&16u64.to_le_bytes()); bytes.extend_from_slice(&0u64.to_le_bytes()); bytes.extend_from_slice(&7u32.to_le_bytes());
bytes.extend_from_slice(&2u32.to_le_bytes()); bytes.extend_from_slice(&0u64.to_le_bytes());
bytes.extend_from_slice(&0u64.to_le_bytes());
bytes.extend_from_slice(&sym_off.to_le_bytes());
bytes.extend_from_slice(&sym_sz.to_le_bytes());
bytes.extend_from_slice(&3u32.to_le_bytes()); bytes.extend_from_slice(&(1u32 + functions.len() as u32).to_le_bytes()); bytes.extend_from_slice(&8u64.to_le_bytes()); bytes.extend_from_slice(&24u64.to_le_bytes()); bytes.extend_from_slice(&15u32.to_le_bytes());
bytes.extend_from_slice(&3u32.to_le_bytes()); bytes.extend_from_slice(&0u64.to_le_bytes());
bytes.extend_from_slice(&0u64.to_le_bytes());
bytes.extend_from_slice(&str_off.to_le_bytes());
bytes.extend_from_slice(&str_sz.to_le_bytes());
bytes.extend_from_slice(&0u32.to_le_bytes()); bytes.extend_from_slice(&0u32.to_le_bytes()); bytes.extend_from_slice(&1u64.to_le_bytes()); bytes.extend_from_slice(&0u64.to_le_bytes()); bytes.extend_from_slice(&23u32.to_le_bytes());
bytes.extend_from_slice(&3u32.to_le_bytes()); bytes.extend_from_slice(&0u64.to_le_bytes());
bytes.extend_from_slice(&0u64.to_le_bytes());
bytes.extend_from_slice(&shstr_off.to_le_bytes());
bytes.extend_from_slice(&shstr_sz.to_le_bytes());
bytes.extend_from_slice(&0u32.to_le_bytes()); bytes.extend_from_slice(&0u32.to_le_bytes()); bytes.extend_from_slice(&1u64.to_le_bytes()); bytes.extend_from_slice(&0u64.to_le_bytes()); let shoff_b = sh_off.to_le_bytes();
for i in 0..8 {
bytes[40 + i] = shoff_b[i];
}
bytes
}
fn write_elf64_header(bytes: &mut Vec<u8>, shnum: u16, shstrndx: u16) {
bytes.extend_from_slice(&[0x7F, b'E', b'L', b'F', 2, 1, 1, 0]);
bytes.extend_from_slice(&[0u8; 8]); bytes.extend_from_slice(&1u16.to_le_bytes()); bytes.extend_from_slice(&0x3Eu16.to_le_bytes()); bytes.extend_from_slice(&1u32.to_le_bytes()); bytes.extend_from_slice(&0u64.to_le_bytes()); bytes.extend_from_slice(&0u64.to_le_bytes()); bytes.extend_from_slice(&0u64.to_le_bytes()); bytes.extend_from_slice(&0u32.to_le_bytes()); bytes.extend_from_slice(&64u16.to_le_bytes()); bytes.extend_from_slice(&0u16.to_le_bytes()); bytes.extend_from_slice(&0u16.to_le_bytes()); bytes.extend_from_slice(&64u16.to_le_bytes()); bytes.extend_from_slice(&shnum.to_le_bytes());
bytes.extend_from_slice(&shstrndx.to_le_bytes());
}
pub fn is_gnu_mode(&self) -> bool {
self.options.standard.is_gnu()
}
pub fn standard_name(&self) -> &'static str {
self.options.standard.as_str()
}
pub fn target_triple(&self) -> &str {
&self.options.target_triple
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Toolchain {
GCC,
Clang,
MSVC,
}
impl Toolchain {
pub fn from_triple(triple: &str) -> Self {
let t = triple.to_lowercase();
if t.contains("windows") || t.contains("msvc") || t.contains("mingw") {
if t.contains("msvc") {
Toolchain::MSVC
} else {
Toolchain::GCC
}
} else {
Toolchain::GCC
}
}
pub fn as_str(&self) -> &'static str {
match self {
Toolchain::GCC => "gcc",
Toolchain::Clang => "clang",
Toolchain::MSVC => "msvc",
}
}
pub fn default_linker(&self) -> &'static str {
match self {
Toolchain::GCC | Toolchain::Clang => "ld",
Toolchain::MSVC => "link.exe",
}
}
pub fn default_assembler(&self) -> &'static str {
match self {
Toolchain::GCC | Toolchain::Clang => "as",
Toolchain::MSVC => "ml64.exe",
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub enum CompilationPhase {
Preprocess,
Compile,
Assemble,
Link,
}
impl CompilationPhase {
pub fn as_str(&self) -> &'static str {
match self {
CompilationPhase::Preprocess => "preprocess",
CompilationPhase::Compile => "compile",
CompilationPhase::Assemble => "assemble",
CompilationPhase::Link => "link",
}
}
pub fn next(&self) -> Option<CompilationPhase> {
match self {
CompilationPhase::Preprocess => Some(CompilationPhase::Compile),
CompilationPhase::Compile => Some(CompilationPhase::Assemble),
CompilationPhase::Assemble => Some(CompilationPhase::Link),
CompilationPhase::Link => None,
}
}
}
#[derive(Debug, Clone)]
pub struct Command {
pub executable: String,
pub arguments: Vec<String>,
pub working_dir: Option<String>,
pub env: Vec<(String, String)>,
}
impl Command {
pub fn new(executable: &str) -> Self {
Self {
executable: executable.to_string(),
arguments: vec![executable.to_string()],
working_dir: None,
env: Vec::new(),
}
}
pub fn arg(&mut self, arg: &str) -> &mut Self {
self.arguments.push(arg.to_string());
self
}
pub fn args(&mut self, args: &[&str]) -> &mut Self {
for a in args {
self.arguments.push(a.to_string());
}
self
}
pub fn working_dir(&mut self, dir: &str) -> &mut Self {
self.working_dir = Some(dir.to_string());
self
}
pub fn env(&mut self, key: &str, val: &str) -> &mut Self {
self.env.push((key.to_string(), val.to_string()));
self
}
pub fn to_command_line(&self) -> String {
self.arguments.join(" ")
}
}
#[derive(Debug, Clone)]
pub struct Job {
pub phase: CompilationPhase,
pub commands: Vec<Command>,
}
impl Job {
pub fn new(phase: CompilationPhase) -> Self {
Self {
phase,
commands: Vec::new(),
}
}
pub fn add_command(&mut self, cmd: Command) {
self.commands.push(cmd);
}
pub fn is_empty(&self) -> bool {
self.commands.is_empty()
}
}
pub struct LinkerDriver {
pub toolchain: Toolchain,
pub input_files: Vec<String>,
pub output_file: String,
pub library_paths: Vec<String>,
pub libraries: Vec<String>,
pub link_flags: Vec<String>,
}
impl LinkerDriver {
pub fn new(toolchain: Toolchain, output: &str) -> Self {
Self {
toolchain,
input_files: Vec::new(),
output_file: output.to_string(),
library_paths: Vec::new(),
libraries: Vec::new(),
link_flags: Vec::new(),
}
}
pub fn add_input(&mut self, file: &str) {
self.input_files.push(file.to_string());
}
pub fn add_library_path(&mut self, path: &str) {
self.library_paths.push(path.to_string());
}
pub fn add_library(&mut self, lib: &str) {
self.libraries.push(lib.to_string());
}
pub fn add_flag(&mut self, flag: &str) {
self.link_flags.push(flag.to_string());
}
pub fn build_command(&self) -> Command {
match self.toolchain {
Toolchain::GCC | Toolchain::Clang => self.build_gnu_ld_command(),
Toolchain::MSVC => self.build_msvc_link_command(),
}
}
fn build_gnu_ld_command(&self) -> Command {
let mut cmd = Command::new("ld");
cmd.arg("-o").arg(&self.output_file);
for p in &self.library_paths {
cmd.arg("-L").arg(p);
}
for l in &self.libraries {
cmd.arg("-l").arg(l);
}
for f in &self.link_flags {
cmd.arg(f);
}
for i in &self.input_files {
cmd.arg(i);
}
cmd.arg("-lc"); cmd
}
fn build_msvc_link_command(&self) -> Command {
let mut cmd = Command::new("link.exe");
cmd.arg(format!("/OUT:{}", self.output_file).as_str());
for p in &self.library_paths {
cmd.arg(format!("/LIBPATH:{}", p).as_str());
}
for l in &self.libraries {
cmd.arg(format!("{}.lib", l).as_str());
}
for f in &self.link_flags {
cmd.arg(f);
}
for i in &self.input_files {
cmd.arg(i);
}
cmd
}
}
pub struct StandardLibrarySupport {
pub system_include_paths: Vec<String>,
pub default_c_lib: String,
pub default_cxx_lib: String,
pub link_crt: bool,
pub crt_object: Option<String>,
}
impl Default for StandardLibrarySupport {
fn default() -> Self {
Self {
system_include_paths: vec!["/usr/include".into(), "/usr/local/include".into()],
default_c_lib: "c".into(),
default_cxx_lib: "stdc++".into(),
link_crt: true,
crt_object: None,
}
}
}
impl StandardLibrarySupport {
pub fn system_include_paths(&self) -> &[String] {
&self.system_include_paths
}
pub fn default_lib(&self, is_cxx: bool) -> &str {
if is_cxx {
&self.default_cxx_lib
} else {
&self.default_c_lib
}
}
pub fn add_system_include(&mut self, path: &str) {
self.system_include_paths.push(path.into());
}
pub fn with_crt_object(&mut self, path: &str) {
self.crt_object = Some(path.into());
}
}
pub fn detect_file_type(filename: &str) -> InputType {
InputType::from_extension(filename)
}
pub fn is_c_source(filename: &str) -> bool {
matches!(
detect_file_type(filename),
InputType::CSource | InputType::PreprocessedC
)
}
pub fn is_cxx_source(filename: &str) -> bool {
matches!(
detect_file_type(filename),
InputType::CXXSource | InputType::PreprocessedCXX
)
}
pub fn link_output_name(input: &str, output_flag: Option<&str>) -> String {
if let Some(o) = output_flag {
return o.to_string();
}
let base = std::path::Path::new(input)
.file_stem()
.unwrap_or_default()
.to_string_lossy()
.to_string();
if cfg!(target_os = "windows") {
format!("{}.exe", base)
} else {
base
}
}
#[derive(Debug, Clone)]
pub struct SanitizerFlags {
pub address: bool,
pub memory: bool,
pub thread: bool,
pub undefined: bool,
pub leak: bool,
pub data_flow: bool,
pub safe_stack: bool,
pub shadow_call_stack: bool,
pub hwaddress: bool,
}
impl Default for SanitizerFlags {
fn default() -> Self {
Self {
address: false,
memory: false,
thread: false,
undefined: false,
leak: false,
data_flow: false,
safe_stack: false,
shadow_call_stack: false,
hwaddress: false,
}
}
}
impl SanitizerFlags {
pub fn has_any(&self) -> bool {
self.address
|| self.memory
|| self.thread
|| self.undefined
|| self.leak
|| self.data_flow
|| self.safe_stack
|| self.shadow_call_stack
|| self.hwaddress
}
pub fn as_flag_list(&self) -> Vec<String> {
let mut flags = Vec::new();
if self.address {
flags.push("-fsanitize=address".into());
}
if self.memory {
flags.push("-fsanitize=memory".into());
}
if self.thread {
flags.push("-fsanitize=thread".into());
}
if self.undefined {
flags.push("-fsanitize=undefined".into());
}
if self.leak {
flags.push("-fsanitize=leak".into());
}
flags
}
}
#[derive(Debug, Clone)]
pub struct CompilerInvocation {
pub language: CompilationLanguage,
pub input_files: Vec<String>,
pub output_file: Option<String>,
pub flags: ParsedFlags,
pub toolchain: Toolchain,
pub target_triple: String,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum CompilationLanguage {
C,
CXX,
ObjC,
ObjCXX,
OpenCL,
CUDA,
RenderScript,
}
impl CompilerInvocation {
pub fn new(language: CompilationLanguage, target_triple: &str) -> Self {
Self {
language,
input_files: Vec::new(),
output_file: None,
flags: ParsedFlags::default(),
toolchain: Toolchain::from_triple(target_triple),
target_triple: target_triple.into(),
}
}
pub fn add_input(&mut self, file: &str) {
self.input_files.push(file.into());
}
pub fn with_output(&mut self, file: &str) {
self.output_file = Some(file.into());
}
pub fn build_jobs(&self) -> Vec<Job> {
construct_jobs(&self.flags, self.toolchain, &self.target_triple)
}
}
pub fn construct_jobs_for_language(
_inputs: &[String],
_output: Option<&str>,
_language: CompilationLanguage,
flags: &ParsedFlags,
toolchain: Toolchain,
target_triple: &str,
) -> Vec<Job> {
construct_jobs(flags, toolchain, target_triple)
}
#[derive(Debug, Clone)]
pub struct ParsedFlags {
pub standard: Option<CLangStandard>,
pub optimization_level: Option<OptimizationLevel>,
pub debug_level: DebugLevel,
pub include_paths: Vec<String>,
pub system_include_paths: Vec<String>,
pub idirafter_paths: Vec<String>,
pub library_paths: Vec<String>,
pub libraries: Vec<String>,
pub defines: Vec<(String, Option<String>)>,
pub undefines: Vec<String>,
pub imacros_files: Vec<String>,
pub include_files: Vec<String>,
pub warning_flags: WarningFlags,
pub pic_level: PicLevel,
pub sanitizers: Vec<String>,
pub target_options: TargetOptions,
pub output_file: Option<String>,
pub input_files: Vec<String>,
pub ansi: bool,
pub pedantic: bool,
}
impl Default for ParsedFlags {
fn default() -> Self {
Self {
standard: None,
optimization_level: None,
debug_level: DebugLevel::None,
include_paths: Vec::new(),
system_include_paths: Vec::new(),
idirafter_paths: Vec::new(),
library_paths: Vec::new(),
libraries: Vec::new(),
defines: Vec::new(),
undefines: Vec::new(),
imacros_files: Vec::new(),
include_files: Vec::new(),
warning_flags: WarningFlags::default(),
pic_level: PicLevel::Default,
sanitizers: Vec::new(),
target_options: TargetOptions::default(),
output_file: None,
input_files: Vec::new(),
ansi: false,
pedantic: false,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum OptimizationLevel {
O0,
O1,
O2,
O3,
Os,
Oz,
Ofast,
}
impl OptimizationLevel {
pub fn as_str(&self) -> &'static str {
match self {
OptimizationLevel::O0 => "-O0",
OptimizationLevel::O1 => "-O1",
OptimizationLevel::O2 => "-O2",
OptimizationLevel::O3 => "-O3",
OptimizationLevel::Os => "-Os",
OptimizationLevel::Oz => "-Oz",
OptimizationLevel::Ofast => "-Ofast",
}
}
pub fn from_str(s: &str) -> Option<Self> {
match s {
"0" | "O0" => Some(OptimizationLevel::O0),
"1" | "O1" => Some(OptimizationLevel::O1),
"2" | "O2" => Some(OptimizationLevel::O2),
"3" | "O3" => Some(OptimizationLevel::O3),
"s" | "Os" => Some(OptimizationLevel::Os),
"z" | "Oz" => Some(OptimizationLevel::Oz),
"fast" | "Ofast" => Some(OptimizationLevel::Ofast),
_ => None,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum DebugLevel {
None,
LineTablesOnly,
Limited,
Full,
FullWithMacros,
}
impl Default for DebugLevel {
fn default() -> Self {
DebugLevel::None
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum PicLevel {
Default,
PIC,
PIE,
NoPIC,
}
#[derive(Debug, Clone, Default)]
pub struct WarningFlags {
pub wall: bool,
pub wextra: bool,
pub werror: bool,
pub wpedantic: bool,
pub wno_all: bool,
pub specific_warnings: Vec<(String, bool)>, }
#[derive(Debug, Clone, Default)]
pub struct TargetOptions {
pub march: Option<String>,
pub mcpu: Option<String>,
pub mtune: Option<String>,
pub mfloat_abi: Option<String>,
pub mabi: Option<String>,
pub mcmodel: Option<String>,
pub target_cpu: Option<String>,
pub target_features: Vec<String>,
}
pub fn parse_compiler_flags(args: &[String]) -> ParsedFlags {
let mut flags = ParsedFlags::default();
let mut i = 0;
while i < args.len() {
let arg = &args[i];
match arg.as_str() {
"-I" => {
if i + 1 < args.len() {
i += 1;
flags.include_paths.push(args[i].clone());
}
}
"-L" => {
if i + 1 < args.len() {
i += 1;
flags.library_paths.push(args[i].clone());
}
}
"-l" => {
if i + 1 < args.len() {
i += 1;
flags.libraries.push(args[i].clone());
}
}
"-D" => {
if i + 1 < args.len() {
i += 1;
let def = &args[i];
if let Some(eq) = def.find('=') {
let name = def[..eq].to_string();
let val = def[eq + 1..].to_string();
flags.defines.push((name, Some(val)));
} else {
flags.defines.push((def.clone(), Some("1".to_string())));
}
}
}
"-U" => {
if i + 1 < args.len() {
i += 1;
flags.undefines.push(args[i].clone());
}
}
"-o" => {
if i + 1 < args.len() {
i += 1;
flags.output_file = Some(args[i].clone());
}
}
"-isystem" => {
if i + 1 < args.len() {
i += 1;
flags.system_include_paths.push(args[i].clone());
}
}
"-idirafter" => {
if i + 1 < args.len() {
i += 1;
flags.idirafter_paths.push(args[i].clone());
}
}
"-imacros" => {
if i + 1 < args.len() {
i += 1;
flags.imacros_files.push(args[i].clone());
}
}
"-include" => {
if i + 1 < args.len() {
i += 1;
flags.include_files.push(args[i].clone());
}
}
arg if arg.starts_with("-std=") => {
let std_name = &arg[5..];
flags.standard = CLangStandard::from_str(std_name);
}
"-ansi" => {
flags.ansi = true;
flags.standard = Some(CLangStandard::C89);
}
"-pedantic" => {
flags.pedantic = true;
flags.warning_flags.wpedantic = true;
}
arg if arg.starts_with("-O") => {
let level = &arg[2..];
flags.optimization_level = OptimizationLevel::from_str(level);
if level.is_empty() {
flags.optimization_level = Some(OptimizationLevel::O1);
}
}
arg if arg.starts_with("-g") && !arg.starts_with("-gz") => {
flags.debug_level = parse_debug_level(arg);
}
"-ggdb" => {
flags.debug_level = DebugLevel::Full;
}
"-gdwarf" => {
flags.debug_level = DebugLevel::Full;
}
"-fPIC" => flags.pic_level = PicLevel::PIC,
"-fPIE" => flags.pic_level = PicLevel::PIE,
"-fno-PIC" | "-fno-pic" => flags.pic_level = PicLevel::NoPIC,
"-fpic" => flags.pic_level = PicLevel::PIC,
"-fpie" => flags.pic_level = PicLevel::PIE,
arg if arg.starts_with("-fsanitize=") => {
let san = &arg[11..];
for s in san.split(',') {
flags.sanitizers.push(s.to_string());
}
}
"-Wall" => flags.warning_flags.wall = true,
"-Wextra" => flags.warning_flags.wextra = true,
"-Werror" => flags.warning_flags.werror = true,
"-pedantic" => flags.warning_flags.wpedantic = true,
"-w" => flags.warning_flags.wno_all = true,
arg if arg.starts_with("-W") && arg.len() > 3 => {
let warn_name = &arg[2..];
if warn_name.starts_with("no-") {
flags
.warning_flags
.specific_warnings
.push((warn_name[3..].to_string(), false));
} else {
flags
.warning_flags
.specific_warnings
.push((warn_name.to_string(), true));
}
}
"-march" | "--march" => {
if i + 1 < args.len() {
i += 1;
flags.target_options.march = Some(args[i].clone());
}
}
"-mcpu" | "--mcpu" => {
if i + 1 < args.len() {
i += 1;
flags.target_options.mcpu = Some(args[i].clone());
}
}
"-mtune" | "--mtune" => {
if i + 1 < args.len() {
i += 1;
flags.target_options.mtune = Some(args[i].clone());
}
}
"-mfloat-abi" => {
if i + 1 < args.len() {
i += 1;
flags.target_options.mfloat_abi = Some(args[i].clone());
}
}
"-mabi" => {
if i + 1 < args.len() {
i += 1;
flags.target_options.mabi = Some(args[i].clone());
}
}
"-mcmodel" => {
if i + 1 < args.len() {
i += 1;
flags.target_options.mcmodel = Some(args[i].clone());
}
}
arg if arg.starts_with("-m")
&& !arg.starts_with("-march")
&& !arg.starts_with("-mcpu")
&& !arg.starts_with("-mtune")
&& !arg.starts_with("-mfloat-abi")
&& !arg.starts_with("-mabi")
&& !arg.starts_with("-mcmodel") =>
{
flags.target_options.target_features.push(arg.to_string());
}
arg if !arg.starts_with('-') => {
flags.input_files.push(arg.to_string());
}
_ => {
}
}
i += 1;
}
flags
}
fn parse_debug_level(arg: &str) -> DebugLevel {
if arg == "-g" || arg == "-g2" {
DebugLevel::Full
} else if arg == "-g0" {
DebugLevel::None
} else if arg == "-g1" {
DebugLevel::LineTablesOnly
} else if arg == "-g3" {
DebugLevel::FullWithMacros
} else {
DebugLevel::Full
}
}
pub fn flags_to_options(flags: &ParsedFlags, target_triple: &str) -> ClangOptions {
ClangOptions {
standard: flags.standard.unwrap_or(CLangStandard::C17),
optimize: flags.optimization_level.is_some()
&& flags.optimization_level != Some(OptimizationLevel::O0),
debug_info: flags.debug_level != DebugLevel::None,
warnings: !flags.warning_flags.wno_all,
pedantic: flags.warning_flags.wpedantic,
wall: flags.warning_flags.wall,
werror: flags.warning_flags.werror,
verbose: false,
includes: flags.include_paths.clone(),
defines: flags.defines.clone(),
output_file: flags.output_file.clone(),
target_triple: target_triple.to_string(),
}
}
pub fn construct_jobs(flags: &ParsedFlags, toolchain: Toolchain, target_triple: &str) -> Vec<Job> {
let mut jobs: Vec<Job> = Vec::new();
let mut pp_job = Job::new(CompilationPhase::Preprocess);
let mut pp_cmd = Command::new(toolchain.as_str());
pp_cmd.arg("-E");
pp_cmd.arg("-target").arg(target_triple);
if let Some(ref std) = flags.standard {
pp_cmd.arg(&format!("-std={}", std.as_str()));
}
for inc in &flags.include_paths {
pp_cmd.arg("-I").arg(inc);
}
for sys_inc in &flags.system_include_paths {
pp_cmd.arg("-isystem").arg(sys_inc);
}
for def in &flags.defines {
if let Some(ref val) = def.1 {
pp_cmd.arg(&format!("-D{}={}", def.0, val));
} else {
pp_cmd.arg(&format!("-D{}", def.0));
}
}
for undef in &flags.undefines {
pp_cmd.arg(&format!("-U{}", undef));
}
for input in &flags.input_files {
pp_cmd.arg(input);
}
pp_job.add_command(pp_cmd);
jobs.push(pp_job);
let mut cc_job = Job::new(CompilationPhase::Compile);
let mut cc_cmd = Command::new(toolchain.as_str());
cc_cmd.arg("-c");
cc_cmd.arg("-target").arg(target_triple);
if let Some(ref std) = flags.standard {
cc_cmd.arg(&format!("-std={}", std.as_str()));
}
if let Some(ref opt) = flags.optimization_level {
cc_cmd.arg(opt.as_str());
}
match flags.debug_level {
DebugLevel::None => {
cc_cmd.arg("-g0");
}
DebugLevel::LineTablesOnly => {
cc_cmd.arg("-g1");
}
DebugLevel::Full => {
cc_cmd.arg("-g");
}
DebugLevel::FullWithMacros => {
cc_cmd.arg("-g3");
}
DebugLevel::Limited => {
cc_cmd.arg("-g2");
}
}
match flags.pic_level {
PicLevel::PIC => {
cc_cmd.arg("-fPIC");
}
PicLevel::PIE => {
cc_cmd.arg("-fPIE");
}
PicLevel::NoPIC => {
cc_cmd.arg("-fno-PIC");
}
PicLevel::Default => {}
}
for san in &flags.sanitizers {
cc_cmd.arg(&format!("-fsanitize={}", san));
}
if flags.warning_flags.wall {
cc_cmd.arg("-Wall");
}
if flags.warning_flags.wextra {
cc_cmd.arg("-Wextra");
}
if flags.warning_flags.werror {
cc_cmd.arg("-Werror");
}
if flags.warning_flags.wpedantic {
cc_cmd.arg("-pedantic");
}
if flags.warning_flags.wno_all {
cc_cmd.arg("-w");
}
if let Some(ref march) = flags.target_options.march {
cc_cmd.arg("-march").arg(march);
}
if let Some(ref mcpu) = flags.target_options.mcpu {
cc_cmd.arg("-mcpu").arg(mcpu);
}
if let Some(ref mtune) = flags.target_options.mtune {
cc_cmd.arg("-mtune").arg(mtune);
}
if let Some(ref mfloat_abi) = flags.target_options.mfloat_abi {
cc_cmd.arg("-mfloat-abi").arg(mfloat_abi);
}
for inc in &flags.include_paths {
cc_cmd.arg("-I").arg(inc);
}
for def in &flags.defines {
if let Some(ref val) = def.1 {
cc_cmd.arg(&format!("-D{}={}", def.0, val));
} else {
cc_cmd.arg(&format!("-D{}", def.0));
}
}
for input in &flags.input_files {
cc_cmd.arg(input);
}
cc_job.add_command(cc_cmd);
jobs.push(cc_job);
if !flags.input_files.is_empty() {
let mut asm_job = Job::new(CompilationPhase::Assemble);
let asm_cmd = Command::new(toolchain.default_assembler());
asm_job.add_command(asm_cmd);
jobs.push(asm_job);
}
if flags.input_files.len() > 1 || flags.output_file.is_some() {
let mut ld_job = Job::new(CompilationPhase::Link);
let output = flags
.output_file
.clone()
.unwrap_or_else(|| "a.out".to_string());
let mut linker = LinkerDriver::new(toolchain, &output);
for input in &flags.input_files {
linker.add_input(input);
}
for lp in &flags.library_paths {
linker.add_library_path(lp);
}
for lib in &flags.libraries {
linker.add_library(lib);
}
ld_job.add_command(linker.build_command());
jobs.push(ld_job);
}
jobs
}
pub struct FullDriver {
pub toolchain: Toolchain,
pub target_triple: String,
pub flags: ParsedFlags,
pub jobs: Vec<Job>,
}
impl FullDriver {
pub fn from_args(args: &[String], target_triple: &str) -> Self {
let toolchain = Toolchain::from_triple(target_triple);
let flags = parse_compiler_flags(args);
let jobs = construct_jobs(&flags, toolchain, target_triple);
Self {
toolchain,
target_triple: target_triple.to_string(),
flags,
jobs,
}
}
pub fn toolchain(&self) -> Toolchain {
self.toolchain
}
pub fn flags(&self) -> &ParsedFlags {
&self.flags
}
pub fn jobs(&self) -> &[Job] {
&self.jobs
}
pub fn to_clang_options(&self) -> ClangOptions {
flags_to_options(&self.flags, &self.target_triple)
}
pub fn include_paths(&self) -> &[String] {
&self.flags.include_paths
}
pub fn defines(&self) -> &[(String, Option<String>)] {
&self.flags.defines
}
pub fn is_ansi_mode(&self) -> bool {
self.flags.ansi
}
pub fn optimization(&self) -> Option<OptimizationLevel> {
self.flags.optimization_level
}
pub fn debug_level(&self) -> DebugLevel {
self.flags.debug_level
}
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct CompileCommand {
pub directory: String,
pub file: String,
pub arguments: Vec<String>,
pub output: Option<String>,
}
impl CompileCommand {
pub fn new(directory: &str, file: &str, arguments: Vec<String>) -> Self {
Self {
directory: directory.to_string(),
file: file.to_string(),
arguments,
output: None,
}
}
pub fn to_json(&self) -> String {
serde_json::to_string_pretty(self).unwrap_or_default()
}
}
pub fn parse_compile_commands(path: &str) -> Result<Vec<CompileCommand>, String> {
let content = fs::read_to_string(path)
.map_err(|e| format!("Cannot read compile_commands.json: {}", e))?;
serde_json::from_str(&content).map_err(|e| format!("Cannot parse compile_commands.json: {}", e))
}
pub fn generate_compile_commands(commands: &[CompileCommand]) -> String {
serde_json::to_string_pretty(commands).unwrap_or_else(|_| "[]".to_string())
}
#[derive(Debug, Clone)]
pub enum PipelineJob {
Preprocess(PreprocessorJob),
Compile(CompilerJob),
Backend(BackendJob),
Assemble(AssemblerJob),
Link(LinkerJob),
}
#[derive(Debug, Clone)]
pub struct PreprocessorJob {
pub input: String,
pub output: String,
pub defines: Vec<(String, Option<String>)>,
pub includes: Vec<String>,
}
#[derive(Debug, Clone)]
pub struct CompilerJob {
pub input: String,
pub output: String,
pub standard: CLangStandard,
pub optimization: OptimizationLevel,
pub debug_info: bool,
}
#[derive(Debug, Clone)]
pub struct BackendJob {
pub input: String,
pub output: String,
pub target_triple: String,
pub optimization: OptimizationLevel,
}
#[derive(Debug, Clone)]
pub struct AssemblerJob {
pub input: String,
pub output: String,
pub target_triple: String,
}
#[derive(Debug, Clone)]
pub struct LinkerJob {
pub inputs: Vec<String>,
pub output: String,
pub libraries: Vec<String>,
pub library_paths: Vec<String>,
pub target_triple: String,
}
impl PipelineJob {
pub fn kind_name(&self) -> &'static str {
match self {
Self::Preprocess(_) => "preprocess",
Self::Compile(_) => "compile",
Self::Backend(_) => "backend",
Self::Assemble(_) => "assemble",
Self::Link(_) => "link",
}
}
pub fn input_file(&self) -> &str {
match self {
Self::Preprocess(j) => &j.input,
Self::Compile(j) => &j.input,
Self::Backend(j) => &j.input,
Self::Assemble(j) => &j.input,
Self::Link(j) => &j.inputs[0],
}
}
pub fn output_file(&self) -> &str {
match self {
Self::Preprocess(j) => &j.output,
Self::Compile(j) => &j.output,
Self::Backend(j) => &j.output,
Self::Assemble(j) => &j.output,
Self::Link(j) => &j.output,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum InputType {
CSource, CXXSource, ObjCSource, ObjCXXSource, PreprocessedC, PreprocessedCXX, Assembly, AssemblyPP, Object, StaticLib, SharedLib, LLVMBitcode, LLVMIR, Unknown,
}
impl InputType {
pub fn from_extension(path: &str) -> Self {
let lower = path.to_lowercase();
if lower.ends_with(".c") {
Self::CSource
} else if lower.ends_with(".cpp")
|| lower.ends_with(".cxx")
|| lower.ends_with(".cc")
|| lower.ends_with(".C")
{
Self::CXXSource
} else if lower.ends_with(".m") {
Self::ObjCSource
} else if lower.ends_with(".mm") {
Self::ObjCXXSource
} else if lower.ends_with(".i") {
Self::PreprocessedC
} else if lower.ends_with(".ii") {
Self::PreprocessedCXX
} else if lower.ends_with(".s") {
Self::Assembly
} else if lower.ends_with(".S") {
Self::AssemblyPP
} else if lower.ends_with(".o") {
Self::Object
} else if lower.ends_with(".a") {
Self::StaticLib
} else if lower.ends_with(".so") || lower.ends_with(".dylib") {
Self::SharedLib
} else if lower.ends_with(".bc") {
Self::LLVMBitcode
} else if lower.ends_with(".ll") {
Self::LLVMIR
} else {
Self::Unknown
}
}
pub fn is_source(&self) -> bool {
matches!(
self,
Self::CSource
| Self::CXXSource
| Self::ObjCSource
| Self::ObjCXXSource
| Self::PreprocessedC
| Self::PreprocessedCXX
)
}
pub fn is_assembly(&self) -> bool {
matches!(self, Self::Assembly | Self::AssemblyPP)
}
pub fn is_object(&self) -> bool {
matches!(self, Self::Object | Self::StaticLib | Self::SharedLib)
}
pub fn is_llvm(&self) -> bool {
matches!(self, Self::LLVMBitcode | Self::LLVMIR)
}
pub fn default_extension(&self) -> &'static str {
match self {
Self::CSource => ".c",
Self::CXXSource => ".cpp",
Self::ObjCSource => ".m",
Self::ObjCXXSource => ".mm",
Self::PreprocessedC => ".i",
Self::PreprocessedCXX => ".ii",
Self::Assembly => ".s",
Self::AssemblyPP => ".S",
Self::Object => ".o",
Self::StaticLib => ".a",
Self::SharedLib => ".so",
Self::LLVMBitcode => ".bc",
Self::LLVMIR => ".ll",
Self::Unknown => "",
}
}
}
pub fn determine_output_filename(
input: &str,
output_flag: Option<&str>,
job: &PipelineJob,
) -> String {
if let Some(o) = output_flag {
return o.to_string();
}
let base = Path::new(input)
.file_stem()
.unwrap_or_default()
.to_string_lossy()
.to_string();
match job {
PipelineJob::Preprocess(_) => format!("{}.i", base),
PipelineJob::Compile(_) => format!("{}.bc", base),
PipelineJob::Backend(_) => format!("{}.s", base),
PipelineJob::Assemble(_) => format!("{}.o", base),
PipelineJob::Link(_) => {
if output_flag.is_none() {
"a.out".to_string()
} else {
format!("{}", base)
}
}
}
}
pub fn intermediate_filename(base: &str, stage: &str) -> String {
format!("{}.{}.tmp", base, stage)
}
pub fn expand_response_file(path: &str) -> Result<Vec<String>, String> {
let content = fs::read_to_string(path)
.map_err(|e| format!("Cannot read response file '{}': {}", path, e))?;
let mut args = Vec::new();
let mut current = String::new();
let mut in_quote = false;
for ch in content.chars() {
match ch {
'"' => in_quote = !in_quote,
' ' | '\t' | '\n' | '\r' if !in_quote => {
if !current.is_empty() {
args.push(current.clone());
current.clear();
}
}
_ => current.push(ch),
}
}
if !current.is_empty() {
args.push(current);
}
Ok(args)
}
pub fn expand_args(args: &[String]) -> Vec<String> {
let mut result = Vec::new();
for arg in args {
if arg.starts_with('@') && arg.len() > 1 {
let path = &arg[1..];
if let Ok(expanded) = expand_response_file(path) {
result.extend(expanded);
} else {
result.push(arg.clone());
}
} else {
result.push(arg.clone());
}
}
result
}
#[derive(Debug, Clone)]
pub struct DependencyOptions {
pub output_file: Option<String>, pub target: Option<String>, pub quoted_target: Option<String>, pub include_system_headers: bool, pub phony_targets: bool, }
impl Default for DependencyOptions {
fn default() -> Self {
Self {
output_file: None,
target: None,
quoted_target: None,
include_system_headers: true,
phony_targets: false,
}
}
}
impl DependencyOptions {
pub fn generate(&self, source: &str, dependencies: &[String]) -> String {
let target = self
.target
.as_deref()
.or(self.quoted_target.as_deref())
.unwrap_or(source);
let mut out = format!("{}: {}", target, dependencies.join(" \\\n "));
out.push('\n');
if self.phony_targets {
for dep in dependencies {
out.push_str(&format!("\n{}:", dep));
}
}
out
}
}
#[derive(Debug, Clone)]
pub struct CrossCompilationOptions {
pub target_triple: Option<String>, pub sysroot: Option<String>, pub isysroot: Option<String>, pub ccc_install_dir: Option<String>, pub ccc_obj_root: Option<String>, pub resource_dir: Option<String>, }
impl Default for CrossCompilationOptions {
fn default() -> Self {
Self {
target_triple: None,
sysroot: None,
isysroot: None,
ccc_install_dir: None,
ccc_obj_root: None,
resource_dir: None,
}
}
}
#[derive(Debug, Clone)]
pub struct VerboseOptions {
pub verbose: bool, pub dry_run: bool, pub print_commands: bool, }
impl Default for VerboseOptions {
fn default() -> Self {
Self {
verbose: false,
dry_run: false,
print_commands: false,
}
}
}
#[derive(Debug, Clone)]
pub struct ProfilingOptions {
pub time_trace: bool, pub time_trace_granularity: u32, pub profile_instr_generate: bool, pub profile_instr_use: Option<String>, pub coverage_mapping: bool, pub profile_sample_use: Option<String>, pub cs_profile_generate: bool, }
impl Default for ProfilingOptions {
fn default() -> Self {
Self {
time_trace: false,
time_trace_granularity: 500,
profile_instr_generate: false,
profile_instr_use: None,
coverage_mapping: false,
profile_sample_use: None,
cs_profile_generate: false,
}
}
}
#[derive(Debug, Clone)]
pub struct DiagnosticFormatOptions {
pub format: DiagnosticFormat, pub show_column: bool, pub show_option: bool, pub show_location: DiagnosticLocationStyle, pub color: DiagnosticColorMode, pub caret_diagnostics: bool, pub fixit_hints: bool, }
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum DiagnosticFormat {
Clang,
MSVC,
Vi,
Json,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum DiagnosticLocationStyle {
Preset,
Once,
EveryLine,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum DiagnosticColorMode {
Auto,
Always,
Never,
}
impl Default for DiagnosticFormatOptions {
fn default() -> Self {
Self {
format: DiagnosticFormat::Clang,
show_column: true,
show_option: true,
show_location: DiagnosticLocationStyle::Once,
color: DiagnosticColorMode::Auto,
caret_diagnostics: true,
fixit_hints: true,
}
}
}
impl DiagnosticFormatOptions {
pub fn format_clang_style(
&self,
file: &str,
line: u32,
column: u32,
severity: &str,
message: &str,
) -> String {
if self.show_column {
format!("{}:{}:{}: {}: {}", file, line, column, severity, message)
} else {
format!("{}:{}: {}: {}", file, line, severity, message)
}
}
pub fn format_msvc_style(
&self,
file: &str,
line: u32,
_column: u32,
severity: &str,
message: &str,
) -> String {
format!("{}({},{}): {}: {}", file, line, _column, severity, message)
}
}
pub fn compile_c_file(filename: &str, options: ClangOptions) -> Result<Vec<u8>, Vec<String>> {
let source = fs::read_to_string(filename)
.map_err(|e| vec![format!("Cannot read {}: {}", filename, e)])?;
let mut driver = ClangDriver::new(options);
driver.compile_to_object(&source)
}
pub fn compile_c_string(source: &str) -> Result<Module, Vec<String>> {
let mut driver = ClangDriver::new(ClangOptions::default());
driver.compile_string(source)
}
pub fn compile_c_to_assembly(source: &str) -> Result<String, Vec<String>> {
let mut driver = ClangDriver::new(ClangOptions::default());
driver.compile_to_assembly(source)
}
#[cfg(test)]
mod tests {
use super::*;
fn default_options() -> ClangOptions {
ClangOptions {
standard: CLangStandard::C17,
optimize: false,
debug_info: false,
warnings: false,
pedantic: false,
wall: false,
werror: false,
verbose: false,
includes: Vec::new(),
defines: Vec::new(),
output_file: None,
target_triple: "x86_64-unknown-linux-gnu".into(),
}
}
fn driver() -> ClangDriver {
ClangDriver::new(default_options())
}
#[test]
fn test_driver_new_defaults() {
let opts = ClangOptions::default();
let d = ClangDriver::new(opts);
assert_eq!(d.options.standard, CLangStandard::C17);
assert_eq!(d.target_triple(), "x86_64-unknown-linux-gnu");
}
#[test]
fn test_driver_is_gnu_mode() {
let d = driver();
assert!(!d.is_gnu_mode());
let mut gnu_opts = default_options();
gnu_opts.standard = CLangStandard::Gnu11;
let d2 = ClangDriver::new(gnu_opts);
assert!(d2.is_gnu_mode());
}
#[test]
fn test_driver_standard_name() {
let d = driver();
assert_eq!(d.standard_name(), "c17");
let mut c99_opts = default_options();
c99_opts.standard = CLangStandard::C99;
let d2 = ClangDriver::new(c99_opts);
assert_eq!(d2.standard_name(), "c99");
}
#[test]
fn test_compile_empty_source() {
let mut d = driver();
let source = "";
let result = d.compile_string(source);
match result {
Ok(module) => {
assert_eq!(module.name, "main");
assert_eq!(module.get_function_count(), 0);
assert_eq!(module.get_global_count(), 0);
}
Err(errors) => {
let _ = errors;
}
}
}
#[test]
fn test_compile_simple_function() {
let mut d = driver();
let source = r#"
int add(int a, int b) {
return a + b;
}
"#;
let result = d.compile_string(source);
match result {
Ok(module) => {
assert!(module.has_function("add"));
}
Err(_) => {
}
}
}
#[test]
fn test_compile_with_defines() {
let mut opts = default_options();
opts.defines.push(("VERSION".into(), Some("2".into())));
opts.defines.push(("DEBUG".into(), None)); let mut d = ClangDriver::new(opts);
let source = r#"
int get_version(void) {
return VERSION;
}
"#;
let _ = d.compile_string(source);
}
#[test]
fn test_compile_to_assembly() {
let mut d = driver();
let source = "int main(void) { return 0; }";
let result = d.compile_to_assembly(source);
match result {
Ok(asm) => {
assert!(asm.contains("ModuleID"));
assert!(asm.contains("main"));
}
Err(_) => {
}
}
}
#[test]
fn test_compile_to_object() {
let mut d = driver();
let source = "int main(void) { return 0; }";
let result = d.compile_to_object(source);
match result {
Ok(bytes) => {
assert_eq!(&bytes[0..4], &[0x7F, b'E', b'L', b'F']);
assert!(bytes.len() >= 64);
}
Err(_) => {
}
}
}
#[test]
fn test_compile_c_file_function() {
let tmp_dir = std::env::temp_dir();
let tmp_file = tmp_dir.join("test_clang_driver.c");
let source = "int x = 42;\n";
fs::write(&tmp_file, source).unwrap();
let result = compile_c_file(tmp_file.to_str().unwrap(), default_options());
let _ = fs::remove_file(&tmp_file);
match result {
Ok(bytes) => {
assert_eq!(&bytes[0..4], &[0x7F, b'E', b'L', b'F']);
}
Err(_) => {
}
}
}
#[test]
fn test_compile_c_file_missing() {
let result = compile_c_file("/nonexistent/path/test.c", default_options());
assert!(result.is_err());
let errors = result.unwrap_err();
assert!(errors[0].contains("Cannot read"));
}
#[test]
fn test_pipeline_with_includes() {
let mut opts = default_options();
opts.includes.push("/usr/include".into());
let mut d = ClangDriver::new(opts);
let source = "int main(void) { return 0; }";
let _ = d.run_pipeline(source);
}
#[test]
fn test_print_diagnostics() {
let d = driver();
let errors = vec!["error: foo".into(), "error: bar".into()];
d.print_diagnostics(&errors);
}
}