1use std::path::Path;
2
3use crate::core::compressor;
4use crate::core::entropy;
5use crate::core::quality;
6use crate::core::signatures;
7use crate::core::symbol_map::{self, SymbolMap};
8use crate::core::tokens::count_tokens;
9use crate::tools::CrpMode;
10
11pub fn handle(path: &str, crp_mode: CrpMode) -> String {
12 let content = match std::fs::read_to_string(path) {
13 Ok(c) => c,
14 Err(e) => return format!("ERROR: {e}"),
15 };
16
17 let line_count = content.lines().count();
18 let short = crate::core::protocol::shorten_path(path);
19 let ext = Path::new(path)
20 .extension()
21 .and_then(|e| e.to_str())
22 .unwrap_or("");
23
24 let raw_tokens = count_tokens(&content);
25
26 let aggressive = compressor::aggressive_compress(&content, Some(ext));
27 let aggressive_tokens = count_tokens(&aggressive);
28
29 let sigs = signatures::extract_signatures(&content, ext);
30 let sig_compact: String = sigs
31 .iter()
32 .map(|s| s.to_compact())
33 .collect::<Vec<_>>()
34 .join("\n");
35 let sig_tokens = count_tokens(&sig_compact);
36
37 let sig_tdd: String = sigs
38 .iter()
39 .map(|s| s.to_tdd())
40 .collect::<Vec<_>>()
41 .join("\n");
42 let sig_tdd_tokens = count_tokens(&sig_tdd);
43
44 let entropy_result = entropy::entropy_compress(&content);
45 let entropy_tokens = entropy_result.compressed_tokens;
46
47 let cache_hit = format!("F? cached 2t {line_count}L");
48 let cache_tokens = count_tokens(&cache_hit);
49
50 let mut sym = SymbolMap::new();
51 let idents = symbol_map::extract_identifiers(&content, ext);
52 for ident in &idents {
53 sym.register(ident);
54 }
55 let tdd_full = sym.apply(&content);
56 let tdd_table = sym.format_table();
57 let tdd_full_tokens = count_tokens(&tdd_full) + count_tokens(&tdd_table);
58
59 let tdd_agg = sym.apply(&aggressive);
60 let tdd_agg_tokens = count_tokens(&tdd_agg) + count_tokens(&tdd_table);
61
62 let mut rows = Vec::new();
63 rows.push(format!("Benchmark: {short} ({line_count}L)\n"));
64
65 let q_aggressive = quality_cell(&content, &aggressive, ext);
66 let q_sig_compact = quality_cell(&content, &sig_compact, ext);
67 let q_sig_tdd = quality_cell(&content, &sig_tdd, ext);
68 let q_entropy = quality_cell(&content, &entropy_result.output, ext);
69
70 if crp_mode.is_tdd() {
71 rows.push(format!(
72 "{:<28} {:>6} {:>8} {:>7}",
73 "Strategy", "Tokens", "Savings", "Quality"
74 ));
75 rows.push("─".repeat(57));
76 rows.push(format_row("raw", raw_tokens, raw_tokens, "—"));
77 rows.push(format_row(
78 "aggressive",
79 aggressive_tokens,
80 raw_tokens,
81 &q_aggressive,
82 ));
83 rows.push(format_row(
84 "signatures (compact)",
85 sig_tokens,
86 raw_tokens,
87 &q_sig_compact,
88 ));
89 rows.push(format_row(
90 "signatures (tdd)",
91 sig_tdd_tokens,
92 raw_tokens,
93 &q_sig_tdd,
94 ));
95 rows.push(format_row(
96 "entropy",
97 entropy_tokens,
98 raw_tokens,
99 &q_entropy,
100 ));
101 rows.push(format_row(
102 "full + §MAP (tdd)",
103 tdd_full_tokens,
104 raw_tokens,
105 "—",
106 ));
107 rows.push(format_row(
108 "aggressive + §MAP (tdd)",
109 tdd_agg_tokens,
110 raw_tokens,
111 "—",
112 ));
113 rows.push(format_row("cache hit", cache_tokens, raw_tokens, "—"));
114 rows.push("─".repeat(57));
115
116 let strategies = [
117 ("aggressive", aggressive_tokens),
118 ("signatures (compact)", sig_tokens),
119 ("signatures (tdd)", sig_tdd_tokens),
120 ("entropy", entropy_tokens),
121 ("full + §MAP", tdd_full_tokens),
122 ("aggressive + §MAP", tdd_agg_tokens),
123 ("cache hit", cache_tokens),
124 ];
125 if let Some(best) = strategies.iter().min_by_key(|(_, t)| *t) {
126 let saved = raw_tokens.saturating_sub(best.1);
127 let pct = if raw_tokens > 0 {
128 (saved as f64 / raw_tokens as f64 * 100.0).round() as usize
129 } else {
130 0
131 };
132 rows.push(format!(
133 "Best: \"{}\" saves {} tokens ({}%)",
134 best.0, saved, pct
135 ));
136 }
137
138 let tdd_extra = sig_tokens.saturating_sub(sig_tdd_tokens);
139 let tdd_pct = if sig_tokens > 0 {
140 (tdd_extra as f64 / sig_tokens as f64 * 100.0).round() as usize
141 } else {
142 0
143 };
144 rows.push(format!(
145 "TDD bonus (signatures): {} extra tokens saved ({}%)",
146 tdd_extra, tdd_pct
147 ));
148 } else {
149 rows.push(format!(
150 "{:<24} {:>6} {:>8} {:>7}",
151 "Strategy", "Tokens", "Savings", "Quality"
152 ));
153 rows.push("─".repeat(53));
154 rows.push(format_row("raw", raw_tokens, raw_tokens, "—"));
155 rows.push(format_row(
156 "aggressive",
157 aggressive_tokens,
158 raw_tokens,
159 &q_aggressive,
160 ));
161 rows.push(format_row(
162 "signatures (compact)",
163 sig_tokens,
164 raw_tokens,
165 &q_sig_compact,
166 ));
167 rows.push(format_row(
168 "entropy",
169 entropy_tokens,
170 raw_tokens,
171 &q_entropy,
172 ));
173 rows.push(format_row("cache hit", cache_tokens, raw_tokens, "—"));
174 rows.push("─".repeat(53));
175
176 let strategies = [
177 ("aggressive", aggressive_tokens),
178 ("signatures", sig_tokens),
179 ("entropy", entropy_tokens),
180 ("cache hit", cache_tokens),
181 ];
182 if let Some(best) = strategies.iter().min_by_key(|(_, t)| *t) {
183 let saved = raw_tokens.saturating_sub(best.1);
184 let pct = if raw_tokens > 0 {
185 (saved as f64 / raw_tokens as f64 * 100.0).round() as usize
186 } else {
187 0
188 };
189 rows.push(format!(
190 "Best: \"{}\" saves {} tokens ({}%)",
191 best.0, saved, pct
192 ));
193 }
194 }
195
196 rows.join("\n")
197}
198
199fn format_row(name: &str, tokens: usize, baseline: usize, quality: &str) -> String {
200 if tokens >= baseline {
201 format!("{name:<28} {tokens:>6} — {quality:>7}")
202 } else {
203 let saved = baseline - tokens;
204 let pct = (saved as f64 / baseline as f64 * 100.0).round() as usize;
205 format!("{name:<28} {tokens:>6} -{saved} ({pct}%) {quality:>7}")
206 }
207}
208
209fn quality_cell(original: &str, compressed: &str, ext: &str) -> String {
210 let q = quality::score(original, compressed, ext);
211 let pct = (q.composite * 100.0).round() as u32;
212 let pass = if q.passed { "✓" } else { "✗" };
213 format!("{pct:>3}%{pass}")
214}