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