Skip to content

Commit 852c215

Browse files
hyperpolymathclaude
andcommitted
compiler: port the remaining 8 compiler/src modules -> .affine
Completes the compiler/src ReScript->AffineScript port: all 17 modules are now .affine. 2785 lines across Stability, Pretty, TokenStream, LayerNavigator, Analyzer, FiveWhys, IncrementalLexer, IncrementalParser. All verified green with affinescript check and the full 17-source harness. - Stability: paradox/consequence/discovery detection + stability arithmetic. - Pretty: AST->source pretty-printer (immutable Printer threaded through pp_*). - TokenStream: proc-macro-style token-stream API + of_string lexer. - LayerNavigator: 5-layer (Grammar/Parser/AST/Semantics/Runtime) views; owns Layer. - Analyzer: paradox/report formatters, causality, forensic trace. - FiveWhys: automated root-cause why-chains. - IncrementalLexer / IncrementalParser: edit-driven re-lex / re-parse + splice. Documented omissions (one AffineScript limitation, three call sites): the record-constructor functions generate_report (Stability), analyze_program (Analyzer) and create_layer_views (LayerNavigator) build records whose fields are typed Dict<K,V> in the shipped types. Dict<K,V> is effectively a PHANTOM TYPE in AffineScript -- accepted in annotations but with no value-level introduction (no literal, no constructor, no Dict-returning builtin; the stdlib `dict` is a distinct assoc-list type that does not unify). Those records therefore cannot be constructed; every function that READS such a field is ported. Closing them needs the Dict fields switched to the idiomatic assoc-list representation [(K,V)] (or affinescript's Dict made constructable) -- tracked as a follow-up. format_diagnostic is reimplemented locally (byte-faithful) since the shipped Types.affine omitted it. Confirmed all three prior affinescript bugs (builtin-shadow bit once via a `length` param). The Dict phantom-type is the fourth distinct affinescript limitation found this migration. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0195yA45jSSP7YDPwJSpw4bM
1 parent bfcee30 commit 852c215

8 files changed

Lines changed: 2785 additions & 0 deletions

compiler/src/Analyzer.affine

Lines changed: 233 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,233 @@
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

Comments
 (0)