use std::fs::File;
use std::io::{self, BufWriter, Write};
use std::path::PathBuf;
use std::ops::ControlFlow;
use clap::Parser as ClapParser;
use libperl_macrogen::{
ApidocDict, BlockItem, CompileError, FieldsDict, FileId,
ParseResult, Parser, Preprocessor, RustDeclDict, SexpPrinter,
SourceLocation, TokenKind, TypedSexpPrinter,
Pipeline, PipelineError,
};
#[derive(ClapParser)]
#[command(name = "libperl-macrogen")]
#[command(version, about = "C to Rust macro bindgen tool")]
struct Cli {
input: Option<PathBuf>,
#[arg(short = 'I', long = "include")]
include: Vec<PathBuf>,
#[arg(short = 'D', long = "define")]
define: Vec<String>,
#[arg(short = 'o', long = "output")]
output: Option<PathBuf>,
#[arg(short = 'E')]
preprocess_only: bool,
#[arg(long = "debug-pp")]
debug_pp: bool,
#[arg(long = "auto")]
auto: bool,
#[arg(long = "gcc-format")]
gcc_format: bool,
#[arg(long = "streaming")]
streaming: bool,
#[arg(long = "typed-sexp")]
typed_sexp: bool,
#[arg(long = "sexp")]
sexp: bool,
#[arg(long = "dump-fields-dict")]
dump_fields_dict: bool,
#[arg(long = "dump-macros", value_name = "FILTER")]
dump_macros: Option<Option<String>>,
#[arg(long = "apidoc-to-json")]
apidoc_to_json: bool,
#[arg(long = "compact")]
compact: bool,
#[arg(long = "target-dir")]
target_dir: Option<PathBuf>,
#[arg(long = "parse-rust-bindings")]
parse_rust_bindings: Option<PathBuf>,
#[arg(long = "bindings")]
bindings: Option<PathBuf>,
#[arg(long = "apidoc")]
apidoc: Option<PathBuf>,
#[arg(long = "progress")]
progress: bool,
#[arg(long = "emit-macro-markers")]
emit_macro_markers: bool,
#[arg(long = "macro-comments")]
macro_comments: bool,
#[arg(long = "gen-rust")]
gen_rust: bool,
#[arg(long = "dump-apidoc-after-merge", value_name = "FILTER")]
dump_apidoc_after_merge: Option<Option<String>>,
#[arg(long = "rust-edition", default_value = "2024")]
rust_edition: String,
#[arg(long = "strict-rustfmt")]
strict_rustfmt: bool,
#[arg(long = "debug-type-inference", value_name = "MACROS")]
debug_type_inference: Option<String>,
#[arg(long = "dump-ast-for", value_name = "FUNC")]
dump_ast_for: Option<String>,
#[arg(long = "dump-types-for", value_name = "FUNC")]
dump_types_for: Option<String>,
#[arg(long = "skip-codegen-list", value_name = "FILE")]
skip_codegen_list: Vec<PathBuf>,
#[arg(long = "perl-build-mode", value_name = "MODE", value_parser = parse_perl_build_mode)]
perl_build_mode: Option<libperl_macrogen::perl_config::PerlBuildMode>,
}
fn parse_perl_build_mode(s: &str) -> Result<libperl_macrogen::perl_config::PerlBuildMode, String> {
use libperl_macrogen::perl_config::PerlBuildMode;
match s {
"threaded" | "thr" => Ok(PerlBuildMode::Threaded),
"non-threaded" | "nonthreaded" | "nothr" => Ok(PerlBuildMode::NonThreaded),
"auto" => Err(
"use omission of --perl-build-mode for auto-detection".to_string()
),
other => Err(format!(
"unknown perl build mode: {} (expected 'threaded' or 'non-threaded')", other
)),
}
}
fn main() {
if let Err(e) = run() {
eprintln!("Error: {}", e);
std::process::exit(1);
}
}
fn run() -> Result<(), Box<dyn std::error::Error>> {
let cli = Cli::parse();
if let Some(ref rust_file) = cli.parse_rust_bindings {
return run_parse_rust_bindings(rust_file);
}
if cli.apidoc_to_json {
let input = cli.input.ok_or("Input file (apidoc) is required for --apidoc-to-json")?;
return run_apidoc_to_json(&input, cli.output.as_ref(), cli.compact);
}
let input = cli.input.ok_or("Input file is required")?;
let mut builder = Pipeline::builder(&input);
if cli.auto {
if !cli.include.is_empty() {
return Err("--auto cannot be used with -I options".into());
}
builder = builder.with_auto_perl_config()
.map_err(|e| format!("Failed to get Perl config: {}", e))?;
for (name, value) in parse_defines(&cli.define) {
builder = builder.with_define(name, value);
}
} else {
for path in &cli.include {
builder = builder.with_include(path);
}
for (name, value) in parse_defines(&cli.define) {
builder = builder.with_define(name, value);
}
}
if let Some(ref target_dir) = cli.target_dir {
builder = builder.with_target_dir(target_dir);
}
if cli.debug_pp {
builder = builder.with_debug_pp();
}
if cli.emit_macro_markers {
builder = builder.with_emit_markers();
}
builder = builder.with_codegen_defaults();
if let Some(ref bindings_path) = cli.bindings {
builder = builder.with_bindings(bindings_path);
}
if let Some(ref apidoc_path) = cli.apidoc {
builder = builder.with_apidoc(apidoc_path);
}
for path in &cli.skip_codegen_list {
builder = builder.with_skip_codegen_list(path);
}
if let Some(mode) = cli.perl_build_mode {
builder = builder.with_perl_build_mode(mode);
}
if let Some(ref filter_opt) = cli.dump_apidoc_after_merge {
let filter = filter_opt.as_deref().unwrap_or("").to_string();
builder = builder.with_dump_apidoc(filter);
}
if let Some(ref macros) = cli.debug_type_inference {
let macro_list: Vec<String> = macros
.split(',')
.map(|s| s.trim().to_string())
.filter(|s| !s.is_empty())
.collect();
builder = builder.with_debug_type_inference(macro_list);
}
builder = builder.with_rust_edition(&cli.rust_edition);
if cli.strict_rustfmt {
builder = builder.with_strict_rustfmt();
}
if cli.macro_comments {
builder = builder.with_macro_comments();
}
if let Some(ref name) = cli.dump_ast_for {
builder = builder.with_dump_ast_for(name);
}
if let Some(ref name) = cli.dump_types_for {
builder = builder.with_dump_types_for(name);
}
let mut preprocessed = builder.build()?.preprocess()
.map_err(|e| format_pipeline_error(&e))?;
if cli.preprocess_only {
output_preprocessed(preprocessed.preprocessor_mut(), cli.output.as_ref(), cli.gcc_format)?;
} else if cli.streaming {
run_streaming(preprocessed.preprocessor_mut())?;
} else if cli.typed_sexp {
run_typed_sexp(preprocessed.preprocessor_mut(), cli.output.as_ref())?;
} else if cli.dump_fields_dict {
run_dump_fields_dict(preprocessed.preprocessor_mut(), cli.target_dir.as_ref())?;
} else if let Some(ref filter_opt) = cli.dump_macros {
let filter = filter_opt.as_deref().unwrap_or("");
let pp = preprocessed.preprocessor_mut();
loop {
match pp.next_token() {
Ok(token) if matches!(token.kind, libperl_macrogen::TokenKind::Eof) => break,
Ok(_) => continue,
Err(e) => return Err(format_error(&e, pp).into()),
}
}
pp.macros().dump_filtered(filter, pp.interner());
} else if cli.sexp {
let pp = preprocessed.preprocessor_mut();
let mut parser = match Parser::new(pp) {
Ok(p) => p,
Err(e) => return Err(format_error(&e, pp).into()),
};
let tu = match parser.parse() {
Ok(tu) => tu,
Err(e) => return Err(format_error(&e, pp).into()),
};
if let Some(output_path) = cli.output {
let file = File::create(&output_path)?;
let mut writer = BufWriter::new(file);
let mut printer = SexpPrinter::new(&mut writer, pp.interner());
printer.print_translation_unit(&tu)?;
writer.flush()?;
} else {
let stdout = io::stdout();
let mut handle = stdout.lock();
let mut printer = SexpPrinter::new(&mut handle, pp.interner());
printer.print_translation_unit(&tu)?;
handle.flush()?;
}
} else if cli.gen_rust {
run_gen_rust_pipeline(preprocessed, cli.output.as_ref(), cli.auto, &cli.rust_edition, cli.strict_rustfmt)?;
} else {
run_infer_macro_types_pipeline(preprocessed, cli.auto)?;
}
Ok(())
}
fn run_streaming(pp: &mut Preprocessor) -> Result<(), Box<dyn std::error::Error>> {
let mut parser = match Parser::new(pp) {
Ok(p) => p,
Err(e) => return Err(format_error(&e, pp).into()),
};
let stdout = io::stdout();
let mut handle = stdout.lock();
let mut count = 0usize;
let parse_result = parser.parse_each(|decl, _loc, _path, interner| {
let mut printer = SexpPrinter::new(&mut handle, interner);
if let Err(e) = printer.print_external_decl(decl) {
eprintln!("Output error: {}", e);
return ControlFlow::Break(());
}
if let Err(e) = printer.writeln() {
eprintln!("Output error: {}", e);
return ControlFlow::Break(());
}
count += 1;
ControlFlow::Continue(())
});
drop(handle);
if let Err(error) = parse_result {
let error_loc = error.loc();
eprintln!("\n=== Parse Error ===");
eprintln!("Location: {}:{}:{}",
pp.files().get_path(error_loc.file_id).display(),
error_loc.line,
error_loc.column);
eprintln!("Error: {}", format_error(&error, pp));
show_source_context(pp, error_loc);
return Err("Parse failed".into());
}
eprintln!("\nSuccessfully parsed {} declarations", count);
Ok(())
}
fn run_dump_fields_dict(pp: &mut Preprocessor, _target_dir: Option<&PathBuf>) -> Result<(), Box<dyn std::error::Error>> {
let mut fields_dict = FieldsDict::new();
let mut parser = match Parser::new(pp) {
Ok(p) => p,
Err(e) => return Err(format_error(&e, pp).into()),
};
parser.parse_each(|decl, _loc, _path, interner| {
fields_dict.collect_from_external_decl(decl, decl.is_target(), interner);
std::ops::ControlFlow::Continue(())
})?;
let stats = fields_dict.stats();
let interner = parser.interner();
eprintln!("=== Fields Dictionary Stats ===");
eprintln!("Total fields: {}", stats.total_fields);
eprintln!("Unique fields (can infer struct): {}", stats.unique_fields);
eprintln!("Ambiguous fields: {}", stats.ambiguous_fields);
eprintln!("Field types collected: {}", fields_dict.field_types_count());
eprintln!();
println!("{}", fields_dict.dump_unique(interner));
Ok(())
}
fn run_infer_macro_types_pipeline(
preprocessed: libperl_macrogen::PreprocessedPipeline,
auto_mode: bool,
) -> Result<(), Box<dyn std::error::Error>> {
let _ = auto_mode;
let inferred = match preprocessed.infer() {
Ok(r) => r,
Err(e) => {
if matches!(e, PipelineError::Io(ref io_err) if io_err.kind() == std::io::ErrorKind::Interrupted) {
return Ok(());
}
return Err(format_pipeline_error(&e).into());
}
};
let result = inferred.result();
let infer_ctx = &result.infer_ctx;
let interner = result.preprocessor.interner();
let stats = &result.stats;
let mut expr_count = 0;
let mut stmt_count = 0;
let mut unparseable_count = 0;
for info in infer_ctx.macros.values() {
if !info.is_target {
continue;
}
if info.is_expression() {
expr_count += 1;
} else if info.is_statement() {
stmt_count += 1;
} else {
unparseable_count += 1;
}
}
let macro_stats = infer_ctx.stats();
eprintln!("=== Macro Type Inference Stats ===");
eprintln!("Total macros analyzed: {}", macro_stats.total);
eprintln!(" - Expression macros: {}", expr_count);
eprintln!(" - Statement macros: {}", stmt_count);
eprintln!(" - Unparseable: {}", unparseable_count);
eprintln!("Confirmed (type complete): {}", macro_stats.confirmed);
eprintln!("Unconfirmed (pending): {}", macro_stats.unconfirmed);
eprintln!("Args unknown: {}", macro_stats.args_unknown);
eprintln!("Return unknown: {}", macro_stats.return_unknown);
eprintln!();
eprintln!("Apidoc from comments: {}", stats.apidoc_from_comments);
eprintln!("THX-dependent macros: {}", stats.thx_dependent_count);
eprintln!("C function declarations: {} ({} THX)", stats.c_fn_decl_count, stats.c_fn_thx_count);
eprintln!("SV family members: {}", result.fields_dict.sv_family_members_count());
eprintln!("typeName -> struct mapping: {}", result.fields_dict.sv_head_type_mapping_count());
eprintln!();
println!("=== Macro Analysis Results ===");
let mut parseable_count = 0;
let mut has_constraints_count = 0;
let mut sorted_macros: Vec<_> = infer_ctx
.macros
.iter()
.filter(|(_, info)| info.is_target && info.has_body && (info.is_function || info.is_thx_dependent))
.collect();
sorted_macros.sort_by_key(|(name, _)| interner.get(**name));
for (name, info) in sorted_macros {
let name_str = interner.get(*name);
let parse_status = if info.is_expression() {
parseable_count += 1;
"expression"
} else if info.is_statement() {
parseable_count += 1;
"statement"
} else {
"unparseable"
};
let thx_marker = if info.is_thx_dependent { " [THX]" } else { "" };
let pasting_marker = if info.has_token_pasting { " [##]" } else { "" };
let constraint_count = info.type_env.total_constraint_count();
if constraint_count > 0 {
has_constraints_count += 1;
}
println!(
"{}: {} ({} constraints, {} uses){}{}",
name_str,
parse_status,
constraint_count,
info.uses.len(),
thx_marker,
pasting_marker
);
match &info.parse_result {
ParseResult::Expression(expr) => {
let stdout = io::stdout();
let mut handle = stdout.lock();
let mut printer = TypedSexpPrinter::new(&mut handle, interner);
printer.set_type_env(&info.type_env);
printer.set_param_map(&info.params);
printer.set_pretty(true);
printer.set_indent(1); printer.set_skip_first_newline(true); let _ = printer.print_expr(expr);
let _ = writeln!(handle);
}
ParseResult::Statement(block_items) => {
let stdout = io::stdout();
let mut handle = stdout.lock();
let _ = write!(handle, " "); let mut printer = TypedSexpPrinter::new(&mut handle, interner);
printer.set_type_env(&info.type_env);
printer.set_param_map(&info.params);
printer.set_pretty(true);
printer.set_indent(1); printer.set_skip_first_newline(true); for item in block_items {
if let BlockItem::Stmt(stmt) = item {
let _ = printer.print_stmt(stmt);
}
}
let _ = writeln!(handle);
}
ParseResult::Unparseable(Some(err_msg)) => {
println!(" error: {}", err_msg);
}
_ => {}
}
if constraint_count > 0 {
for (expr_id, constraints) in &info.type_env.expr_constraints {
for c in constraints {
println!(" expr#{}: {} ({})", expr_id.0, c.ty.to_display_string(interner), c.context);
}
}
}
}
eprintln!();
eprintln!("Parseable macros: {}", parseable_count);
eprintln!("Macros with type constraints: {}", has_constraints_count);
Ok(())
}
fn run_gen_rust_pipeline(
preprocessed: libperl_macrogen::PreprocessedPipeline,
output_path: Option<&PathBuf>,
auto_mode: bool,
rust_edition: &str,
strict_rustfmt: bool,
) -> Result<(), Box<dyn std::error::Error>> {
let infer_config = preprocessed.infer_config();
if auto_mode && infer_config.apidoc_path.is_none() {
}
let inferred = match preprocessed.infer() {
Ok(r) => r,
Err(e) => {
if matches!(e, PipelineError::Io(ref io_err) if io_err.kind() == std::io::ErrorKind::Interrupted) {
return Ok(());
}
return Err(format_pipeline_error(&e).into());
}
};
let mut buffer = Vec::new();
let generated = inferred.generate(&mut buffer)
.map_err(|e| format_pipeline_error(&e))?;
let stats = generated.stats();
let formatted = match apply_rustfmt(&buffer, rust_edition) {
Ok(code) => code,
Err(e) => {
if strict_rustfmt {
return Err(format!("rustfmt failed: {}", e).into());
} else {
eprintln!("Warning: {}", e);
buffer
}
}
};
if let Some(path) = output_path {
let mut file = File::create(path)?;
file.write_all(&formatted)?;
file.flush()?;
eprintln!("Output: {}", path.display());
} else {
let stdout = io::stdout();
let mut handle = stdout.lock();
handle.write_all(&formatted)?;
handle.flush()?;
}
eprintln!("=== Rust Code Generation Stats ===");
eprintln!("Macros: {} success, {} parse failed, {} type incomplete, {} cascade unavailable, {} unresolved names",
stats.macros_success, stats.macros_parse_failed, stats.macros_type_incomplete,
stats.macros_cascade_unavailable, stats.macros_unresolved_names);
eprintln!("Inline functions: {} success, {} type incomplete, {} cascade unavailable, {} unresolved names, {} contains goto",
stats.inline_fns_success, stats.inline_fns_type_incomplete,
stats.inline_fns_cascade_unavailable, stats.inline_fns_unresolved_names,
stats.inline_fns_contains_goto);
Ok(())
}
fn apply_rustfmt(code: &[u8], edition: &str) -> Result<Vec<u8>, String> {
use std::process::{Command, Stdio};
let mut child = match Command::new("rustfmt")
.arg("--edition")
.arg(edition)
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.spawn()
{
Ok(child) => child,
Err(_) => {
return Err("rustfmt not found, skipping formatting".to_string());
}
};
if let Some(mut stdin) = child.stdin.take() {
if let Err(e) = stdin.write_all(code) {
return Err(format!("failed to write to rustfmt stdin: {}", e));
}
}
match child.wait_with_output() {
Ok(output) if output.status.success() => Ok(output.stdout),
Ok(output) => {
let mut msg = "rustfmt failed".to_string();
if !output.stderr.is_empty() {
if let Ok(stderr_str) = std::str::from_utf8(&output.stderr) {
msg = format!("rustfmt failed: {}", stderr_str.trim());
}
}
Err(msg)
}
Err(e) => {
Err(format!("rustfmt failed: {}", e))
}
}
}
fn run_typed_sexp(pp: &mut Preprocessor, output: Option<&PathBuf>) -> Result<(), Box<dyn std::error::Error>> {
let mut parser = match Parser::new(pp) {
Ok(p) => p,
Err(e) => return Err(format_error(&e, pp).into()),
};
let tu = match parser.parse() {
Ok(tu) => tu,
Err(e) => return Err(format_error(&e, pp).into()),
};
if let Some(output_path) = output {
let file = File::create(output_path)?;
let mut writer = BufWriter::new(file);
let mut printer = TypedSexpPrinter::new(&mut writer, pp.interner());
for decl in &tu.decls {
printer.print_external_decl(decl)?;
}
writer.flush()?;
} else {
let stdout = io::stdout();
let mut handle = stdout.lock();
let mut printer = TypedSexpPrinter::new(&mut handle, pp.interner());
for decl in &tu.decls {
printer.print_external_decl(decl)?;
}
handle.flush()?;
}
Ok(())
}
fn run_parse_rust_bindings(rust_file: &PathBuf) -> Result<(), Box<dyn std::error::Error>> {
let dict = RustDeclDict::parse_file(rust_file)?;
let stats = dict.stats();
eprintln!("=== Rust Declarations Stats ===");
eprintln!("Constants: {}", stats.const_count);
eprintln!("Type aliases: {}", stats.type_count);
eprintln!("Functions: {}", stats.fn_count);
eprintln!("Structs: {}", stats.struct_count);
eprintln!();
println!("{}", dict.dump());
Ok(())
}
fn run_apidoc_to_json(
input: &PathBuf,
output: Option<&PathBuf>,
compact: bool,
) -> Result<(), Box<dyn std::error::Error>> {
let dict = ApidocDict::parse_embed_fnc(input)?;
let stats = dict.stats();
eprintln!("Loaded {} entries from {:?}", stats.total, input);
eprintln!(" Functions: {}", stats.function_count);
eprintln!(" Macros: {}", stats.macro_count);
eprintln!(" Inline: {}", stats.inline_count);
eprintln!(" Public API: {}", stats.api_count);
let json = if compact {
serde_json::to_string(&dict)?
} else {
serde_json::to_string_pretty(&dict)?
};
if let Some(output_path) = output {
let file = File::create(output_path)?;
let mut writer = BufWriter::new(file);
writeln!(writer, "{}", json)?;
writer.flush()?;
eprintln!("Written to {:?}", output_path);
} else {
println!("{}", json);
}
Ok(())
}
fn show_source_context(pp: &Preprocessor, loc: &SourceLocation) {
let path = pp.files().get_path(loc.file_id);
let content = match std::fs::read_to_string(path) {
Ok(c) => c,
Err(e) => {
eprintln!("Could not read source file: {}", e);
return;
}
};
let lines: Vec<&str> = content.lines().collect();
let target_line = loc.line as usize;
let start = target_line.saturating_sub(3);
let end = (target_line + 2).min(lines.len());
eprintln!("\nSource context:");
eprintln!("{}:{}:{}", path.display(), loc.line, loc.column);
eprintln!("{}", "-".repeat(60));
for i in start..end {
let line_num = i + 1;
let marker = if line_num == target_line as usize { ">>>" } else { " " };
if i < lines.len() {
eprintln!("{} {:4} | {}", marker, line_num, lines[i]);
if line_num == target_line as usize && loc.column > 0 {
let spaces = " ".repeat(loc.column as usize + 7);
eprintln!("{}^", spaces);
}
}
}
eprintln!("{}", "-".repeat(60));
}
fn format_error(e: &CompileError, pp: &Preprocessor) -> String {
e.format_with_files(pp.files())
}
fn format_pipeline_error(e: &PipelineError) -> String {
match e {
PipelineError::PerlConfig(pe) => format!("Perl config error: {}", pe),
PipelineError::Compile(ce) => format!("Compile error: {}", ce),
PipelineError::Infer(ie) => format!("Inference error: {}", ie),
PipelineError::Io(io_err) => format!("I/O error: {}", io_err),
}
}
fn output_preprocessed(
pp: &mut Preprocessor,
output: Option<&PathBuf>,
gcc_format: bool,
) -> Result<(), Box<dyn std::error::Error>> {
let mut out: Box<dyn Write> = if let Some(path) = output {
Box::new(BufWriter::new(File::create(path)?))
} else {
Box::new(io::stdout().lock())
};
if gcc_format {
output_gcc_format(pp, &mut out)
} else {
output_debug_format(pp, &mut out)
}
}
fn output_gcc_format(
pp: &mut Preprocessor,
out: &mut dyn Write,
) -> Result<(), Box<dyn std::error::Error>> {
let mut last_file: Option<FileId> = None;
let mut last_output_line = 0u32;
let mut need_space = false;
let mut at_statement_start = true;
let mut brace_depth = 0i32;
let mut pending_block_end = false;
let mut file_stack: Vec<FileId> = Vec::new();
loop {
let token = match pp.next_token() {
Ok(t) => t,
Err(e) => return Err(format_error(&e, pp).into()),
};
if matches!(token.kind, TokenKind::Eof) {
break;
}
let current_file = token.loc.file_id;
let current_line = token.loc.line;
if at_statement_start && brace_depth == 0 {
if last_file != Some(current_file) {
if need_space {
writeln!(out)?;
}
let flag = if last_file.is_none() {
"" } else if file_stack.contains(¤t_file) {
while file_stack.last() != Some(¤t_file) {
file_stack.pop();
}
" 2"
} else {
if let Some(prev) = last_file {
file_stack.push(prev);
}
" 1"
};
let path = pp.files().get_path(current_file);
writeln!(out, "# {} \"{}\"{}", current_line, path.display(), flag)?;
last_file = Some(current_file);
last_output_line = current_line;
need_space = false;
} else if current_line > last_output_line {
let gap = current_line - last_output_line;
if gap <= 8 {
if need_space {
writeln!(out)?;
}
for _ in 1..gap {
writeln!(out)?;
}
need_space = false;
} else {
if need_space {
writeln!(out)?;
}
let path = pp.files().get_path(current_file);
writeln!(out, "# {} \"{}\"", current_line, path.display())?;
need_space = false;
}
last_output_line = current_line;
}
at_statement_start = false;
}
if pending_block_end && !matches!(token.kind, TokenKind::Semi) {
writeln!(out)?;
last_output_line += 1;
need_space = false;
at_statement_start = true;
}
pending_block_end = false;
let was_in_block = brace_depth > 0;
match token.kind {
TokenKind::LBrace => brace_depth += 1,
TokenKind::RBrace => brace_depth -= 1,
_ => {}
}
if need_space && !matches!(token.kind, TokenKind::Semi | TokenKind::Comma | TokenKind::RParen | TokenKind::RBracket | TokenKind::RBrace) {
write!(out, " ")?;
}
let suppress_next_space = matches!(token.kind, TokenKind::LParen | TokenKind::LBracket);
write!(out, "{}", token.kind.format(pp.interner()))?;
need_space = !suppress_next_space;
if brace_depth == 0 && matches!(token.kind, TokenKind::Semi) {
writeln!(out)?;
last_output_line += 1;
need_space = false;
at_statement_start = true;
}
if brace_depth == 0 && was_in_block && matches!(token.kind, TokenKind::RBrace) {
pending_block_end = true;
}
}
if need_space {
writeln!(out)?;
}
Ok(())
}
fn output_debug_format(
pp: &mut Preprocessor,
out: &mut dyn Write,
) -> Result<(), Box<dyn std::error::Error>> {
let mut last_line = 0u32;
let mut last_file = None;
let mut need_space = false;
loop {
let token = match pp.next_token() {
Ok(t) => t,
Err(e) => return Err(format_error(&e, pp).into()),
};
if matches!(token.kind, TokenKind::Eof) {
break;
}
let current_file = Some(token.loc.file_id);
let current_line = token.loc.line;
if current_file != last_file {
if last_file.is_some() {
writeln!(out)?;
}
let path = pp.files().get_path(token.loc.file_id);
writeln!(out, "# {} \"{}\"", current_line, path.display())?;
last_line = current_line;
last_file = current_file;
need_space = false;
} else if current_line > last_line {
let gap = current_line - last_line;
if gap <= 8 {
for _ in 0..gap {
writeln!(out)?;
}
} else {
writeln!(out)?;
let path = pp.files().get_path(token.loc.file_id);
writeln!(out, "# {} \"{}\"", current_line, path.display())?;
}
last_line = current_line;
need_space = false;
}
if need_space {
write!(out, " ")?;
}
write!(out, "{}", token.kind.format(pp.interner()))?;
need_space = true;
}
writeln!(out)?;
Ok(())
}
fn parse_defines(defines: &[String]) -> Vec<(String, Option<String>)> {
defines
.iter()
.map(|s| {
if let Some(pos) = s.find('=') {
let (name, value) = s.split_at(pos);
(name.to_string(), Some(value[1..].to_string()))
} else {
(s.clone(), None)
}
})
.collect()
}