use std::collections::HashMap;
use std::path::PathBuf;
use anyhow::Result;
use clap::Args;
use serde::Serialize;
use tldr_core::ssa::compute_live_variables;
use tldr_core::{get_cfg_context, get_dfg_context, Language};
use crate::output::OutputFormat;
#[derive(Debug, Args)]
pub struct LiveVarsArgs {
pub file: PathBuf,
pub function: String,
#[arg(long, short = 'l')]
pub lang: Option<Language>,
#[arg(long)]
pub var: Option<String>,
#[arg(long)]
pub live_only: bool,
}
#[derive(Debug, Serialize)]
struct BlockLiveSets {
live_in: Vec<String>,
live_out: Vec<String>,
}
#[derive(Debug, Serialize)]
struct LiveVarsOutput {
function: String,
blocks: HashMap<String, BlockLiveSets>,
}
impl LiveVarsArgs {
pub fn run(&self, format: OutputFormat, quiet: bool) -> Result<()> {
use crate::output::OutputWriter;
let writer = OutputWriter::new(format, quiet);
let language = self.lang.unwrap_or_else(|| {
Language::from_path(&self.file).unwrap_or(Language::Python)
});
writer.progress(&format!(
"Analyzing live variables for {} in {}...",
self.function,
self.file.display()
));
if !self.file.exists() {
return Err(anyhow::anyhow!(
"File not found: {}",
self.file.display()
));
}
let cfg = get_cfg_context(
self.file.to_str().unwrap_or_default(),
&self.function,
language,
)?;
let dfg = get_dfg_context(
self.file.to_str().unwrap_or_default(),
&self.function,
language,
)?;
let live_vars = compute_live_variables(&cfg, &dfg.refs)?;
let mut blocks_output: HashMap<String, BlockLiveSets> = HashMap::new();
for (block_id, live_sets) in &live_vars.blocks {
let mut live_in: Vec<String> = live_sets.live_in.iter().cloned().collect();
let mut live_out: Vec<String> = live_sets.live_out.iter().cloned().collect();
live_in.sort();
live_out.sort();
if let Some(ref var) = self.var {
let has_var = live_in.contains(var) || live_out.contains(var);
if self.live_only && !has_var {
continue;
}
live_in.retain(|v| v == var);
live_out.retain(|v| v == var);
}
if self.live_only && live_in.is_empty() && live_out.is_empty() {
continue;
}
blocks_output.insert(
block_id.to_string(),
BlockLiveSets { live_in, live_out },
);
}
let output = LiveVarsOutput {
function: live_vars.function.clone(),
blocks: blocks_output,
};
match format {
OutputFormat::Text => {
let text = format_live_vars_text(&output, self.var.as_deref());
writer.write_text(&text)?;
}
OutputFormat::Json | OutputFormat::Compact => {
writer.write(&output)?;
}
OutputFormat::Dot => {
writer.write(&output)?;
}
OutputFormat::Sarif => {
writer.write(&output)?;
}
}
Ok(())
}
}
fn format_live_vars_text(output: &LiveVarsOutput, var_filter: Option<&str>) -> String {
let mut lines = Vec::new();
lines.push(format!("Live Variables Analysis: {}", output.function));
if let Some(var) = var_filter {
lines.push(format!("Filtered to variable: {}", var));
}
lines.push(String::new());
let mut block_ids: Vec<_> = output.blocks.keys().collect();
block_ids.sort_by_key(|k| k.parse::<usize>().unwrap_or(0));
for block_id in block_ids {
let sets = &output.blocks[block_id];
lines.push(format!("Block {}:", block_id));
if sets.live_in.is_empty() {
lines.push(" live_in: {}".to_string());
} else {
lines.push(format!(" live_in: {{{}}}", sets.live_in.join(", ")));
}
if sets.live_out.is_empty() {
lines.push(" live_out: {}".to_string());
} else {
lines.push(format!(" live_out: {{{}}}", sets.live_out.join(", ")));
}
lines.push(String::new());
}
lines.join("\n")
}