1use serde::{Deserialize, Serialize};
9use std::collections::HashMap;
10
11#[derive(Debug, Clone, Default, Serialize, Deserialize)]
16pub struct CompileMetrics {
17 total_attempts: u64,
19
20 successes: u64,
22
23 failures: u64,
25
26 error_counts: HashMap<String, u64>,
28}
29
30impl CompileMetrics {
31 pub fn new() -> Self {
33 Self::default()
34 }
35
36 pub fn record_success(&mut self) {
38 self.total_attempts += 1;
39 self.successes += 1;
40 }
41
42 pub fn record_failure(&mut self, error_message: &str) {
46 self.total_attempts += 1;
47 self.failures += 1;
48
49 let error_code = extract_error_code(error_message);
51 *self.error_counts.entry(error_code).or_insert(0) += 1;
52 }
53
54 pub fn total_attempts(&self) -> u64 {
56 self.total_attempts
57 }
58
59 pub fn successes(&self) -> u64 {
61 self.successes
62 }
63
64 pub fn failures(&self) -> u64 {
66 self.failures
67 }
68
69 pub fn success_rate(&self) -> f64 {
71 if self.total_attempts == 0 {
72 0.0
73 } else {
74 self.successes as f64 / self.total_attempts as f64
75 }
76 }
77
78 pub fn meets_target(&self, target: f64) -> bool {
83 self.success_rate() >= target
84 }
85
86 pub fn error_histogram(&self) -> &HashMap<String, u64> {
88 &self.error_counts
89 }
90
91 pub fn reset(&mut self) {
93 self.total_attempts = 0;
94 self.successes = 0;
95 self.failures = 0;
96 self.error_counts.clear();
97 }
98
99 pub fn to_markdown(&self) -> String {
101 let rate_pct = self.success_rate() * 100.0;
102 let target_status = if self.meets_target(0.80) { "✅ PASS" } else { "❌ FAIL" };
103
104 let mut report = format!(
105 "## Compile Success Rate Metrics\n\n\
106 | Metric | Value |\n\
107 |--------|-------|\n\
108 | Total Attempts | {} |\n\
109 | Successes | {} |\n\
110 | Failures | {} |\n\
111 | Success Rate | {:.1}% |\n\
112 | Target (80%) | {} |\n",
113 self.total_attempts, self.successes, self.failures, rate_pct, target_status
114 );
115
116 if !self.error_counts.is_empty() {
117 report.push_str("\n### Error Breakdown\n\n");
118 report.push_str("| Error Code | Count |\n");
119 report.push_str("|------------|-------|\n");
120
121 let mut sorted_errors: Vec<_> = self.error_counts.iter().collect();
122 sorted_errors.sort_by(|a, b| b.1.cmp(a.1));
123
124 for (code, count) in sorted_errors {
125 report.push_str(&format!("| {} | {} |\n", code, count));
126 }
127 }
128
129 report
130 }
131
132 pub fn to_json(&self) -> String {
134 serde_json::to_string_pretty(self).unwrap_or_else(|_| "{}".to_string())
135 }
136}
137
138fn extract_error_code(message: &str) -> String {
145 if let Some(start) = message.find('E') {
147 let rest = &message[start..];
148 if rest.len() >= 5 && rest[1..5].chars().all(|c| c.is_ascii_digit()) {
149 return rest[..5].to_string();
150 }
151 }
152
153 if let Some(bracket_start) = message.find("[E") {
155 let rest = &message[bracket_start + 1..];
156 if let Some(bracket_end) = rest.find(']') {
157 return rest[..bracket_end].to_string();
158 }
159 }
160
161 "UNKNOWN".to_string()
162}
163
164#[derive(Debug, Clone, Serialize, Deserialize)]
166pub struct TranspilationResult {
167 pub rust_code: String,
169
170 pub compiles: bool,
172
173 pub errors: Vec<String>,
175
176 pub warnings: usize,
178}
179
180impl TranspilationResult {
181 pub fn success(rust_code: String) -> Self {
183 Self { rust_code, compiles: true, errors: Vec::new(), warnings: 0 }
184 }
185
186 pub fn failure(rust_code: String, errors: Vec<String>) -> Self {
188 Self { rust_code, compiles: false, errors, warnings: 0 }
189 }
190}
191
192#[derive(Debug, Clone, Default, Serialize, Deserialize)]
194pub struct TierMetrics {
195 pub tier_name: String,
197 pub total_files: u64,
199 pub transpile_success: u64,
201 pub compile_success: u64,
203 pub transpile_failures: u64,
205 pub compile_failures: u64,
207}
208
209impl TierMetrics {
210 pub fn new(name: &str) -> Self {
212 Self { tier_name: name.to_string(), ..Default::default() }
213 }
214
215 pub fn transpile_rate(&self) -> f64 {
217 contract_pre_configuration!();
218 if self.total_files == 0 {
219 0.0
220 } else {
221 self.transpile_success as f64 / self.total_files as f64
222 }
223 }
224
225 pub fn compile_rate(&self) -> f64 {
227 if self.total_files == 0 {
228 0.0
229 } else {
230 self.compile_success as f64 / self.total_files as f64
231 }
232 }
233}
234
235#[derive(Debug, Clone, Default, Serialize, Deserialize)]
237pub struct ConvergenceReport {
238 pub tiers: Vec<TierMetrics>,
240}
241
242impl ConvergenceReport {
243 pub fn new() -> Self {
245 Self::default()
246 }
247
248 pub fn add_tier(&mut self, tier: TierMetrics) {
250 self.tiers.push(tier);
251 }
252
253 pub fn overall_transpile_rate(&self) -> f64 {
255 contract_pre_configuration!();
256 let total: u64 = self.tiers.iter().map(|t| t.total_files).sum();
257 let success: u64 = self.tiers.iter().map(|t| t.transpile_success).sum();
258 if total == 0 {
259 0.0
260 } else {
261 success as f64 / total as f64
262 }
263 }
264
265 pub fn overall_compile_rate(&self) -> f64 {
267 let total: u64 = self.tiers.iter().map(|t| t.total_files).sum();
268 let success: u64 = self.tiers.iter().map(|t| t.compile_success).sum();
269 if total == 0 {
270 0.0
271 } else {
272 success as f64 / total as f64
273 }
274 }
275
276 pub fn to_markdown(&self) -> String {
278 let mut report = String::new();
279 report.push_str("## Corpus Convergence Report\n\n");
280 report.push_str("| Tier | Files | Transpile | Compile | Transpile Rate | Compile Rate |\n");
281 report.push_str("|------|-------|-----------|---------|----------------|-------------|\n");
282
283 for tier in &self.tiers {
284 report.push_str(&format!(
285 "| {} | {} | {} | {} | {:.1}% | {:.1}% |\n",
286 tier.tier_name,
287 tier.total_files,
288 tier.transpile_success,
289 tier.compile_success,
290 tier.transpile_rate() * 100.0,
291 tier.compile_rate() * 100.0,
292 ));
293 }
294
295 let total_files: u64 = self.tiers.iter().map(|t| t.total_files).sum();
296 let total_transpile: u64 = self.tiers.iter().map(|t| t.transpile_success).sum();
297 let total_compile: u64 = self.tiers.iter().map(|t| t.compile_success).sum();
298
299 report.push_str(&format!(
300 "| **Total** | **{}** | **{}** | **{}** | **{:.1}%** | **{:.1}%** |\n",
301 total_files,
302 total_transpile,
303 total_compile,
304 self.overall_transpile_rate() * 100.0,
305 self.overall_compile_rate() * 100.0,
306 ));
307
308 report
309 }
310}
311
312#[derive(Debug, Clone, Serialize, Deserialize)]
314pub struct EquivalenceDivergence {
315 pub file: String,
317 pub expected_stdout: String,
319 pub actual_stdout: String,
321 pub expected_exit: i32,
323 pub actual_exit: i32,
325}
326
327#[derive(Debug, Clone, Default, Serialize, Deserialize)]
329pub struct EquivalenceMetrics {
330 pub total_files: u64,
332 pub equivalent: u64,
334 pub divergent: u64,
336 pub errors: u64,
338 pub divergences: Vec<EquivalenceDivergence>,
340}
341
342impl EquivalenceMetrics {
343 pub fn new() -> Self {
345 Self::default()
346 }
347
348 pub fn equivalence_rate(&self) -> f64 {
350 if self.total_files == 0 {
351 0.0
352 } else {
353 self.equivalent as f64 / self.total_files as f64
354 }
355 }
356
357 pub fn to_markdown(&self) -> String {
359 let mut report = String::new();
360 report.push_str("## Semantic Equivalence Report\n\n");
361 report.push_str(&format!(
362 "| Metric | Value |\n\
363 |--------|-------|\n\
364 | Total Files | {} |\n\
365 | Equivalent | {} |\n\
366 | Divergent | {} |\n\
367 | Errors | {} |\n\
368 | Equivalence Rate | {:.1}% |\n",
369 self.total_files,
370 self.equivalent,
371 self.divergent,
372 self.errors,
373 self.equivalence_rate() * 100.0,
374 ));
375
376 if !self.divergences.is_empty() {
377 report.push_str("\n### Divergences\n\n");
378 for d in &self.divergences {
379 report.push_str(&format!(
380 "- **{}**: exit {} vs {} | stdout differs: {}\n",
381 d.file,
382 d.expected_exit,
383 d.actual_exit,
384 d.expected_stdout != d.actual_stdout,
385 ));
386 }
387 }
388
389 report
390 }
391}
392
393#[cfg(test)]
394mod tests {
395 use super::*;
396
397 #[test]
398 fn test_extract_error_code_standard() {
399 assert_eq!(extract_error_code("E0308: mismatched types"), "E0308");
400 }
401
402 #[test]
403 fn test_extract_error_code_with_brackets() {
404 assert_eq!(extract_error_code("error[E0502]: cannot borrow"), "E0502");
405 }
406
407 #[test]
408 fn test_extract_error_code_unknown() {
409 assert_eq!(extract_error_code("some random error"), "UNKNOWN");
410 }
411
412 #[test]
413 fn test_extract_error_code_partial() {
414 assert_eq!(extract_error_code("E03"), "UNKNOWN");
415 }
416
417 #[test]
420 fn test_tier_metrics_new() {
421 let tier = TierMetrics::new("chapter-1");
422 assert_eq!(tier.tier_name, "chapter-1");
423 assert_eq!(tier.total_files, 0);
424 assert_eq!(tier.transpile_success, 0);
425 }
426
427 #[test]
428 fn test_tier_metrics_transpile_rate_empty() {
429 let tier = TierMetrics::new("empty");
430 assert_eq!(tier.transpile_rate(), 0.0);
431 }
432
433 #[test]
434 fn test_tier_metrics_transpile_rate() {
435 let mut tier = TierMetrics::new("test");
436 tier.total_files = 10;
437 tier.transpile_success = 8;
438 assert!((tier.transpile_rate() - 0.8).abs() < 0.001);
439 }
440
441 #[test]
442 fn test_tier_metrics_compile_rate_empty() {
443 let tier = TierMetrics::new("empty");
444 assert_eq!(tier.compile_rate(), 0.0);
445 }
446
447 #[test]
448 fn test_tier_metrics_compile_rate() {
449 let mut tier = TierMetrics::new("test");
450 tier.total_files = 10;
451 tier.compile_success = 7;
452 assert!((tier.compile_rate() - 0.7).abs() < 0.001);
453 }
454
455 #[test]
458 fn test_convergence_report_new_empty() {
459 let report = ConvergenceReport::new();
460 assert!(report.tiers.is_empty());
461 assert_eq!(report.overall_transpile_rate(), 0.0);
462 assert_eq!(report.overall_compile_rate(), 0.0);
463 }
464
465 #[test]
466 fn test_convergence_report_add_tier() {
467 let mut report = ConvergenceReport::new();
468 let mut tier = TierMetrics::new("tier1");
469 tier.total_files = 10;
470 tier.transpile_success = 9;
471 tier.compile_success = 7;
472 report.add_tier(tier);
473 assert_eq!(report.tiers.len(), 1);
474 }
475
476 #[test]
477 fn test_convergence_report_overall_rates() {
478 let mut report = ConvergenceReport::new();
479 let mut t1 = TierMetrics::new("t1");
480 t1.total_files = 10;
481 t1.transpile_success = 8;
482 t1.compile_success = 6;
483 let mut t2 = TierMetrics::new("t2");
484 t2.total_files = 10;
485 t2.transpile_success = 10;
486 t2.compile_success = 8;
487 report.add_tier(t1);
488 report.add_tier(t2);
489
490 assert!((report.overall_transpile_rate() - 0.9).abs() < 0.001);
491 assert!((report.overall_compile_rate() - 0.7).abs() < 0.001);
492 }
493
494 #[test]
495 fn test_convergence_report_to_markdown() {
496 let mut report = ConvergenceReport::new();
497 let mut tier = TierMetrics::new("chapter-1");
498 tier.total_files = 20;
499 tier.transpile_success = 18;
500 tier.compile_success = 15;
501 report.add_tier(tier);
502
503 let md = report.to_markdown();
504 assert!(md.contains("Corpus Convergence Report"));
505 assert!(md.contains("chapter-1"));
506 assert!(md.contains("20"));
507 assert!(md.contains("18"));
508 assert!(md.contains("15"));
509 assert!(md.contains("Total"));
510 }
511
512 #[test]
515 fn test_equivalence_metrics_new_empty() {
516 let metrics = EquivalenceMetrics::new();
517 assert_eq!(metrics.total_files, 0);
518 assert_eq!(metrics.equivalence_rate(), 0.0);
519 }
520
521 #[test]
522 fn test_equivalence_metrics_rate() {
523 let mut metrics = EquivalenceMetrics::new();
524 metrics.total_files = 20;
525 metrics.equivalent = 18;
526 metrics.divergent = 2;
527 assert!((metrics.equivalence_rate() - 0.9).abs() < 0.001);
528 }
529
530 #[test]
531 fn test_equivalence_metrics_to_markdown() {
532 let mut metrics = EquivalenceMetrics::new();
533 metrics.total_files = 10;
534 metrics.equivalent = 8;
535 metrics.divergent = 1;
536 metrics.errors = 1;
537
538 let md = metrics.to_markdown();
539 assert!(md.contains("Semantic Equivalence Report"));
540 assert!(md.contains("10"));
541 assert!(md.contains("80.0%"));
542 }
543
544 #[test]
545 fn test_equivalence_metrics_to_markdown_with_divergences() {
546 let mut metrics = EquivalenceMetrics::new();
547 metrics.total_files = 5;
548 metrics.equivalent = 4;
549 metrics.divergent = 1;
550 metrics.errors = 0;
551 metrics.divergences.push(EquivalenceDivergence {
552 file: "test.c".to_string(),
553 expected_stdout: "hello".to_string(),
554 actual_stdout: "world".to_string(),
555 expected_exit: 0,
556 actual_exit: 1,
557 });
558
559 let md = metrics.to_markdown();
560 assert!(md.contains("Divergences"));
561 assert!(md.contains("test.c"));
562 assert!(md.contains("exit 0 vs 1"));
563 }
564
565 #[test]
568 fn test_compile_metrics_new_empty() {
569 let metrics = CompileMetrics::new();
570 assert_eq!(metrics.success_rate(), 0.0);
571 assert_eq!(metrics.total_attempts(), 0);
572 assert!(metrics.error_histogram().is_empty());
573 }
574
575 #[test]
576 fn test_compile_metrics_record_success() {
577 let mut metrics = CompileMetrics::new();
578 metrics.record_success();
579 metrics.record_success();
580 assert!((metrics.success_rate() - 1.0).abs() < 0.001);
581 assert_eq!(metrics.successes(), 2);
582 }
583
584 #[test]
585 fn test_compile_metrics_record_failure() {
586 let mut metrics = CompileMetrics::new();
587 metrics.record_success();
588 metrics.record_failure("E0308: mismatched types");
589 metrics.record_failure("E0502: cannot borrow");
590 assert!((metrics.success_rate() - (1.0 / 3.0)).abs() < 0.01);
591 assert_eq!(metrics.failures(), 2);
592 }
593
594 #[test]
595 fn test_compile_metrics_error_histogram() {
596 let mut metrics = CompileMetrics::new();
597 metrics.record_failure("E0308: mismatched");
598 metrics.record_failure("E0308: mismatched again");
599 metrics.record_failure("E0502: borrow");
600 let hist = metrics.error_histogram();
601 assert_eq!(hist.get("E0308"), Some(&2));
602 assert_eq!(hist.get("E0502"), Some(&1));
603 }
604
605 #[test]
606 fn test_compile_metrics_meets_target() {
607 let mut metrics = CompileMetrics::new();
608 for _ in 0..8 {
609 metrics.record_success();
610 }
611 for _ in 0..2 {
612 metrics.record_failure("E0308: test");
613 }
614 assert!(metrics.meets_target(0.80));
615 assert!(!metrics.meets_target(0.90));
616 }
617
618 #[test]
619 fn test_compile_metrics_to_markdown() {
620 let mut metrics = CompileMetrics::new();
621 metrics.record_success();
622 metrics.record_failure("E0308: test");
623 let md = metrics.to_markdown();
624 assert!(md.contains("Compile Success Rate"));
625 assert!(md.contains("50.0%"));
626 assert!(md.contains("E0308"));
627 }
628
629 #[test]
630 fn test_compile_metrics_to_markdown_passing() {
631 let mut metrics = CompileMetrics::new();
632 for _ in 0..10 {
633 metrics.record_success();
634 }
635 let md = metrics.to_markdown();
636 assert!(md.contains("PASS"));
637 assert!(md.contains("100.0%"));
638 }
639
640 #[test]
641 fn test_compile_metrics_reset() {
642 let mut metrics = CompileMetrics::new();
643 metrics.record_success();
644 metrics.record_failure("E0308: test");
645 metrics.reset();
646 assert_eq!(metrics.total_attempts(), 0);
647 assert_eq!(metrics.successes(), 0);
648 assert_eq!(metrics.failures(), 0);
649 assert!(metrics.error_histogram().is_empty());
650 }
651
652 #[test]
653 fn test_compile_metrics_to_json() {
654 let mut metrics = CompileMetrics::new();
655 metrics.record_success();
656 metrics.record_failure("E0308: test");
657 let json = metrics.to_json();
658 assert!(json.contains("\"total_attempts\": 2"));
659 assert!(json.contains("\"successes\": 1"));
660 assert!(json.contains("\"failures\": 1"));
661 assert!(json.contains("E0308"));
662 }
663
664 #[test]
665 fn test_transpilation_result_success() {
666 let result = TranspilationResult::success("fn main() {}".to_string());
667 assert!(result.compiles);
668 assert!(result.errors.is_empty());
669 assert_eq!(result.warnings, 0);
670 }
671
672 #[test]
673 fn test_transpilation_result_failure() {
674 let result = TranspilationResult::failure(
675 "fn main() {}".to_string(),
676 vec!["E0308: mismatched types".to_string()],
677 );
678 assert!(!result.compiles);
679 assert_eq!(result.errors.len(), 1);
680 }
681
682 #[test]
683 fn test_extract_error_code_bracket_fallback() {
684 assert_eq!(extract_error_code("error[E99]: short code"), "E99");
686 }
687
688 #[test]
689 fn test_extract_error_code_bracket_first_e_not_code() {
690 assert_eq!(extract_error_code("EXPECTED [E55]: issue"), "E55");
692 }
693
694 #[test]
695 fn test_compile_metrics_to_markdown_no_errors() {
696 let metrics = CompileMetrics::new();
697 let md = metrics.to_markdown();
698 assert!(md.contains("0.0%"));
699 assert!(md.contains("FAIL")); assert!(!md.contains("Error Breakdown"));
702 }
703}