Skip to main content

lean_ctx/tools/
ctx_read.rs

1use std::path::Path;
2
3use crate::core::cache::SessionCache;
4use crate::core::compressor;
5use crate::core::deps;
6use crate::core::entropy;
7use crate::core::protocol;
8use crate::core::signatures;
9use crate::core::symbol_map::{self, SymbolMap};
10use crate::core::tokens::count_tokens;
11use crate::tools::CrpMode;
12
13pub fn read_file_lossy(path: &str) -> Result<String, std::io::Error> {
14    let bytes = std::fs::read(path)?;
15    match String::from_utf8(bytes) {
16        Ok(s) => Ok(s),
17        Err(e) => Ok(String::from_utf8_lossy(e.as_bytes()).into_owned()),
18    }
19}
20
21pub fn handle(cache: &mut SessionCache, path: &str, mode: &str, crp_mode: CrpMode) -> String {
22    handle_with_options(cache, path, mode, false, crp_mode, None)
23}
24
25pub fn handle_fresh(cache: &mut SessionCache, path: &str, mode: &str, crp_mode: CrpMode) -> String {
26    handle_with_options(cache, path, mode, true, crp_mode, None)
27}
28
29pub fn handle_with_task(
30    cache: &mut SessionCache,
31    path: &str,
32    mode: &str,
33    crp_mode: CrpMode,
34    task: Option<&str>,
35) -> String {
36    handle_with_options(cache, path, mode, false, crp_mode, task)
37}
38
39pub fn handle_fresh_with_task(
40    cache: &mut SessionCache,
41    path: &str,
42    mode: &str,
43    crp_mode: CrpMode,
44    task: Option<&str>,
45) -> String {
46    handle_with_options(cache, path, mode, true, crp_mode, task)
47}
48
49fn handle_with_options(
50    cache: &mut SessionCache,
51    path: &str,
52    mode: &str,
53    fresh: bool,
54    crp_mode: CrpMode,
55    task: Option<&str>,
56) -> String {
57    let file_ref = cache.get_file_ref(path);
58    let short = protocol::shorten_path(path);
59    let ext = Path::new(path)
60        .extension()
61        .and_then(|e| e.to_str())
62        .unwrap_or("");
63
64    if fresh {
65        cache.invalidate(path);
66    }
67
68    if mode == "diff" {
69        return handle_diff(cache, path, &file_ref);
70    }
71
72    if cache.get(path).is_some() {
73        if mode == "full" {
74            let result = handle_full_with_auto_delta(cache, path, &file_ref, &short, ext, crp_mode);
75            return maybe_apply_task_filter(result, cache, path, task);
76        }
77        let existing = cache.get(path).unwrap();
78        let content = existing.content.clone();
79        let original_tokens = existing.original_tokens;
80        return process_mode(
81            &content,
82            mode,
83            &file_ref,
84            &short,
85            ext,
86            original_tokens,
87            crp_mode,
88            path,
89            task,
90        );
91    }
92
93    let content = match read_file_lossy(path) {
94        Ok(c) => c,
95        Err(e) => return format!("ERROR: {e}"),
96    };
97
98    let (entry, _is_hit) = cache.store(path, content.clone());
99
100    if mode == "full" {
101        let result = format_full_output(cache, &file_ref, &short, ext, &content, &entry, crp_mode);
102        return maybe_apply_task_filter(result, cache, path, task);
103    }
104
105    process_mode(
106        &content,
107        mode,
108        &file_ref,
109        &short,
110        ext,
111        entry.original_tokens,
112        crp_mode,
113        path,
114        task,
115    )
116}
117
118const AUTO_DELTA_THRESHOLD: f64 = 0.6;
119
120/// Re-reads from disk; if content changed and delta is compact, sends auto-delta.
121fn handle_full_with_auto_delta(
122    cache: &mut SessionCache,
123    path: &str,
124    file_ref: &str,
125    short: &str,
126    ext: &str,
127    crp_mode: CrpMode,
128) -> String {
129    let disk_content = match read_file_lossy(path) {
130        Ok(c) => c,
131        Err(_) => {
132            cache.record_cache_hit(path);
133            let existing = cache.get(path).unwrap();
134            return format!(
135                "{file_ref}={short} cached {}t {}L",
136                existing.read_count, existing.line_count
137            );
138        }
139    };
140
141    let old_content = cache.get(path).unwrap().content.clone();
142    let (entry, is_hit) = cache.store(path, disk_content.clone());
143
144    if is_hit {
145        return format!(
146            "{file_ref}={short} cached {}t {}L",
147            entry.read_count, entry.line_count
148        );
149    }
150
151    let diff = compressor::diff_content(&old_content, &disk_content);
152    let diff_tokens = count_tokens(&diff);
153    let full_tokens = entry.original_tokens;
154
155    if full_tokens > 0 && (diff_tokens as f64) < (full_tokens as f64 * AUTO_DELTA_THRESHOLD) {
156        let savings = protocol::format_savings(full_tokens, diff_tokens);
157        return format!(
158            "{file_ref}={short} [auto-delta] ∆{}L\n{diff}\n{savings}",
159            disk_content.lines().count()
160        );
161    }
162
163    format_full_output(cache, file_ref, short, ext, &disk_content, &entry, crp_mode)
164}
165
166fn format_full_output(
167    _cache: &mut SessionCache,
168    file_ref: &str,
169    short: &str,
170    ext: &str,
171    content: &str,
172    entry: &crate::core::cache::CacheEntry,
173    _crp_mode: CrpMode,
174) -> String {
175    let tokens = entry.original_tokens;
176    let metadata = build_header(file_ref, short, ext, content, entry.line_count, true);
177
178    let mut sym = SymbolMap::new();
179    let idents = symbol_map::extract_identifiers(content, ext);
180    for ident in &idents {
181        sym.register(ident);
182    }
183
184    let sym_beneficial = if sym.len() >= 3 {
185        let sym_table = sym.format_table();
186        let compressed = sym.apply(content);
187        let original_tok = count_tokens(content);
188        let compressed_tok = count_tokens(&compressed) + count_tokens(&sym_table);
189        let net_saving = original_tok.saturating_sub(compressed_tok);
190        original_tok > 0 && net_saving * 100 / original_tok >= 5
191    } else {
192        false
193    };
194
195    if sym_beneficial {
196        let compressed_content = sym.apply(content);
197        let sym_table = sym.format_table();
198        let output = format!("{compressed_content}{sym_table}\n{metadata}");
199        let sent = count_tokens(&output);
200        let savings = protocol::format_savings(tokens, sent);
201        return format!("{output}\n{savings}");
202    }
203
204    let output = format!("{content}\n{metadata}");
205    let sent = count_tokens(&output);
206    let savings = protocol::format_savings(tokens, sent);
207    format!("{output}\n{savings}")
208}
209
210const TASK_FILTER_TOKEN_THRESHOLD: usize = 1000;
211const TASK_FILTER_BUDGET_RATIO: f64 = 0.5;
212
213fn maybe_apply_task_filter(
214    full_output: String,
215    cache: &mut SessionCache,
216    path: &str,
217    task: Option<&str>,
218) -> String {
219    let task_str = match task {
220        Some(t) if !t.is_empty() => t,
221        _ => return full_output,
222    };
223
224    let ext = Path::new(path)
225        .extension()
226        .and_then(|e| e.to_str())
227        .unwrap_or("");
228
229    if !crate::tools::ctx_smart_read::is_code_ext(ext) {
230        return full_output;
231    }
232
233    let original_tokens = match cache.get(path) {
234        Some(entry) => entry.original_tokens,
235        None => return full_output,
236    };
237
238    if original_tokens < TASK_FILTER_TOKEN_THRESHOLD {
239        return full_output;
240    }
241
242    let content = match cache.get(path) {
243        Some(entry) => entry.content.clone(),
244        None => return full_output,
245    };
246
247    let (_files, keywords) = crate::core::task_relevance::parse_task_hints(task_str);
248    if keywords.is_empty() {
249        return full_output;
250    }
251
252    let original_lines = content.lines().count();
253    let filtered = crate::core::task_relevance::information_bottleneck_filter(
254        &content,
255        &keywords,
256        TASK_FILTER_BUDGET_RATIO,
257    );
258    let filtered_lines = filtered.lines().count();
259
260    if filtered_lines >= original_lines {
261        return full_output;
262    }
263
264    let file_ref = cache.get_file_ref(path);
265    let short = protocol::shorten_path(path);
266    let header = format!(
267        "{file_ref}={short} {original_lines}L [task-enhanced: {original_lines}→{filtered_lines}]"
268    );
269    let sent = count_tokens(&filtered) + count_tokens(&header);
270    let savings = protocol::format_savings(original_tokens, sent);
271    format!("{header}\n{filtered}\n{savings}")
272}
273
274fn build_header(
275    file_ref: &str,
276    short: &str,
277    ext: &str,
278    content: &str,
279    line_count: usize,
280    include_deps: bool,
281) -> String {
282    let mut header = format!("{file_ref}={short} {line_count}L");
283
284    if include_deps {
285        let dep_info = deps::extract_deps(content, ext);
286        if !dep_info.imports.is_empty() {
287            let imports_str: Vec<&str> = dep_info
288                .imports
289                .iter()
290                .take(8)
291                .map(|s| s.as_str())
292                .collect();
293            header.push_str(&format!("\n deps {}", imports_str.join(",")));
294        }
295        if !dep_info.exports.is_empty() {
296            let exports_str: Vec<&str> = dep_info
297                .exports
298                .iter()
299                .take(8)
300                .map(|s| s.as_str())
301                .collect();
302            header.push_str(&format!("\n exports {}", exports_str.join(",")));
303        }
304    }
305
306    header
307}
308
309#[allow(clippy::too_many_arguments)]
310fn process_mode(
311    content: &str,
312    mode: &str,
313    file_ref: &str,
314    short: &str,
315    ext: &str,
316    original_tokens: usize,
317    crp_mode: CrpMode,
318    file_path: &str,
319    task: Option<&str>,
320) -> String {
321    let line_count = content.lines().count();
322
323    match mode {
324        "auto" => {
325            let sig =
326                crate::core::mode_predictor::FileSignature::from_path(file_path, original_tokens);
327            let predictor = crate::core::mode_predictor::ModePredictor::new();
328            let resolved = predictor
329                .predict_best_mode(&sig)
330                .unwrap_or_else(|| "full".to_string());
331            process_mode(
332                content,
333                &resolved,
334                file_ref,
335                short,
336                ext,
337                original_tokens,
338                crp_mode,
339                file_path,
340                task,
341            )
342        }
343        "signatures" => {
344            let sigs = signatures::extract_signatures(content, ext);
345            let dep_info = deps::extract_deps(content, ext);
346
347            let mut output = format!("{file_ref}={short} {line_count}L");
348            if !dep_info.imports.is_empty() {
349                let imports_str: Vec<&str> = dep_info
350                    .imports
351                    .iter()
352                    .take(8)
353                    .map(|s| s.as_str())
354                    .collect();
355                output.push_str(&format!("\n deps {}", imports_str.join(",")));
356            }
357            for sig in &sigs {
358                output.push('\n');
359                if crp_mode.is_tdd() {
360                    output.push_str(&sig.to_tdd());
361                } else {
362                    output.push_str(&sig.to_compact());
363                }
364            }
365            let sent = count_tokens(&output);
366            let savings = protocol::format_savings(original_tokens, sent);
367            format!("{output}\n{savings}")
368        }
369        "map" => {
370            if ext == "php" {
371                if let Some(php_map) = crate::core::patterns::php::compress_php_map(content, short)
372                {
373                    let mut output = format!("{file_ref}={short} {line_count}L\n{php_map}");
374                    let sent = count_tokens(&output);
375                    let savings = protocol::format_savings(original_tokens, sent);
376                    output.push('\n');
377                    output.push_str(&savings);
378                    return output;
379                }
380            }
381
382            let sigs = signatures::extract_signatures(content, ext);
383            let dep_info = deps::extract_deps(content, ext);
384
385            let mut output = format!("{file_ref}={short} {line_count}L");
386
387            if !dep_info.imports.is_empty() {
388                output.push_str("\n  deps: ");
389                output.push_str(&dep_info.imports.join(", "));
390            }
391
392            if !dep_info.exports.is_empty() {
393                output.push_str("\n  exports: ");
394                output.push_str(&dep_info.exports.join(", "));
395            }
396
397            let key_sigs: Vec<&signatures::Signature> = sigs
398                .iter()
399                .filter(|s| s.is_exported || s.indent == 0)
400                .collect();
401
402            if !key_sigs.is_empty() {
403                output.push_str("\n  API:");
404                for sig in &key_sigs {
405                    output.push_str("\n    ");
406                    if crp_mode.is_tdd() {
407                        output.push_str(&sig.to_tdd());
408                    } else {
409                        output.push_str(&sig.to_compact());
410                    }
411                }
412            }
413
414            let sent = count_tokens(&output);
415            let savings = protocol::format_savings(original_tokens, sent);
416            format!("{output}\n{savings}")
417        }
418        "aggressive" => {
419            let raw = compressor::aggressive_compress(content, Some(ext));
420            let compressed = compressor::safeguard_ratio(content, &raw);
421            let header = build_header(file_ref, short, ext, content, line_count, true);
422
423            let mut sym = SymbolMap::new();
424            let idents = symbol_map::extract_identifiers(&compressed, ext);
425            for ident in &idents {
426                sym.register(ident);
427            }
428
429            let sym_beneficial = if sym.len() >= 3 {
430                let sym_table = sym.format_table();
431                let sym_applied = sym.apply(&compressed);
432                let orig_tok = count_tokens(&compressed);
433                let comp_tok = count_tokens(&sym_applied) + count_tokens(&sym_table);
434                let net = orig_tok.saturating_sub(comp_tok);
435                orig_tok > 0 && net * 100 / orig_tok >= 5
436            } else {
437                false
438            };
439
440            if sym_beneficial {
441                let sym_output = sym.apply(&compressed);
442                let sym_table = sym.format_table();
443                let sent = count_tokens(&sym_output) + count_tokens(&sym_table);
444                let savings = protocol::format_savings(original_tokens, sent);
445                return format!("{header}\n{sym_output}{sym_table}\n{savings}");
446            }
447
448            let sent = count_tokens(&compressed);
449            let savings = protocol::format_savings(original_tokens, sent);
450            format!("{header}\n{compressed}\n{savings}")
451        }
452        "entropy" => {
453            let result = entropy::entropy_compress_adaptive(content, file_path);
454            let avg_h = entropy::analyze_entropy(content).avg_entropy;
455            let header = build_header(file_ref, short, ext, content, line_count, false);
456            let techs = result.techniques.join(", ");
457            let output = format!("{header} H̄={avg_h:.1} [{techs}]\n{}", result.output);
458            let sent = count_tokens(&output);
459            let savings = protocol::format_savings(original_tokens, sent);
460            format!("{output}\n{savings}")
461        }
462        "task" => {
463            let task_str = task.unwrap_or("");
464            if task_str.is_empty() {
465                let header = build_header(file_ref, short, ext, content, line_count, true);
466                return format!("{header}\n{content}\n[task mode: no task set — returned full]");
467            }
468            let (_files, keywords) = crate::core::task_relevance::parse_task_hints(task_str);
469            if keywords.is_empty() {
470                let header = build_header(file_ref, short, ext, content, line_count, true);
471                return format!(
472                    "{header}\n{content}\n[task mode: no keywords extracted — returned full]"
473                );
474            }
475            let filtered =
476                crate::core::task_relevance::information_bottleneck_filter(content, &keywords, 0.3);
477            let filtered_lines = filtered.lines().count();
478            let header = format!(
479                "{file_ref}={short} {line_count}L [task-filtered: {line_count}→{filtered_lines}]"
480            );
481            let sent = count_tokens(&filtered) + count_tokens(&header);
482            let savings = protocol::format_savings(original_tokens, sent);
483            format!("{header}\n{filtered}\n{savings}")
484        }
485        "reference" => {
486            let tok = count_tokens(content);
487            let output = format!("{file_ref}={short}: {line_count} lines, {tok} tok ({ext})");
488            let sent = count_tokens(&output);
489            let savings = protocol::format_savings(original_tokens, sent);
490            format!("{output}\n{savings}")
491        }
492        mode if mode.starts_with("lines:") => {
493            let range_str = &mode[6..];
494            let extracted = extract_line_range(content, range_str);
495            let header = format!("{file_ref}={short} {line_count}L lines:{range_str}");
496            let sent = count_tokens(&extracted);
497            let savings = protocol::format_savings(original_tokens, sent);
498            format!("{header}\n{extracted}\n{savings}")
499        }
500        _ => {
501            let header = build_header(file_ref, short, ext, content, line_count, true);
502            format!("{header}\n{content}")
503        }
504    }
505}
506
507fn extract_line_range(content: &str, range_str: &str) -> String {
508    let lines: Vec<&str> = content.lines().collect();
509    let total = lines.len();
510    let mut selected = Vec::new();
511
512    for part in range_str.split(',') {
513        let part = part.trim();
514        if let Some((start_s, end_s)) = part.split_once('-') {
515            let start = start_s.trim().parse::<usize>().unwrap_or(1).max(1);
516            let end = end_s.trim().parse::<usize>().unwrap_or(total).min(total);
517            for i in start..=end {
518                if i >= 1 && i <= total {
519                    selected.push(format!("{i:>4}| {}", lines[i - 1]));
520                }
521            }
522        } else if let Ok(n) = part.parse::<usize>() {
523            if n >= 1 && n <= total {
524                selected.push(format!("{n:>4}| {}", lines[n - 1]));
525            }
526        }
527    }
528
529    if selected.is_empty() {
530        "No lines matched the range.".to_string()
531    } else {
532        selected.join("\n")
533    }
534}
535
536fn handle_diff(cache: &mut SessionCache, path: &str, file_ref: &str) -> String {
537    let short = protocol::shorten_path(path);
538    let old_content = cache.get(path).map(|e| e.content.clone());
539
540    let new_content = match read_file_lossy(path) {
541        Ok(c) => c,
542        Err(e) => return format!("ERROR: {e}"),
543    };
544
545    let original_tokens = count_tokens(&new_content);
546
547    let diff_output = if let Some(old) = &old_content {
548        compressor::diff_content(old, &new_content)
549    } else {
550        format!("[first read]\n{new_content}")
551    };
552
553    cache.store(path, new_content);
554
555    let sent = count_tokens(&diff_output);
556    let savings = protocol::format_savings(original_tokens, sent);
557    format!("{file_ref}={short} [diff]\n{diff_output}\n{savings}")
558}
559
560#[cfg(test)]
561mod tests {
562    use super::*;
563
564    #[test]
565    fn test_header_toon_format_no_brackets() {
566        let content = "use std::io;\nfn main() {}\n";
567        let header = build_header("F1", "main.rs", "rs", content, 2, false);
568        assert!(!header.contains('['));
569        assert!(!header.contains(']'));
570        assert!(header.contains("F1=main.rs 2L"));
571    }
572
573    #[test]
574    fn test_header_toon_deps_indented() {
575        let content = "use crate::core::cache;\nuse crate::tools;\npub fn main() {}\n";
576        let header = build_header("F1", "main.rs", "rs", content, 3, true);
577        if header.contains("deps") {
578            assert!(
579                header.contains("\n deps "),
580                "deps should use indented TOON format"
581            );
582            assert!(
583                !header.contains("deps:["),
584                "deps should not use bracket format"
585            );
586        }
587    }
588
589    #[test]
590    fn test_header_toon_saves_tokens() {
591        let content = "use crate::foo;\nuse crate::bar;\npub fn baz() {}\npub fn qux() {}\n";
592        let old_header = "F1=main.rs [4L +] deps:[foo,bar] exports:[baz,qux]".to_string();
593        let new_header = build_header("F1", "main.rs", "rs", content, 4, true);
594        let old_tokens = count_tokens(&old_header);
595        let new_tokens = count_tokens(&new_header);
596        assert!(
597            new_tokens <= old_tokens,
598            "TOON header ({new_tokens} tok) should be <= old format ({old_tokens} tok)"
599        );
600    }
601
602    #[test]
603    fn test_tdd_symbols_are_compact() {
604        let symbols = [
605            "⊕", "⊖", "∆", "→", "⇒", "✓", "✗", "⚠", "λ", "§", "∂", "τ", "ε",
606        ];
607        for sym in &symbols {
608            let tok = count_tokens(sym);
609            assert!(tok <= 2, "Symbol {sym} should be 1-2 tokens, got {tok}");
610        }
611    }
612
613    #[test]
614    fn test_task_mode_filters_content() {
615        let content = (0..200)
616            .map(|i| {
617                if i % 20 == 0 {
618                    format!("fn validate_token(token: &str) -> bool {{ /* line {i} */ }}")
619                } else {
620                    format!("fn unrelated_helper_{i}(x: i32) -> i32 {{ x + {i} }}")
621                }
622            })
623            .collect::<Vec<_>>()
624            .join("\n");
625        let full_tokens = count_tokens(&content);
626        let task = Some("fix bug in validate_token");
627        let result = process_mode(
628            &content,
629            "task",
630            "F1",
631            "test.rs",
632            "rs",
633            full_tokens,
634            CrpMode::Off,
635            "test.rs",
636            task,
637        );
638        let result_tokens = count_tokens(&result);
639        assert!(
640            result_tokens < full_tokens,
641            "task mode ({result_tokens} tok) should be less than full ({full_tokens} tok)"
642        );
643        assert!(
644            result.contains("task-filtered"),
645            "output should contain task-filtered marker"
646        );
647    }
648
649    #[test]
650    fn test_task_mode_without_task_returns_full() {
651        let content = "fn main() {}\nfn helper() {}\n";
652        let tokens = count_tokens(content);
653        let result = process_mode(
654            content,
655            "task",
656            "F1",
657            "test.rs",
658            "rs",
659            tokens,
660            CrpMode::Off,
661            "test.rs",
662            None,
663        );
664        assert!(
665            result.contains("no task set"),
666            "should indicate no task: {result}"
667        );
668    }
669
670    #[test]
671    fn test_reference_mode_one_line() {
672        let content = "fn main() {}\nfn helper() {}\nfn other() {}\n";
673        let tokens = count_tokens(content);
674        let result = process_mode(
675            content,
676            "reference",
677            "F1",
678            "test.rs",
679            "rs",
680            tokens,
681            CrpMode::Off,
682            "test.rs",
683            None,
684        );
685        let lines: Vec<&str> = result.lines().collect();
686        assert!(
687            lines.len() <= 3,
688            "reference mode should be very compact, got {} lines",
689            lines.len()
690        );
691        assert!(result.contains("lines"), "should contain line count");
692        assert!(result.contains("tok"), "should contain token count");
693    }
694
695    #[test]
696    fn benchmark_task_conditioned_compression() {
697        let content = generate_benchmark_code(500);
698        let full_tokens = count_tokens(&content);
699        let task = Some("fix authentication in validate_token");
700
701        let full_output = process_mode(
702            &content,
703            "full",
704            "F1",
705            "server.rs",
706            "rs",
707            full_tokens,
708            CrpMode::Off,
709            "server.rs",
710            task,
711        );
712        let task_output = process_mode(
713            &content,
714            "task",
715            "F1",
716            "server.rs",
717            "rs",
718            full_tokens,
719            CrpMode::Off,
720            "server.rs",
721            task,
722        );
723        let sig_output = process_mode(
724            &content,
725            "signatures",
726            "F1",
727            "server.rs",
728            "rs",
729            full_tokens,
730            CrpMode::Off,
731            "server.rs",
732            task,
733        );
734        let ref_output = process_mode(
735            &content,
736            "reference",
737            "F1",
738            "server.rs",
739            "rs",
740            full_tokens,
741            CrpMode::Off,
742            "server.rs",
743            task,
744        );
745
746        let full_tok = count_tokens(&full_output);
747        let task_tok = count_tokens(&task_output);
748        let sig_tok = count_tokens(&sig_output);
749        let ref_tok = count_tokens(&ref_output);
750
751        eprintln!("\n=== Task-Conditioned Compression Benchmark ===");
752        eprintln!("Source: 500-line Rust file, task='fix authentication in validate_token'");
753        eprintln!("  full:       {full_tok:>6} tokens (baseline)");
754        eprintln!(
755            "  task:       {task_tok:>6} tokens ({:.0}% savings)",
756            (1.0 - task_tok as f64 / full_tok as f64) * 100.0
757        );
758        eprintln!(
759            "  signatures: {sig_tok:>6} tokens ({:.0}% savings)",
760            (1.0 - sig_tok as f64 / full_tok as f64) * 100.0
761        );
762        eprintln!(
763            "  reference:  {ref_tok:>6} tokens ({:.0}% savings)",
764            (1.0 - ref_tok as f64 / full_tok as f64) * 100.0
765        );
766        eprintln!("================================================\n");
767
768        assert!(task_tok < full_tok, "task mode should save tokens");
769        assert!(sig_tok < full_tok, "signatures should save tokens");
770        assert!(ref_tok < sig_tok, "reference should be most compact");
771    }
772
773    fn generate_benchmark_code(lines: usize) -> String {
774        let mut code = Vec::with_capacity(lines);
775        code.push("use std::collections::HashMap;".to_string());
776        code.push("use crate::core::auth;".to_string());
777        code.push(String::new());
778        code.push("pub struct Server {".to_string());
779        code.push("    config: Config,".to_string());
780        code.push("    cache: HashMap<String, String>,".to_string());
781        code.push("}".to_string());
782        code.push(String::new());
783        code.push("impl Server {".to_string());
784        code.push(
785            "    pub fn validate_token(&self, token: &str) -> Result<Claims, AuthError> {"
786                .to_string(),
787        );
788        code.push("        let decoded = auth::decode_jwt(token)?;".to_string());
789        code.push("        if decoded.exp < chrono::Utc::now().timestamp() {".to_string());
790        code.push("            return Err(AuthError::Expired);".to_string());
791        code.push("        }".to_string());
792        code.push("        Ok(decoded.claims)".to_string());
793        code.push("    }".to_string());
794        code.push(String::new());
795
796        let remaining = lines.saturating_sub(code.len());
797        for i in 0..remaining {
798            if i % 30 == 0 {
799                code.push(format!(
800                    "    pub fn handler_{i}(&self, req: Request) -> Response {{"
801                ));
802            } else if i % 30 == 29 {
803                code.push("    }".to_string());
804            } else {
805                code.push(format!("        let val_{i} = self.cache.get(\"key_{i}\").unwrap_or(&\"default\".to_string());"));
806            }
807        }
808        code.push("}".to_string());
809        code.join("\n")
810    }
811}