|
| 1 | +// SPDX-License-Identifier: MPL-2.0 |
| 2 | +// Analyzer.affine — static analysis & paradox detection |
| 3 | +// (ported from compiler/src/Analyzer.res). |
| 4 | +// |
| 5 | +// ── AffineScript port conventions ── |
| 6 | +// * `open Types` + `open Stability` -> `use Types::*;` + `use Stability::*;`. |
| 7 | +// The `Paradox`/`Consequence`/`Discovery` enums and the |
| 8 | +// `operator_behavior_from_position` / `alternative_operator_behaviors` / |
| 9 | +// `is_context_collapse` / `stability_bar` / `consequence_emoji` helpers |
| 10 | +// are reused from Stability.affine verbatim. |
| 11 | +// * ReScript inline-record variants -> positional (matching Stability's |
| 12 | +// positional constructors and Types' positional AST/StabilityFactor ctors). |
| 13 | +// * Backtick `${…}` templates with embedded newlines -> `++` concatenation |
| 14 | +// with `\n`; emoji/box glyphs preserved. `Array.map(...)->Array.joinWith` -> |
| 15 | +// `join(map(...), sep)`; `Array.slice(~start,~end)` -> `xs[a:b]`; |
| 16 | +// `Array.mapWithIndex` -> an index-carrying loop. |
| 17 | +// |
| 18 | +// ── Faithfulness notes: documented gaps / simplifications vs Analyzer.res ── |
| 19 | +// * `analyze_program` is OMITTED. It constructs an `AnalysisResult` whose |
| 20 | +// `stabilityReport` field is a `Types::StabilityReport`, and it does so by |
| 21 | +// calling `Stability::generate_report` — both of which are unconstructable |
| 22 | +// because `StabilityReport.breakdown : Dict<String,Int>` has no value-level |
| 23 | +// introduction in AffineScript (full rationale in Stability.affine's |
| 24 | +// header). The AST walk it performs (paradox detection via |
| 25 | +// `is_context_collapse` on let-names and `operator_behavior_from_position` |
| 26 | +// on +/-/* binaries, plus mutable-state factor accumulation) cannot be |
| 27 | +// surfaced without building that result record, so it is not ported. |
| 28 | +// Consumers that already hold an `AnalysisResult` work: `forensic_analysis` |
| 29 | +// (which only READS `analysis.stabilityReport.factors`) is ported, as is |
| 30 | +// `format_stability_report` (which READS a `StabilityReport`). |
| 31 | +// * Everything else is ported with identical strings/arithmetic: the |
| 32 | +// `Paradox`/`Consequence`/etc. constructors, `trace_causality`, |
| 33 | +// `generate_alternatives`, `forensic_analysis`, `format_paradox`, and |
| 34 | +// `format_stability_report` (including the top-3 recommendation slice). |
| 35 | +// * No `runTests`/Console code exists in Analyzer.res, so nothing else omitted. |
| 36 | + |
| 37 | +module Analyzer; |
| 38 | + |
| 39 | +use prelude::*; |
| 40 | +use string::{join}; |
| 41 | +use Types::*; |
| 42 | +use Stability::*; |
| 43 | + |
| 44 | +// ============================================ |
| 45 | +// Program Analysis (result type) |
| 46 | +// ============================================ |
| 47 | + |
| 48 | +// `stabilityReport` is a `Types::StabilityReport` (phantom `Dict` field — see |
| 49 | +// header); the struct can be declared but not constructed here. |
| 50 | +pub struct AnalysisResult { |
| 51 | + paradoxes: [Paradox], |
| 52 | + consequences: [Consequence], |
| 53 | + stabilityReport: StabilityReport, |
| 54 | + discoveries: [Discovery] |
| 55 | +} |
| 56 | + |
| 57 | +// NOTE: `analyze_program` (Analyzer.res) is intentionally not ported — it |
| 58 | +// builds an `AnalysisResult` containing a `Types::StabilityReport`, which is |
| 59 | +// unconstructable in AffineScript (see header / Stability.affine). |
| 60 | + |
| 61 | +// ============================================ |
| 62 | +// Causality Tracing |
| 63 | +// ============================================ |
| 64 | + |
| 65 | +pub struct CausalityChain { |
| 66 | + symptom: String, |
| 67 | + symptomLocation: Location, |
| 68 | + chain: [(String, Location)], |
| 69 | + rootCause: String, |
| 70 | + rootLocation: Location |
| 71 | +} |
| 72 | + |
| 73 | +pub fn trace_causality(symptom: String, symptom_loc: Location, prog: Program) -> CausalityChain { |
| 74 | + // Simplified causality tracing — in a real implementation this would track |
| 75 | + // data flow through the AST. |
| 76 | + #{ symptom: symptom, |
| 77 | + symptomLocation: symptom_loc, |
| 78 | + chain: [("Type mismatch propagated from", symptom_loc)], |
| 79 | + rootCause: "Unvalidated user input", |
| 80 | + rootLocation: symptom_loc } |
| 81 | +} |
| 82 | + |
| 83 | +// ============================================ |
| 84 | +// Alternative Code Generation |
| 85 | +// ============================================ |
| 86 | + |
| 87 | +pub struct Alternative { |
| 88 | + description: String, |
| 89 | + code: String, |
| 90 | + stabilityScore: Int, |
| 91 | + improvements: [String] |
| 92 | +} |
| 93 | + |
| 94 | +pub fn generate_alternatives(stmt: Stmt, current_stability: Int) -> [Alternative] { |
| 95 | + match stmt { |
| 96 | + LetStmt(mutable_, name, _type, _value, _loc) => |
| 97 | + if mutable_ { |
| 98 | + [ |
| 99 | + #{ description: "Use immutable binding", |
| 100 | + code: "let " ++ name ++ " = ...", |
| 101 | + stabilityScore: current_stability + 15, |
| 102 | + improvements: [ |
| 103 | + "No mutation risk", |
| 104 | + "Thread-safe by default", |
| 105 | + "Easier to reason about" |
| 106 | + ] } |
| 107 | + ] |
| 108 | + } else { |
| 109 | + [] |
| 110 | + }, |
| 111 | + _ => [] |
| 112 | + } |
| 113 | +} |
| 114 | + |
| 115 | +// ============================================ |
| 116 | +// Forensic Analysis (Deep Dive) |
| 117 | +// ============================================ |
| 118 | + |
| 119 | +pub struct ForensicTrace { |
| 120 | + target: String, |
| 121 | + targetLocation: Location, |
| 122 | + instabilityFactors: [(String, Int, String)], // (description, impact, reason) |
| 123 | + probabilityMap: [(String, Int)], // (scenario, probability %) |
| 124 | + suggestedFix: String |
| 125 | +} |
| 126 | + |
| 127 | +fn factor_description(factor: StabilityFactor) -> String { |
| 128 | + match factor { |
| 129 | + MutableState(_m, _r) => "Mutable state detected", |
| 130 | + TypeInstability(_x) => "Type changed dynamically", |
| 131 | + _ => "Unknown factor" |
| 132 | + } |
| 133 | +} |
| 134 | + |
| 135 | +pub fn forensic_analysis(target: String, target_loc: Location, analysis: AnalysisResult) -> ForensicTrace { |
| 136 | + // Find all factors affecting this target. |
| 137 | + let factors = map(analysis.stabilityReport.factors, |factor| { |
| 138 | + let impact = stability_impact(factor); |
| 139 | + (factor_description(factor), impact, "See stability report") |
| 140 | + }); |
| 141 | + |
| 142 | + let probability_map = [ |
| 143 | + ("Standard behavior", 40), |
| 144 | + ("Positional override", 30), |
| 145 | + ("Context collapse", 20), |
| 146 | + ("Temporal corruption", 10) |
| 147 | + ]; |
| 148 | + |
| 149 | + #{ target: target, |
| 150 | + targetLocation: target_loc, |
| 151 | + instabilityFactors: factors, |
| 152 | + probabilityMap: probability_map, |
| 153 | + suggestedFix: "Move to different position or add explicit type" } |
| 154 | +} |
| 155 | + |
| 156 | +// ============================================ |
| 157 | +// Visualization Formatters |
| 158 | +// ============================================ |
| 159 | + |
| 160 | +pub fn format_paradox(paradox: Paradox) -> String { |
| 161 | + match paradox { |
| 162 | + ContextCollapseKeyword(keyword, line, _depth, _isKw, reason) => |
| 163 | + "⚡ QUANTUM KEYWORD at line " ++ int_to_string(line) ++ "\n" ++ |
| 164 | + " '" ++ keyword ++ "' is both keyword AND identifier\n" ++ |
| 165 | + " Reason: " ++ reason, |
| 166 | + |
| 167 | + PositionalOperator(operator, line, column, behavior, alternatives) => |
| 168 | + "🎲 POSITIONAL OPERATOR at line " ++ int_to_string(line) ++ ":" ++ int_to_string(column) ++ "\n" ++ |
| 169 | + " '" ++ operator ++ "' behaves as: " ++ behavior ++ "\n" ++ |
| 170 | + " Alternative positions:\n" ++ |
| 171 | + join(map(alternatives, |pair| { |
| 172 | + let (col, beh) = pair; |
| 173 | + " Col " ++ int_to_string(col) ++ ": " ++ beh |
| 174 | + }), "\n"), |
| 175 | + |
| 176 | + TypeSuperposition(variable, _line, possible_types, collapse_target) => |
| 177 | + "🌀 TYPE SUPERPOSITION\n" ++ |
| 178 | + " Variable '" ++ variable ++ "' exists as: " ++ join(possible_types, " | ") ++ "\n" ++ |
| 179 | + " Will collapse to: " ++ collapse_target, |
| 180 | + |
| 181 | + ScopeLeakage(variable, declared_line, access_line, _runNumber, leak_reason) => |
| 182 | + "🕐 SCOPE LEAKAGE\n" ++ |
| 183 | + " Variable '" ++ variable ++ "' declared at line " ++ int_to_string(declared_line) ++ "\n" ++ |
| 184 | + " Accessible at line " ++ int_to_string(access_line) ++ "\n" ++ |
| 185 | + " Reason: " ++ leak_reason, |
| 186 | + |
| 187 | + TemporalCorruption(variable, _line, _affectedByRun, mechanism) => |
| 188 | + "⏰ TEMPORAL CORRUPTION\n" ++ |
| 189 | + " Variable '" ++ variable ++ "' affected by historical state\n" ++ |
| 190 | + " Mechanism: " ++ mechanism |
| 191 | + } |
| 192 | +} |
| 193 | + |
| 194 | +fn factor_line(factor: StabilityFactor) -> String { |
| 195 | + let emoji = consequence_emoji(factor); |
| 196 | + let impact = stability_impact(factor); |
| 197 | + let desc = match factor { |
| 198 | + MutableState(mutations, readers) => |
| 199 | + "Mutable state (" ++ int_to_string(mutations) ++ " mutations, " ++ int_to_string(readers) ++ " readers)", |
| 200 | + TypeInstability(reassignments) => |
| 201 | + "Type instability (" ++ int_to_string(reassignments) ++ " reassignments)", |
| 202 | + NullPropagation(depth) => |
| 203 | + "Null propagation (depth " ++ int_to_string(depth) ++ ")", |
| 204 | + _ => "Other factor" |
| 205 | + }; |
| 206 | + emoji ++ " " ++ desc ++ ": " ++ int_to_string(impact) ++ " points" |
| 207 | +} |
| 208 | + |
| 209 | +pub fn format_stability_report(report: StabilityReport) -> String { |
| 210 | + let bar = stability_bar(report.score); |
| 211 | + |
| 212 | + let factor_list = join(map(report.factors, factor_line), "\n "); |
| 213 | + |
| 214 | + // Top-3 recommendations with 1-based numbering. |
| 215 | + let recs = report.recommendations; |
| 216 | + let top = if len(recs) > 3 { recs[0:3] } else { recs }; |
| 217 | + let mut rec_lines = ""; |
| 218 | + let mut i = 0; |
| 219 | + let n = len(top); |
| 220 | + while i < n { |
| 221 | + if i > 0 { rec_lines = rec_lines ++ "\n"; } |
| 222 | + rec_lines = rec_lines ++ " " ++ int_to_string(i + 1) ++ ". " ++ top[i]; |
| 223 | + i = i + 1; |
| 224 | + } |
| 225 | + |
| 226 | + "\n🎯 STABILITY REPORT\n" ++ |
| 227 | + "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n\n" ++ |
| 228 | + bar ++ "\n\n" ++ |
| 229 | + "Breakdown:\n" ++ |
| 230 | + " " ++ factor_list ++ "\n\n" ++ |
| 231 | + "💡 Top Recommendations:\n" ++ |
| 232 | + rec_lines ++ "\n" |
| 233 | +} |
0 commit comments