Skip to main content

logicaffeine_compile/
compile.rs

1//! LOGOS Compilation Pipeline
2//!
3//! This module provides the end-to-end compilation pipeline that transforms
4//! LOGOS source code into executable Rust programs.
5//!
6//! # Pipeline Overview
7//!
8//! ```text
9//! LOGOS Source (.md)
10//!       │
11//!       ▼
12//! ┌───────────────────┐
13//! │  1. Lexer         │ Tokenize source
14//! └─────────┬─────────┘
15//!           ▼
16//! ┌───────────────────┐
17//! │  2. Discovery     │ Type & policy definitions
18//! └─────────┬─────────┘
19//!           ▼
20//! ┌───────────────────┐
21//! │  3. Parser        │ Build AST
22//! └─────────┬─────────┘
23//!           ▼
24//! ┌───────────────────┐
25//! │  4. Analysis      │ Escape, ownership, verification
26//! └─────────┬─────────┘
27//!           ▼
28//! ┌───────────────────┐
29//! │  5. CodeGen       │ Emit Rust source
30//! └─────────┬─────────┘
31//!           ▼
32//!     Rust Source
33//! ```
34//!
35//! # Compilation Functions
36//!
37//! | Function | Analysis | Use Case |
38//! |----------|----------|----------|
39//! | [`compile_to_rust`] | Escape only | Basic compilation |
40//! | [`compile_to_rust_checked`] | Escape + Ownership | Use with `--check` flag |
41//! | `compile_to_rust_verified` | All + Z3 | Formal verification (requires `verification` feature) |
42//! | [`compile_project`] | Multi-file | Projects with imports |
43//! | [`compile_and_run`] | Full + Execute | Development workflow |
44//!
45//! # Examples
46//!
47//! ## Basic Compilation
48//!
49//! ```
50//! # use logicaffeine_compile::compile::compile_to_rust;
51//! # use logicaffeine_compile::ParseError;
52//! # fn main() -> Result<(), ParseError> {
53//! let source = "## Main\nLet x be 5.\nShow x.";
54//! let rust_code = compile_to_rust(source)?;
55//! // rust_code contains:
56//! // fn main() {
57//! //     let x = 5;
58//! //     println!("{}", x);
59//! // }
60//! # Ok(())
61//! # }
62//! ```
63//!
64//! ## With Ownership Checking
65//!
66//! ```
67//! # use logicaffeine_compile::compile::compile_to_rust_checked;
68//! let source = "## Main\nLet x be 5.\nGive x to y.\nShow x.";
69//! let result = compile_to_rust_checked(source);
70//! // Returns Err: "x has already been given away"
71//! ```
72
73use std::collections::{HashMap, HashSet};
74use std::fs;
75use std::io::Write;
76use std::path::Path;
77use std::process::Command;
78
79// Runtime crates paths (relative to workspace root)
80const CRATES_DATA_PATH: &str = "crates/logicaffeine_data";
81const CRATES_SYSTEM_PATH: &str = "crates/logicaffeine_system";
82
83use std::fmt::Write as FmtWrite;
84
85use crate::analysis::{DiscoveryPass, EscapeChecker, OwnershipChecker, PolicyRegistry};
86use crate::arena::Arena;
87use crate::arena_ctx::AstContext;
88use crate::ast::{Expr, MatchArm, Stmt, TypeExpr};
89use crate::ast::stmt::{BinaryOpKind, ClosureBody, Literal, Pattern, ReadSource, SelectBranch, StringPart};
90use crate::codegen::{codegen_program, generate_c_header, generate_python_bindings, generate_typescript_bindings};
91use crate::diagnostic::{parse_rustc_json, translate_diagnostics, LogosError};
92use crate::drs::WorldState;
93use crate::error::ParseError;
94use crate::intern::Interner;
95use crate::lexer::Lexer;
96use crate::parser::Parser;
97use crate::sourcemap::SourceMap;
98
99/// A declared external crate dependency from a `## Requires` block.
100#[derive(Debug, Clone)]
101pub struct CrateDependency {
102    pub name: String,
103    pub version: String,
104    pub features: Vec<String>,
105}
106
107/// Full compilation output including generated Rust code and extracted dependencies.
108#[derive(Debug)]
109pub struct CompileOutput {
110    pub rust_code: String,
111    pub dependencies: Vec<CrateDependency>,
112    /// Generated C header content (populated when C exports exist).
113    pub c_header: Option<String>,
114    /// Generated Python ctypes bindings (populated when C exports exist).
115    pub python_bindings: Option<String>,
116    /// Generated TypeScript type declarations (.d.ts content, populated when C exports exist).
117    pub typescript_types: Option<String>,
118    /// Generated TypeScript FFI bindings (.js content, populated when C exports exist).
119    pub typescript_bindings: Option<String>,
120}
121
122/// Interpret LOGOS source and return output as a string.
123///
124/// Runs the full pipeline (lex → discovery → parse → interpret) without
125/// generating Rust code. Useful for sub-second feedback during development.
126///
127/// # Arguments
128///
129/// * `source` - LOGOS source code as a string
130///
131/// # Returns
132///
133/// The collected output from `Show` statements, joined by newlines.
134///
135/// # Errors
136///
137/// Returns [`ParseError`] if parsing fails or the interpreter encounters
138/// a runtime error.
139pub fn interpret_program(source: &str) -> Result<String, ParseError> {
140    let result = crate::ui_bridge::interpret_for_ui_sync(source);
141    if let Some(err) = result.error {
142        Err(ParseError {
143            kind: crate::error::ParseErrorKind::Custom(err),
144            span: crate::token::Span::default(),
145        })
146    } else {
147        Ok(result.lines.join("\n"))
148    }
149}
150
151/// Parse LOGOS source and execute it on the register bytecode VM, returning the
152/// captured output. Uses the SAME front-end as the tree-walker
153/// ([`crate::ui_bridge::with_parsed_program`]: lex → MWE → discovery → parse),
154/// so differential tests compare two engines on one program, never two
155/// programs. Returns `Err` for programs outside the VM's supported subset (the
156/// compiler reports `vm: unsupported …`).
157pub fn vm_run_source(source: &str) -> Result<String, String> {
158    crate::ui_bridge::with_parsed_program(source, |parsed, interner| match parsed {
159        Ok((stmts, types, policies)) => {
160            let (output, error) =
161                crate::vm::run_to_outcome(stmts, interner, Some(types), Some(&policies));
162            match error {
163                None => Ok(output),
164                Some(e) => Err(e),
165            }
166        }
167        Err(advice) => Err(advice),
168    })
169}
170
171/// What a program run produced: every output line emitted before completion or
172/// failure, plus the error if it failed. The differential contract is
173/// `vm_outcome(src) == tw_outcome(src)` — output AND error.
174#[derive(Debug, Clone, PartialEq, Eq)]
175pub struct RunOutcome {
176    pub output: String,
177    pub error: Option<String>,
178}
179
180/// Run `source` on the TREE-WALKER (the oracle), capturing partial output and
181/// the error, if any. This entry point always uses the tree-walker — the
182/// dispatching `interpret_for_ui_sync` now runs the VM for the sync path, and
183/// the differential suites need the oracle by itself.
184pub fn tw_outcome(source: &str) -> RunOutcome {
185    // Deterministic oracle: no relay transport, so `Connect` runs as a single-node local no-op (the
186    // networking analogue of the fixed clock). Only affects a `Connect` program; inert otherwise.
187    crate::concurrency::net_inbox::set_net_offline(true);
188    crate::ui_bridge::with_parsed_program(source, |parsed, interner| match parsed {
189        Ok((stmts, types, policies)) => {
190            let force_async = crate::interpreter::needs_async(stmts);
191            let result = crate::ui_bridge::run_treewalker(
192                stmts, types, policies, interner, force_async, &[],
193            );
194            RunOutcome { output: result.lines.join("\n"), error: result.error }
195        }
196        Err(advice) => RunOutcome { output: String::new(), error: Some(advice) },
197    })
198}
199
200/// Run `source` on the bytecode VM, capturing partial output and the error,
201/// if any. Same front-end as [`tw_outcome`].
202pub fn vm_outcome(source: &str) -> RunOutcome {
203    crate::ui_bridge::with_parsed_program(source, |parsed, interner| match parsed {
204        Ok((stmts, types, policies)) => {
205            let (output, error) =
206                crate::vm::run_to_outcome(stmts, interner, Some(types), Some(&policies));
207            RunOutcome { output, error }
208        }
209        Err(advice) => RunOutcome { output: String::new(), error: Some(advice) },
210    })
211}
212
213/// The VM outcome for a CONCURRENT program — drives the bytecode VM on the cooperative scheduler
214/// (`VmTask` on `run_with_seed`, seed 0) exactly as the browser/playground does, so a `Pipe`/task/
215/// `Select` program runs on the VM byte-identical to the tree-walker's async path. The plain
216/// [`vm_outcome`] errors on any concurrency op (`requires the scheduler driver`); this is the oracle
217/// the AOT concurrency lock compares against so tw == VM == AOT is verifiable for the concurrent set.
218#[cfg(not(target_arch = "wasm32"))]
219pub fn vm_outcome_concurrent(source: &str) -> RunOutcome {
220    let r = crate::ui_bridge::run_vm_concurrent(source);
221    RunOutcome { output: r.lines.join("\n"), error: r.error }
222}
223
224/// The VM outcome for a peer-NETWORKING program — drives the resumable bytecode VM and services each
225/// `VmBlock::Net*` through the shared LOCAL `NetInbox` (the same inbox the tree-walker's offline
226/// mode uses), so `Listen`/`Send`/`Sync`/`PeerAgent` run on the VM byte-identical to the tree-walker.
227/// The AOT networking lock compares against this so tw == VM == AOT holds for the local-mode net set.
228#[cfg(not(target_arch = "wasm32"))]
229pub fn vm_outcome_net(source: &str) -> RunOutcome {
230    // Deterministic oracle: offline single node, so `Connect` is a local no-op (matches `tw_outcome`).
231    crate::concurrency::net_inbox::set_net_offline(true);
232    let r = futures::executor::block_on(crate::ui_bridge::run_vm_net_async(source));
233    RunOutcome { output: r.lines.join("\n"), error: r.error }
234}
235
236/// [`tw_outcome`] with the program argument vector for the `args()` system
237/// native (full argv; index 0 is the program name) — the seam the benchmark
238/// corpus differential uses to run `main.lg` programs that read their size
239/// from argv.
240pub fn tw_outcome_with_args(source: &str, program_args: &[String]) -> RunOutcome {
241    crate::ui_bridge::with_parsed_program(source, |parsed, interner| match parsed {
242        Ok((stmts, types, policies)) => {
243            let force_async = crate::interpreter::needs_async(stmts);
244            let result = crate::ui_bridge::run_treewalker(
245                stmts, types, policies, interner, force_async, program_args,
246            );
247            RunOutcome { output: result.lines.join("\n"), error: result.error }
248        }
249        Err(advice) => RunOutcome { output: String::new(), error: Some(advice) },
250    })
251}
252
253/// [`vm_outcome`] with the program argument vector and an optional private
254/// native tier (so a differential test can observe THAT program's JIT compile
255/// counters in isolation — the process-wide tier's counters are shared by
256/// every test in the binary).
257pub fn vm_outcome_with_args(
258    source: &str,
259    program_args: &[String],
260    tier: Option<&dyn crate::vm::NativeTier>,
261) -> RunOutcome {
262    crate::ui_bridge::with_parsed_program(source, |parsed, interner| match parsed {
263        Ok((stmts, types, policies)) => {
264            let (output, error) = crate::vm::run_to_outcome_with_args(
265                stmts,
266                interner,
267                Some(types),
268                Some(&policies),
269                program_args,
270                tier,
271            );
272            RunOutcome { output, error }
273        }
274        Err(advice) => RunOutcome { output: String::new(), error: Some(advice) },
275    })
276}
277
278/// [`vm_outcome_with_args`], but the statements pass through the run-path optimizer
279/// at an explicit hotness `tier` (HOTSWAP §4) before reaching the VM — the seam the
280/// `tier_invariance` gate drives at every tier T0..T3 to assert byte-identical
281/// output. `tier = Tier::T3` reproduces today's optimized run path.
282pub fn vm_outcome_tiered(
283    source: &str,
284    program_args: &[String],
285    tier: crate::optimization::Tier,
286    native_tier: Option<&dyn crate::vm::NativeTier>,
287) -> RunOutcome {
288    crate::ui_bridge::with_optimized_program_tiered(source, tier, |parsed, interner| match parsed {
289        Ok((stmts, types, policies)) => {
290            let (output, error) = crate::vm::run_to_outcome_with_args(
291                stmts,
292                interner,
293                Some(types),
294                Some(&policies),
295                program_args,
296                native_tier,
297            );
298            RunOutcome { output, error }
299        }
300        Err(advice) => RunOutcome { output: String::new(), error: Some(advice) },
301    })
302}
303
304/// [`vm_outcome_with_args`] driven through the BACKGROUND native compiler (HOTSWAP §6):
305/// hot functions compile on a worker thread off the interpreter's path. Requires a
306/// process-installed tier (the P8 gate installs one first). The compiled chain is
307/// identical to the synchronous one — only WHEN it is produced differs — so output
308/// must match the synchronous/tree-walker engines exactly.
309#[cfg(not(target_arch = "wasm32"))]
310pub fn vm_outcome_bg(source: &str, program_args: &[String]) -> RunOutcome {
311    crate::ui_bridge::with_parsed_program(source, |parsed, interner| match parsed {
312        Ok((stmts, types, policies)) => {
313            let (output, error) = crate::vm::run_to_outcome_bg(
314                stmts,
315                interner,
316                Some(types),
317                Some(&policies),
318                program_args,
319            );
320            RunOutcome { output, error }
321        }
322        Err(advice) => RunOutcome { output: String::new(), error: Some(advice) },
323    })
324}
325
326/// Compile LOGOS source to Rust source code.
327///
328/// This is the basic compilation function that runs lexing, parsing, and
329/// escape analysis before generating Rust code.
330///
331/// # Arguments
332///
333/// * `source` - LOGOS source code as a string
334///
335/// # Returns
336///
337/// Generated Rust source code on success.
338///
339/// # Errors
340///
341/// Returns [`ParseError`] if:
342/// - Lexical analysis fails (invalid tokens)
343/// - Parsing fails (syntax errors)
344/// - Escape analysis fails (zone-local values escaping)
345///
346/// # Example
347///
348/// ```
349/// # use logicaffeine_compile::compile::compile_to_rust;
350/// # use logicaffeine_compile::ParseError;
351/// # fn main() -> Result<(), ParseError> {
352/// let source = "## Main\nLet x be 5.\nShow x.";
353/// let rust_code = compile_to_rust(source)?;
354/// assert!(rust_code.contains("let x = 5;"));
355/// # Ok(())
356/// # }
357/// ```
358pub fn compile_to_rust(source: &str) -> Result<String, ParseError> {
359    compile_program_full(source).map(|o| o.rust_code)
360}
361
362/// Compile in **Mode B (deterministic replay)**: identical to [`compile_to_rust`]
363/// except a `Select` lowers to the *seeded* winner-pick that shares the
364/// interpreter's choice function, so a binary run under `LOGOS_SEED=…` reproduces
365/// the interpreter's selection. Used by `largo build --deterministic` and the
366/// seeded differential harness. Non-`Select` emission is unchanged.
367pub fn compile_to_rust_deterministic(source: &str) -> Result<String, ParseError> {
368    crate::codegen::with_seeded_select(|| compile_to_rust(source))
369}
370
371/// Compile imperative LOGOS to Rust with an extracted math/logic module bundled in.
372/// `proven` is a main-less Rust module body (the Forge's extraction); it is emitted
373/// as `pub mod proven { … } use proven::*;` so the imperative program can call its
374/// functions / `check_*` predicates by name. See [`compile_program_full_with_proven`].
375pub fn compile_to_rust_with_proven(source: &str, proven: &str) -> Result<String, ParseError> {
376    compile_program_full_with_proven(source, Some(proven)).map(|o| o.rust_code)
377}
378
379/// Which optimizations actually FIRE for `source`: an optimization is "used" when
380/// disabling it (via a file-level `## No <X>` decorator) changes the generated
381/// Rust. Lets the benchmarks UI show, per program, which optimizations it USES vs
382/// the full set it CAN use. O(number of optimizations) compiles — intended for
383/// per-benchmark, not hot-path, use. Returns the decorator keywords of the firing
384/// optimizations (in registry order).
385pub fn optimizations_used(source: &str) -> Vec<&'static str> {
386    let base = match compile_to_rust(source) {
387        Ok(r) => r,
388        Err(_) => return Vec::new(),
389    };
390    crate::optimization::REGISTRY
391        .iter()
392        .filter(|m| {
393            let decorated = crate::optimization::decorate_source(source, &[m.keyword]);
394            compile_to_rust(&decorated).map(|r| r != base).unwrap_or(false)
395        })
396        .map(|m| m.keyword)
397        .collect()
398}
399
400/// Compile `source` while recording which optimizations actually FIRED — the
401/// honest, single-compile alternative to [`optimizations_used`]'s O(N) diff. An
402/// optimization is "fired" when it actually changed the program or emitted its
403/// optimized form for this compile, under the effective (normalized) config.
404/// Reusable: any caller can compile a program and learn what fired, for tracing
405/// and tooling.
406pub fn compile_program_traced(
407    source: &str,
408) -> Result<(CompileOutput, crate::optimization::FiredOptimizations), ParseError> {
409    crate::optimize::begin_fired_trace();
410    let result = compile_program_full(source);
411    // Always end the trace (even on a parse error) so the thread-local never
412    // leaks into a later compile on this thread.
413    let fired = crate::optimize::end_fired_trace().unwrap_or_default();
414    result.map(|o| (o, fired))
415}
416
417/// The decorator keywords of the optimizations that fired for `source` (registry
418/// order), under the default/effective config. One compile, exact.
419pub fn optimizations_fired(source: &str) -> Vec<&'static str> {
420    compile_program_traced(source)
421        .map(|(_, fired)| fired.keywords())
422        .unwrap_or_default()
423}
424
425/// The optimizations that fired for `source` under an explicit config — re-traces
426/// an arbitrary toggle state (turning some off can make others fire). Realised by
427/// disabling that config's off-keywords on the source, then tracing.
428pub fn optimizations_fired_with(
429    source: &str,
430    cfg: &crate::optimization::OptimizationConfig,
431) -> Vec<&'static str> {
432    let disabled: Vec<&'static str> = cfg.disabled_keywords().collect();
433    let decorated = crate::optimization::decorate_source(source, &disabled);
434    optimizations_fired(&decorated)
435}
436
437/// One compile returning the generated Rust, the fired-optimization keywords, AND
438/// the `(winner, loser)` precedence decisions made — what an interactive viewer
439/// calls per toggle state to show the firing opts and the conflicts that occurred.
440pub fn compile_to_rust_traced(
441    source: &str,
442) -> Result<(String, Vec<&'static str>, Vec<(&'static str, &'static str)>), ParseError> {
443    crate::optimize::begin_fired_trace();
444    let result = compile_program_full(source);
445    let fired = crate::optimize::end_fired_trace().unwrap_or_default().keywords();
446    let preempted = preempted_keywords();
447    result.map(|o| (o.rust_code, fired, preempted))
448}
449
450/// The `(winner, loser)` precedence decisions the compiler made for `source` — the
451/// conflicts traced at their source (`mark_preempted`), under the effective config.
452pub fn optimization_preemptions(source: &str) -> Vec<(&'static str, &'static str)> {
453    crate::optimize::begin_fired_trace();
454    let _ = compile_program_full(source);
455    let _ = crate::optimize::end_fired_trace();
456    preempted_keywords()
457}
458
459/// Convert the trace's `(Opt, Opt)` preemption edges to `(keyword, keyword)`.
460fn preempted_keywords() -> Vec<(&'static str, &'static str)> {
461    crate::optimize::end_preempted_trace()
462        .into_iter()
463        .map(|(w, l)| (w.meta().keyword, l.meta().keyword))
464        .collect()
465}
466
467/// The per-program DEPENDENCY edges for `source`, discovered by evaluating with
468/// all optimizations on and probing: disabling a fired optimization `dep` that
469/// makes another fired optimization `dependent` STOP firing means `dependent`
470/// depended on `dep` for this program. Stops already explained by a declared
471/// `requires`-cascade (`normalize` would disable the dependent anyway) are
472/// excluded — those are captured by the static registry graph. What remains are
473/// the EMERGENT, program-specific dependencies (e.g. dead-code elimination only
474/// had work because scalarization produced the dead code): returned as
475/// `(dependent, dep)` keyword pairs, sorted and deduped (deterministic).
476///
477/// This is offline analysis (one compile per fired optimization) — it is baked
478/// into the benchmark data, never run in the hot path.
479pub fn optimization_dependencies(source: &str) -> Vec<(&'static str, &'static str)> {
480    use crate::optimization::{by_keyword, decorate_source, OptimizationConfig};
481    use std::collections::BTreeSet;
482
483    let base: Vec<&'static str> = optimizations_fired(source);
484    let base_set: BTreeSet<&'static str> = base.iter().copied().collect();
485    let mut out: Vec<(&'static str, &'static str)> = Vec::new();
486    for &dep in &base {
487        let dep_opt = match by_keyword(dep) {
488            Some(o) => o,
489            None => continue,
490        };
491        // What the declared `requires`-cascade disables when `dep` is off.
492        let mut cfg = OptimizationConfig::all_on();
493        cfg.set(dep_opt, false);
494        cfg.normalize();
495        let off: BTreeSet<&'static str> =
496            optimizations_fired(&decorate_source(source, &[dep])).into_iter().collect();
497        for &dependent in &base_set {
498            if dependent == dep || off.contains(dependent) {
499                continue;
500            }
501            let dependent_opt = match by_keyword(dependent) {
502                Some(o) => o,
503                None => continue,
504            };
505            // Skip stops the static graph already explains (normalize disabled it).
506            if cfg.is_on(dependent_opt) {
507                out.push((dependent, dep));
508            }
509        }
510    }
511    out.sort();
512    out.dedup();
513    out
514}
515
516/// The complete per-program optimization graph from one all-on evaluation: the
517/// optimizations that `fired`, the `blockers` (`(winner, loser)` preemptions that
518/// occurred), and the emergent `dependencies` (`(dependent, dep)` — see
519/// [`optimization_dependencies`]). Everything the menu-tree needs, baked once per
520/// benchmark so the UI never probes in the hot path. All on the AOT codegen path
521/// (the one that produces the shown Rust), so the three views agree.
522pub fn optimization_graph(
523    source: &str,
524) -> (
525    Vec<&'static str>,
526    Vec<(&'static str, &'static str)>,
527    Vec<(&'static str, &'static str)>,
528) {
529    (
530        optimizations_fired(source),
531        optimization_preemptions(source),
532        optimization_dependencies(source),
533    )
534}
535
536/// The optimizations that fired on the RUN path — the VM/interpreter optimizer
537/// (`optimize_for_run`) — for `source`. The run-path analogue of
538/// [`optimizations_fired`] (which traces the AOT codegen path); it surfaces the
539/// run-path-only passes — inlining, loop-carried CSE, float strength reduction —
540/// the AOT trace cannot see. Respects file-level `## No <opt>` decorators.
541pub fn optimizations_fired_run(source: &str) -> Vec<&'static str> {
542    crate::optimize::begin_fired_trace();
543    crate::ui_bridge::with_optimized_program(source, |_parsed, _interner| {});
544    crate::optimize::end_fired_trace()
545        .map(|fired| fired.keywords())
546        .unwrap_or_default()
547}
548
549/// The optimizations that fired while compiling `source` to VM bytecode — the
550/// third path, after AOT codegen and the run-path optimizer. Surfaces the
551/// VM-compile-time opts (constant-divisor magic division, VM `i32` narrowing)
552/// the other two traces cannot see. Compiles only (no execution), so it is fast
553/// and needs no program arguments.
554pub fn optimizations_fired_vm(source: &str) -> Vec<&'static str> {
555    crate::optimize::begin_fired_trace();
556    crate::ui_bridge::with_optimized_program(source, |parsed, interner| {
557        if let Ok((stmts, types, _policies)) = parsed {
558            let oracle = crate::optimize::oracle_analyze_with(stmts, interner);
559            let _ = crate::vm::Compiler::compile_with_oracle(stmts, interner, Some(types), Some(oracle));
560        }
561    });
562    crate::optimize::end_fired_trace()
563        .map(|fired| fired.keywords())
564        .unwrap_or_default()
565}
566
567/// Compile LOGOS source to C code (benchmark-only subset).
568///
569/// Produces a self-contained C file with embedded runtime that can be
570/// compiled with `gcc -O2 -o program output.c`.
571pub fn compile_to_c(source: &str) -> Result<String, ParseError> {
572    let mut interner = Interner::new();
573    let mut lexer = Lexer::new(source, &mut interner);
574    let tokens = lexer.tokenize();
575
576    let (type_registry, _policy_registry) = {
577        let mut discovery = DiscoveryPass::new(&tokens, &mut interner);
578        let result = discovery.run_full();
579        (result.types, result.policies)
580    };
581    let codegen_registry = type_registry.clone();
582
583    let mut world_state = WorldState::new();
584    let expr_arena = Arena::new();
585    let term_arena = Arena::new();
586    let np_arena = Arena::new();
587    let sym_arena = Arena::new();
588    let role_arena = Arena::new();
589    let pp_arena = Arena::new();
590    let stmt_arena: Arena<Stmt> = Arena::new();
591    let imperative_expr_arena: Arena<Expr> = Arena::new();
592    let type_expr_arena: Arena<TypeExpr> = Arena::new();
593
594    let ast_ctx = AstContext::with_types(
595        &expr_arena, &term_arena, &np_arena, &sym_arena,
596        &role_arena, &pp_arena, &stmt_arena, &imperative_expr_arena,
597        &type_expr_arena,
598    );
599
600    let mut parser = Parser::new(tokens, &mut world_state, &mut interner, ast_ctx, type_registry);
601    let stmts = parser.parse_program()?;
602    let stmts = crate::optimize::optimize_program(stmts, &imperative_expr_arena, &stmt_arena, &mut interner, &crate::optimization::OptimizationConfig::from_env());
603
604    Ok(crate::codegen_c::codegen_program_c(&stmts, &codegen_registry, &interner))
605}
606
607/// Compile LOGOS source DIRECTLY to a self-contained WebAssembly module — no rustc, no cargo,
608/// no wasm-bindgen, no toolchain. Uses the SAME front-end as [`vm_outcome`]
609/// ([`crate::ui_bridge::with_parsed_program`] → [`crate::vm::Compiler::compile_with_types`]),
610/// so the emitted module runs the IDENTICAL bytecode the VM does; the AOT WebAssembly backend
611/// then lowers the whole program to `.wasm`.
612///
613/// Returns the bytes of a `.wasm` file. A program outside the backend's supported fragment
614/// yields a [`ParseError`] carrying the backend's `unsupported …` reason — the corpus lock
615/// turns that into a tracked, shrinking gap rather than a silent skip.
616pub fn compile_to_wasm(source: &str) -> Result<Vec<u8>, ParseError> {
617    fn custom(msg: String) -> ParseError {
618        ParseError { kind: crate::error::ParseErrorKind::Custom(msg), span: crate::token::Span::default() }
619    }
620    crate::ui_bridge::with_parsed_program(source, |parsed, interner| {
621        let (stmts, types, policies) = parsed.map_err(custom)?;
622        // Compile with the SAME Oracle the VM/native tier use (`run_to_outcome`), so the AOT receives
623        // the optimized bytecode — DivPow2 / MagicDivU (magic-reciprocal division) and the Oracle-proven
624        // bounds-check-elimination forms (IndexUnchecked / SetIndexUnchecked / RegionBoundsGuard). The
625        // AOT lowers each (division bit-exact to the VM's `magic_eval`; the unchecked forms keep the
626        // bounds check, a safe superset), so WASM == VM stays exact while the two share one bytecode.
627        let oracle = crate::optimize::oracle_analyze_with(stmts, interner);
628        let program = crate::vm::Compiler::compile_with_oracle(stmts, interner, Some(types), Some(oracle))
629            .map_err(custom)?;
630        crate::vm::wasm::assemble_program(&program, &policies, interner).map_err(|e| custom(e.to_string()))
631    })
632}
633
634/// Compile LOGOS source to a LINKED WebAssembly module that uses the real `logicaffeine_base::BigInt`
635/// runtime for arbitrary-precision integer arithmetic: an overflowing integer expression
636/// (`Show 99999999999 * 99999999999`, `Show 2 to the power of 200`) computes the EXACT big number —
637/// matching the VM's promote-on-overflow — instead of trapping, and `Show` prints its decimal.
638///
639/// Unlike [`compile_to_wasm`] (fully self-contained, no toolchain), this is TOOLCHAIN-DEPENDENT: the
640/// BigInt-aware emitter ([`crate::vm::wasm::assemble_program_linked`]) produces a RELOCATABLE object that
641/// is linked against `logicaffeine_base` (compiled once to `wasm32-unknown-unknown`) with the Rust
642/// toolchain's `rust-lld`. The runtime object is cached per process, so the amortized cost is one link.
643/// Returns the bytes of a `.wasm` file, or a [`ParseError`] carrying the reason the backend or linker
644/// declined (an unsupported program shape, or an unavailable toolchain / base wasm32 build).
645#[cfg(all(feature = "wasm-jit", not(target_arch = "wasm32")))]
646pub fn compile_to_wasm_linked(source: &str) -> Result<Vec<u8>, ParseError> {
647    fn custom(msg: String) -> ParseError {
648        ParseError { kind: crate::error::ParseErrorKind::Custom(msg), span: crate::token::Span::default() }
649    }
650    let relocatable = crate::ui_bridge::with_parsed_program(source, |parsed, interner| -> Result<Vec<u8>, ParseError> {
651        let (stmts, types, policies) = parsed.map_err(custom)?;
652        let oracle = crate::optimize::oracle_analyze_with(stmts, interner);
653        let program = crate::vm::Compiler::compile_with_oracle(stmts, interner, Some(types), Some(oracle)).map_err(custom)?;
654        let module = crate::vm::wasm::assemble_program_linked(&program, &policies, interner).map_err(|e| custom(e.to_string()))?;
655        crate::vm::wasm::module_to_relocatable(&module).map_err(|e| custom(e.to_string()))
656    })?;
657    crate::vm::wasm::link_relocatable_bigint(&relocatable).map_err(|e| custom(e.to_string()))
658}
659
660/// Format an `f64` exactly as a `Show` of a `Float` would — i.e. byte-identical to the
661/// tree-walker / VM (`RuntimeValue::to_display_string`). The direct-WASM host's `print_f64`
662/// sink uses this so a `Show` of a float matches every other engine. Delegating to the real
663/// formatter (rather than re-deriving it) makes drift impossible.
664pub fn display_float_like_logos(f: f64) -> String {
665    crate::interpreter::RuntimeValue::Float(f).to_display_string()
666}
667
668/// Phase 0 (work/FINISH_INTERPRETER.md): classify the determinacy of a LOGOS program.
669///
670/// Parses `source` and runs the determinacy classifier over the parsed program.
671/// Pure analysis — no optimization, no codegen. Returns whether the program is
672/// in the determinate (Kahn-deterministic) or nondeterminate fragment, with the
673/// nondeterminism witnesses.
674pub fn classify_source(source: &str) -> Result<crate::concurrency::Determinacy, ParseError> {
675    let mut interner = Interner::new();
676    let mut lexer = Lexer::new(source, &mut interner);
677    let tokens = lexer.tokenize();
678
679    let (type_registry, _policy_registry) = {
680        let mut discovery = DiscoveryPass::new(&tokens, &mut interner);
681        let result = discovery.run_full();
682        (result.types, result.policies)
683    };
684
685    let mut world_state = WorldState::new();
686    let expr_arena = Arena::new();
687    let term_arena = Arena::new();
688    let np_arena = Arena::new();
689    let sym_arena = Arena::new();
690    let role_arena = Arena::new();
691    let pp_arena = Arena::new();
692    let stmt_arena: Arena<Stmt> = Arena::new();
693    let imperative_expr_arena: Arena<Expr> = Arena::new();
694    let type_expr_arena: Arena<TypeExpr> = Arena::new();
695
696    let ast_ctx = AstContext::with_types(
697        &expr_arena, &term_arena, &np_arena, &sym_arena,
698        &role_arena, &pp_arena, &stmt_arena, &imperative_expr_arena,
699        &type_expr_arena,
700    );
701
702    let mut parser = Parser::new(tokens, &mut world_state, &mut interner, ast_ctx, type_registry);
703    let stmts = parser.parse_program()?;
704    Ok(crate::concurrency::classify_program(&stmts))
705}
706
707/// Phase 0 test seam: is the first `Simultaneously:` / `Attempt all of the
708/// following:` block in `source` made of data-independent branches?
709/// `None` if the program contains no such block.
710pub fn first_parallel_block_independent(source: &str) -> Result<Option<bool>, ParseError> {
711    let mut interner = Interner::new();
712    let mut lexer = Lexer::new(source, &mut interner);
713    let tokens = lexer.tokenize();
714
715    let (type_registry, _policy_registry) = {
716        let mut discovery = DiscoveryPass::new(&tokens, &mut interner);
717        let result = discovery.run_full();
718        (result.types, result.policies)
719    };
720
721    let mut world_state = WorldState::new();
722    let expr_arena = Arena::new();
723    let term_arena = Arena::new();
724    let np_arena = Arena::new();
725    let sym_arena = Arena::new();
726    let role_arena = Arena::new();
727    let pp_arena = Arena::new();
728    let stmt_arena: Arena<Stmt> = Arena::new();
729    let imperative_expr_arena: Arena<Expr> = Arena::new();
730    let type_expr_arena: Arena<TypeExpr> = Arena::new();
731
732    let ast_ctx = AstContext::with_types(
733        &expr_arena, &term_arena, &np_arena, &sym_arena,
734        &role_arena, &pp_arena, &stmt_arena, &imperative_expr_arena,
735        &type_expr_arena,
736    );
737
738    let mut parser = Parser::new(tokens, &mut world_state, &mut interner, ast_ctx, type_registry);
739    let stmts = parser.parse_program()?;
740    Ok(find_first_parallel_independence(&stmts))
741}
742
743fn find_first_parallel_independence(stmts: &[Stmt]) -> Option<bool> {
744    for s in stmts {
745        match s {
746            Stmt::Concurrent { tasks } | Stmt::Parallel { tasks } => {
747                return Some(crate::concurrency::branches_independent(tasks));
748            }
749            Stmt::FunctionDef { body, .. }
750            | Stmt::While { body, .. }
751            | Stmt::Repeat { body, .. }
752            | Stmt::Zone { body, .. } => {
753                if let Some(r) = find_first_parallel_independence(body) {
754                    return Some(r);
755                }
756            }
757            Stmt::If { then_block, else_block, .. } => {
758                if let Some(r) = find_first_parallel_independence(then_block) {
759                    return Some(r);
760                }
761                if let Some(eb) = else_block {
762                    if let Some(r) = find_first_parallel_independence(eb) {
763                        return Some(r);
764                    }
765                }
766            }
767            _ => {}
768        }
769    }
770    None
771}
772
773/// Phase 4 (work/FINISH_INTERPRETER.md): run the Send/escape analysis over a program.
774///
775/// Parses `source` and returns the Send/escape diagnostics (empty = the program
776/// respects the message-passing + CRDT discipline). Pure analysis — no codegen.
777pub fn send_check_source(source: &str) -> Result<Vec<crate::concurrency::SendDiagnostic>, ParseError> {
778    let mut interner = Interner::new();
779    let mut lexer = Lexer::new(source, &mut interner);
780    let tokens = lexer.tokenize();
781
782    let (type_registry, _policy_registry) = {
783        let mut discovery = DiscoveryPass::new(&tokens, &mut interner);
784        let result = discovery.run_full();
785        (result.types, result.policies)
786    };
787
788    let mut world_state = WorldState::new();
789    let expr_arena = Arena::new();
790    let term_arena = Arena::new();
791    let np_arena = Arena::new();
792    let sym_arena = Arena::new();
793    let role_arena = Arena::new();
794    let pp_arena = Arena::new();
795    let stmt_arena: Arena<Stmt> = Arena::new();
796    let imperative_expr_arena: Arena<Expr> = Arena::new();
797    let type_expr_arena: Arena<TypeExpr> = Arena::new();
798
799    let ast_ctx = AstContext::with_types(
800        &expr_arena, &term_arena, &np_arena, &sym_arena,
801        &role_arena, &pp_arena, &stmt_arena, &imperative_expr_arena,
802        &type_expr_arena,
803    );
804
805    let mut parser = Parser::new(tokens, &mut world_state, &mut interner, ast_ctx, type_registry);
806    let stmts = parser.parse_program()?;
807    Ok(crate::concurrency::check_send_escape(&stmts))
808}
809
810/// Compile LOGOS source and return full output including dependency metadata.
811///
812/// This is the primary compilation entry point that returns both the generated
813/// Rust code and any crate dependencies declared in `## Requires` blocks.
814/// [`compile_program_full`] in **Mode B (deterministic replay)** — the full
815/// output (rust + dependencies) with the seeded `Select` lowering. The seeded
816/// differential harness uses this so it gets the same dependency set as Mode A.
817pub fn compile_program_full_deterministic(source: &str) -> Result<CompileOutput, ParseError> {
818    crate::codegen::with_seeded_select(|| compile_program_full(source))
819}
820
821pub fn compile_program_full(source: &str) -> Result<CompileOutput, ParseError> {
822    // Mixed-document aware: a source interleaving imperative code with Coq-style math
823    // (`Definition`/`## Theorem:`/…) routes the math to the Forge and bundles it as
824    // `mod proven`. Pure imperative programs partition to `(source, None)` — a no-op.
825    let (imperative_src, math_src) = crate::ui_bridge::partition_mixed(source);
826    let proven = math_src.as_deref().and_then(crate::ui_bridge::mixed_proven_module);
827    compile_program_full_with_proven(&imperative_src, proven.as_deref())
828}
829
830/// Like [`compile_program_full`], but every emitted (non-generic) `enum` also gets
831/// `WireEncode`/`WireDecode` impls over the shared `logicaffeine_data::wire` codec — byte-identical
832/// to the peer codec's `encode_value_raw`. This lets a compile-once native partial evaluator
833/// receive a program AST as data over the same fast codec the interpreter uses. Ordinary compiles
834/// (the default) are byte-unchanged.
835pub fn compile_program_full_with_wire(source: &str) -> Result<CompileOutput, ParseError> {
836    crate::codegen::types::with_wire_impls(true, || compile_program_full(source))
837}
838
839/// Like [`compile_program_full`], but bundles an extracted math/logic module
840/// (`proven`) into the generated Rust — the imperative half of a mixed document.
841/// The module is emitted as `pub mod proven { … } use proven::*;` so the imperative
842/// program can call its functions / `check_*` predicates by name. `None` (or a
843/// blank module) yields output byte-identical to [`compile_program_full`].
844pub fn compile_program_full_with_proven(source: &str, proven: Option<&str>) -> Result<CompileOutput, ParseError> {
845    // Phase 10: auto-prepend the stdlib modules the program references (no-op when
846    // it uses no stdlib vocabulary, so the benchmark corpus stays byte-identical).
847    let prelude_src = crate::loader::apply_prelude(source);
848    let source = prelude_src.as_ref();
849
850    let mut interner = Interner::new();
851    let mut lexer = Lexer::new(source, &mut interner);
852    let tokens = lexer.tokenize();
853
854    // Pass 1: Discovery - scan for type definitions and policies
855    let (type_registry, policy_registry) = {
856        let mut discovery = DiscoveryPass::new(&tokens, &mut interner);
857        let result = discovery.run_full();
858        (result.types, result.policies)
859    };
860    // Clone for codegen (parser takes ownership)
861    let codegen_registry = type_registry.clone();
862    let codegen_policies = policy_registry.clone();
863
864    let mut world_state = WorldState::new();
865    let expr_arena = Arena::new();
866    let term_arena = Arena::new();
867    let np_arena = Arena::new();
868    let sym_arena = Arena::new();
869    let role_arena = Arena::new();
870    let pp_arena = Arena::new();
871    let stmt_arena: Arena<Stmt> = Arena::new();
872    let imperative_expr_arena: Arena<Expr> = Arena::new();
873    let type_expr_arena: Arena<TypeExpr> = Arena::new();
874
875    let ast_ctx = AstContext::with_types(
876        &expr_arena,
877        &term_arena,
878        &np_arena,
879        &sym_arena,
880        &role_arena,
881        &pp_arena,
882        &stmt_arena,
883        &imperative_expr_arena,
884        &type_expr_arena,
885    );
886
887    // Pass 2: Parse with type context
888    let mut parser = Parser::new(tokens, &mut world_state, &mut interner, ast_ctx, type_registry);
889    // Note: Don't call process_block_headers() - parse_program handles blocks itself
890
891    let stmts = parser.parse_program()?;
892    // The ONE optimization config for this compile: env baseline merged with the
893    // file-level `## No <X>` decorators (program-wide), normalized. Threaded to
894    // BOTH the optimizer and codegen so they never disagree. Captured here (the
895    // last use of `parser`) so its `&mut interner` borrow ends before reuse.
896    let opt_config = {
897        let mut c = crate::optimization::OptimizationConfig::from_env()
898            .merged(&parser.program_opt_flags());
899        c.normalize();
900        c
901    };
902
903    // Type-directed division: rewrite `Divide → ExactDivide` in every Rational context
904    // (the integer default stays floor), so a `Let x: Rational be 7 / 2` compiles exact
905    // (`7/2`) instead of flooring to `3` — matching the tree-walker and VM. The optimizer
906    // treats `ExactDivide` as opaque (never floor-folds it), so this is purely additive.
907    let resolved = crate::resolve_division::resolve_divisions(
908        &stmts,
909        &stmt_arena,
910        &imperative_expr_arena,
911        &interner,
912        opt_config.is_on(crate::optimization::Opt::Comptime),
913    );
914    let stmts: Vec<Stmt> = match resolved {
915        Some(rw) => rw.to_vec(),
916        None => stmts,
917    };
918
919    // Pass 2.5: Optimization - constant folding and dead code elimination
920    let stmts = crate::optimize::optimize_program(stmts, &imperative_expr_arena, &stmt_arena, &mut interner, &opt_config);
921
922    // Extract dependencies before escape analysis
923    let mut dependencies = extract_dependencies(&stmts, &interner)?;
924
925    // FFI: Auto-inject wasm-bindgen dependency if any function is exported for WASM
926    let needs_wasm_bindgen = stmts.iter().any(|stmt| {
927        if let Stmt::FunctionDef { is_exported: true, export_target: Some(target), .. } = stmt {
928            interner.resolve(*target).eq_ignore_ascii_case("wasm")
929        } else {
930            false
931        }
932    });
933    if needs_wasm_bindgen && !dependencies.iter().any(|d| d.name == "wasm-bindgen") {
934        dependencies.push(CrateDependency {
935            name: "wasm-bindgen".to_string(),
936            version: "0.2".to_string(),
937            features: vec![],
938        });
939    }
940
941    // Pass 3: Escape analysis - check for zone escape violations
942    // This catches obvious cases like returning zone-local variables
943    let mut escape_checker = EscapeChecker::new(&interner);
944    escape_checker.check_program(&stmts).map_err(|e| {
945        // Convert EscapeError to ParseError for now
946        // The error message is already Socratic from EscapeChecker
947        ParseError {
948            kind: crate::error::ParseErrorKind::Custom(e.to_string()),
949            span: e.span,
950        }
951    })?;
952
953    // Note: Static verification is available when the `verification` feature is enabled,
954    // but must be explicitly invoked via compile_to_rust_verified().
955
956    let type_env = crate::analysis::check_program(&stmts, &interner, &codegen_registry)
957        .map_err(|e| ParseError {
958            kind: e.to_parse_error_kind(&interner),
959            span: crate::token::Span::default(),
960        })?;
961    let rust_code = crate::codegen::codegen_program_with_proven(&stmts, &codegen_registry, &codegen_policies, &interner, &type_env, &opt_config, "proven", proven);
962
963    // Universal ABI: Generate C header + bindings if any C exports exist
964    let has_c = stmts.iter().any(|stmt| {
965        if let Stmt::FunctionDef { is_exported: true, export_target, .. } = stmt {
966            match export_target {
967                None => true,
968                Some(t) => interner.resolve(*t).eq_ignore_ascii_case("c"),
969            }
970        } else {
971            false
972        }
973    });
974
975    let c_header = if has_c {
976        Some(generate_c_header(&stmts, "module", &interner, &codegen_registry))
977    } else {
978        None
979    };
980
981    // Auto-inject serde_json dependency when C exports exist (needed for collection to_json and portable struct JSON accessors)
982    if has_c && !dependencies.iter().any(|d| d.name == "serde_json") {
983        dependencies.push(CrateDependency {
984            name: "serde_json".to_string(),
985            version: "1".to_string(),
986            features: vec![],
987        });
988    }
989
990    let python_bindings = if has_c {
991        Some(generate_python_bindings(&stmts, "module", &interner, &codegen_registry))
992    } else {
993        None
994    };
995
996    let (typescript_bindings, typescript_types) = if has_c {
997        let (js, dts) = generate_typescript_bindings(&stmts, "module", &interner, &codegen_registry);
998        (Some(js), Some(dts))
999    } else {
1000        (None, None)
1001    };
1002
1003    Ok(CompileOutput { rust_code, dependencies, c_header, python_bindings, typescript_types, typescript_bindings })
1004}
1005
1006/// Generate the Rust SOURCE for an AOT-native cdylib of ONE function (HOTSWAP §Axis-3
1007/// / P14b): the `target` function plus its transitive callees ([`crate::codegen::function_slice`]),
1008/// emitted through the normal ARCHITECT codegen, with the
1009/// [`crate::codegen::codegen_native_tier_export`] shim appended so the loader can
1010/// resolve the `logos_native_<target>` symbol. Mirrors [`compile_program_full`]'s
1011/// front-end, then slices before codegen.
1012///
1013/// Returns `Ok(None)` when `target` is absent or outside the sound scalar ABI subset —
1014/// the caller then keeps that function on VM+JIT (no AOT-native, no gap at the seam).
1015/// `Err` only on a genuine parse/type error in the source.
1016pub fn compile_function_to_native_rust(
1017    source: &str,
1018    target: &str,
1019) -> Result<Option<AotModule>, ParseError> {
1020    let mut interner = Interner::new();
1021    let mut lexer = Lexer::new(source, &mut interner);
1022    let tokens = lexer.tokenize();
1023
1024    let (type_registry, policy_registry) = {
1025        let mut discovery = DiscoveryPass::new(&tokens, &mut interner);
1026        let result = discovery.run_full();
1027        (result.types, result.policies)
1028    };
1029    let codegen_registry = type_registry.clone();
1030    let codegen_policies = policy_registry.clone();
1031
1032    let mut world_state = WorldState::new();
1033    let expr_arena = Arena::new();
1034    let term_arena = Arena::new();
1035    let np_arena = Arena::new();
1036    let sym_arena = Arena::new();
1037    let role_arena = Arena::new();
1038    let pp_arena = Arena::new();
1039    let stmt_arena: Arena<Stmt> = Arena::new();
1040    let imperative_expr_arena: Arena<Expr> = Arena::new();
1041    let type_expr_arena: Arena<TypeExpr> = Arena::new();
1042    let ast_ctx = AstContext::with_types(
1043        &expr_arena,
1044        &term_arena,
1045        &np_arena,
1046        &sym_arena,
1047        &role_arena,
1048        &pp_arena,
1049        &stmt_arena,
1050        &imperative_expr_arena,
1051        &type_expr_arena,
1052    );
1053
1054    let mut parser = Parser::new(tokens, &mut world_state, &mut interner, ast_ctx, type_registry);
1055    let stmts = parser.parse_program()?;
1056    let opt_config = {
1057        let mut c = crate::optimization::OptimizationConfig::from_env()
1058            .merged(&parser.program_opt_flags());
1059        c.normalize();
1060        c
1061    };
1062    // Optimize the whole program (ARCHITECT), THEN slice: the slice picks the optimized
1063    // target + its (optimized) transitive callees.
1064    let stmts = crate::optimize::optimize_program(
1065        stmts,
1066        &imperative_expr_arena,
1067        &stmt_arena,
1068        &mut interner,
1069        &opt_config,
1070    );
1071
1072    // Resolve the target's symbol + signature for the shim; absent ⇒ no AOT-native fn.
1073    let target_sig = stmts.iter().find_map(|s| match s {
1074        Stmt::FunctionDef { name, params, return_type, .. }
1075            if interner.resolve(*name) == target =>
1076        {
1077            let ps: Vec<(crate::intern::Symbol, &TypeExpr)> =
1078                params.iter().map(|(s, t)| (*s, *t)).collect();
1079            Some((*name, ps, *return_type))
1080        }
1081        _ => None,
1082    });
1083    let Some((target_sym, params, return_type)) = target_sig else {
1084        return Ok(None);
1085    };
1086    // The shim only exists for the sound scalar subset; otherwise no AOT-native fn.
1087    let Some(shim) =
1088        crate::codegen::codegen_native_tier_export(target_sym, &params, return_type, &interner)
1089    else {
1090        return Ok(None);
1091    };
1092
1093    let slice = crate::codegen::function_slice(&stmts, target_sym, &interner);
1094    let type_env = crate::analysis::check_program(&slice, &interner, &codegen_registry).map_err(|e| {
1095        ParseError {
1096            kind: e.to_parse_error_kind(&interner),
1097            span: crate::token::Span::default(),
1098        }
1099    })?;
1100    let module = crate::codegen::codegen_program(
1101        &slice,
1102        &codegen_registry,
1103        &codegen_policies,
1104        &interner,
1105        &type_env,
1106        &opt_config,
1107    );
1108    let symbol = format!(
1109        "logos_native_{}",
1110        crate::analysis::types::RustNames::new(&interner).raw(target_sym)
1111    );
1112    Ok(Some(AotModule {
1113        rust: format!("{module}\n{shim}"),
1114        symbol,
1115        arity: params.len(),
1116    }))
1117}
1118
1119/// A compiled AOT-native module (HOTSWAP §Axis-3): the cdylib Rust SOURCE plus the
1120/// exact exported `symbol` to resolve and its `arity` — everything the loader needs.
1121#[derive(Debug, Clone)]
1122pub struct AotModule {
1123    pub rust: String,
1124    pub symbol: String,
1125    pub arity: usize,
1126}
1127
1128/// Build a function's AOT module to a browser-loadable `wasm32-unknown-unknown` cdylib
1129/// (HOTSWAP §Axis-3 / P17 — the browser analog of [`build_native_cdylib`]). The module
1130/// is the same scalar-ABI shim; the wasm crate keeps `logicaffeine-data` +
1131/// `logicaffeine-system` (both wasm-ready) but drops the desktop-only `tokio`, the
1132/// `logicaffeine-system` `full` feature, and `target-cpu=native`. Returns the `.wasm` path;
1133/// the browser then `WebAssembly.instantiate`s it and the entry calls into it through
1134/// the warm-bytecode indirection. Requires the `wasm32-unknown-unknown` target.
1135#[cfg(not(target_arch = "wasm32"))]
1136pub fn build_native_wasm(
1137    rust_source: &str,
1138    crate_name: &str,
1139    work_dir: &std::path::Path,
1140) -> Result<std::path::PathBuf, String> {
1141    use std::process::Command;
1142    let proj = work_dir.join(crate_name);
1143    let _ = fs::remove_dir_all(&proj);
1144    fs::create_dir_all(proj.join("src")).map_err(|e| e.to_string())?;
1145    fs::write(proj.join("src").join("lib.rs"), rust_source).map_err(|e| e.to_string())?;
1146
1147    let cargo_toml = format!(
1148        "[package]\nname = \"{crate_name}\"\nversion = \"0.0.0\"\nedition = \"2021\"\n\n\
1149         [lib]\ncrate-type = [\"cdylib\"]\n\n\
1150         [dependencies]\n\
1151         logicaffeine-data = {{ path = \"./crates/logicaffeine_data\" }}\n\
1152         logicaffeine-system = {{ path = \"./crates/logicaffeine_system\" }}\n\n\
1153         [profile.release]\nlto = true\nopt-level = 3\ncodegen-units = 1\nstrip = true\n"
1154    );
1155    fs::write(proj.join("Cargo.toml"), &cargo_toml).map_err(|e| e.to_string())?;
1156
1157    copy_runtime_crates(&proj).map_err(|e| e.to_string())?;
1158
1159    let out = Command::new("cargo")
1160        .args(["build", "--release", "--lib", "--target", "wasm32-unknown-unknown"])
1161        .current_dir(&proj)
1162        .output()
1163        .map_err(|e| format!("failed to run cargo: {e}"))?;
1164    if !out.status.success() {
1165        return Err(format!(
1166            "wasm bundle build failed:\n{}",
1167            String::from_utf8_lossy(&out.stderr)
1168        ));
1169    }
1170
1171    let wasm = proj
1172        .join("target")
1173        .join("wasm32-unknown-unknown")
1174        .join("release")
1175        .join(format!("{crate_name}.wasm"));
1176    if !wasm.exists() {
1177        return Err(format!("wasm module not found at {}", wasm.display()));
1178    }
1179    Ok(wasm)
1180}
1181
1182/// Build AOT-native cdylib Rust source (from [`compile_function_to_native_rust`]) into
1183/// a loadable `cdylib` (HOTSWAP §Axis-3 / P16). Generates a minimal crate under
1184/// `work_dir/<crate_name>`, copies the shared runtime crates (so the `.so` and the
1185/// interpreter share the SAME `logicaffeine_data` ABI), and runs `cargo build
1186/// --release`. Returns the path to the produced dynamic library. `panic = "unwind"`
1187/// (the default) — NOT `abort` — so a panic in a loaded function can be contained
1188/// rather than killing the host process.
1189#[cfg(not(target_arch = "wasm32"))]
1190pub fn build_native_cdylib(
1191    rust_source: &str,
1192    crate_name: &str,
1193    work_dir: &std::path::Path,
1194) -> Result<std::path::PathBuf, String> {
1195    use std::process::Command;
1196    let proj = work_dir.join(crate_name);
1197    let _ = fs::remove_dir_all(&proj);
1198    fs::create_dir_all(proj.join("src")).map_err(|e| e.to_string())?;
1199    fs::write(proj.join("src").join("lib.rs"), rust_source).map_err(|e| e.to_string())?;
1200
1201    let cargo_toml = format!(
1202        "[package]\nname = \"{crate_name}\"\nversion = \"0.0.0\"\nedition = \"2021\"\n\n\
1203         [lib]\ncrate-type = [\"cdylib\"]\n\n\
1204         [dependencies]\n\
1205         logicaffeine-data = {{ path = \"./crates/logicaffeine_data\" }}\n\
1206         logicaffeine-system = {{ path = \"./crates/logicaffeine_system\", features = [\"full\"] }}\n\
1207         tokio = {{ version = \"1\", features = [\"rt-multi-thread\", \"macros\"] }}\n\n\
1208         [profile.release]\nlto = true\nopt-level = 3\ncodegen-units = 1\nstrip = true\n"
1209    );
1210    fs::write(proj.join("Cargo.toml"), &cargo_toml).map_err(|e| e.to_string())?;
1211
1212    let cargo_cfg = proj.join(".cargo");
1213    fs::create_dir_all(&cargo_cfg).map_err(|e| e.to_string())?;
1214    fs::write(
1215        cargo_cfg.join("config.toml"),
1216        "[build]\nrustflags = [\"-C\", \"target-cpu=native\"]\n",
1217    )
1218    .map_err(|e| e.to_string())?;
1219
1220    copy_runtime_crates(&proj).map_err(|e| e.to_string())?;
1221
1222    let out = Command::new("cargo")
1223        .args(["build", "--release", "--lib"])
1224        .current_dir(&proj)
1225        .output()
1226        .map_err(|e| format!("failed to run cargo: {e}"))?;
1227    if !out.status.success() {
1228        return Err(format!(
1229            "AOT cdylib build failed:\n{}",
1230            String::from_utf8_lossy(&out.stderr)
1231        ));
1232    }
1233
1234    let so = proj.join("target").join("release").join(format!(
1235        "{}{}{}",
1236        std::env::consts::DLL_PREFIX,
1237        crate_name,
1238        std::env::consts::DLL_SUFFIX
1239    ));
1240    if !so.exists() {
1241        return Err(format!("AOT cdylib not found at {}", so.display()));
1242    }
1243    Ok(so)
1244}
1245
1246/// The cache key for an AOT-native artifact (HOTSWAP §Axis-3 / P16): a deterministic
1247/// FNV-1a hash of the rustc toolchain version AND the optimized Rust source. The
1248/// toolchain is in the key because Rust has no stable cross-version ABI — a different
1249/// `rustc`/runtime yields a different key, so a stale, ABI-mismatched `.so` is NEVER
1250/// reused (soundness over convenience). The source captures the function, its callees,
1251/// and the optimization config (it IS the codegen output).
1252#[cfg(not(target_arch = "wasm32"))]
1253pub fn aot_cache_key(rust_source: &str) -> String {
1254    let toolchain = std::process::Command::new("rustc")
1255        .arg("--version")
1256        .output()
1257        .ok()
1258        .map(|o| String::from_utf8_lossy(&o.stdout).into_owned())
1259        .unwrap_or_default();
1260    let mut h: u64 = 0xcbf29ce4_84222325;
1261    for b in toolchain.bytes().chain(rust_source.bytes()) {
1262        h ^= b as u64;
1263        h = h.wrapping_mul(0x0000_0100_0000_01b3);
1264    }
1265    format!("{h:016x}")
1266}
1267
1268/// [`build_native_cdylib`] with a persistent cache (HOTSWAP §Axis-3 / P16). The artifact
1269/// lives at `cache_dir/aot_<fn>_<key>/…` keyed by [`aot_cache_key`]; an identical
1270/// function reuses its `.so` across runs (no rebuild), and a toolchain change yields a
1271/// new key so the stale library is never loaded.
1272#[cfg(not(target_arch = "wasm32"))]
1273pub fn build_native_cdylib_cached(
1274    rust_source: &str,
1275    fn_name: &str,
1276    cache_dir: &std::path::Path,
1277) -> Result<std::path::PathBuf, String> {
1278    let key = aot_cache_key(rust_source);
1279    let crate_name = format!("aot_{fn_name}_{key}");
1280    let so = cache_dir.join(&crate_name).join("target").join("release").join(format!(
1281        "{}{}{}",
1282        std::env::consts::DLL_PREFIX,
1283        crate_name,
1284        std::env::consts::DLL_SUFFIX
1285    ));
1286    if so.exists() {
1287        return Ok(so); // cache hit — reuse, skip the rustc build
1288    }
1289    fs::create_dir_all(cache_dir).map_err(|e| e.to_string())?;
1290    build_native_cdylib(rust_source, &crate_name, cache_dir)
1291}
1292
1293/// On-demand AOT-native build + load (HOTSWAP §Axis-3): compile `fn_name` (and its
1294/// callees) to an optimized cdylib (cached), `dlopen` it, and return the loaded
1295/// [`crate::vm::NativeFn`] ready to install via `Vm::install_aot_native`. `None` if the
1296/// function is absent / outside the scalar subset / the build or load failed — the
1297/// caller keeps it on VM+JIT. Blocking (runs `rustc`); the background worker
1298/// (`BgAotCompiler`) calls this off the interpreter thread.
1299#[cfg(not(target_arch = "wasm32"))]
1300pub fn aot_build_function(
1301    source: &str,
1302    fn_name: &str,
1303    cache_dir: &std::path::Path,
1304) -> Option<Box<dyn crate::vm::NativeFn>> {
1305    let module = compile_function_to_native_rust(source, fn_name).ok().flatten()?;
1306    let so = build_native_cdylib_cached(&module.rust, fn_name, cache_dir).ok()?;
1307    let (nf, _calls) = crate::vm::aot_tier::load_aot_native(&so, &module.symbol, module.arity)?;
1308    Some(nf)
1309}
1310
1311/// The names of functions annotated for the AOT-native tier — `## To <fn> … is
1312/// exported for native:` (HOTSWAP §Axis-3 selectivity). These are the functions
1313/// `largo build --native-functions` pre-bundles; everything else stays on VM+JIT.
1314pub fn native_export_function_names(source: &str) -> Vec<String> {
1315    crate::ui_bridge::with_parsed_program(source, |parsed, interner| match parsed {
1316        Ok((stmts, _types, _policies)) => stmts
1317            .iter()
1318            .filter_map(|s| match s {
1319                Stmt::FunctionDef { name, is_exported: true, export_target: Some(t), .. }
1320                    if interner.resolve(*t).eq_ignore_ascii_case("native") =>
1321                {
1322                    Some(interner.resolve(*name).to_string())
1323                }
1324                _ => None,
1325            })
1326            .collect(),
1327        Err(_) => Vec::new(),
1328    })
1329}
1330
1331/// Load every `native`-annotated function in `source` as a compiled-native
1332/// [`crate::vm::NativeFn`] (HOTSWAP §Axis-3): the run path installs these via
1333/// `Vm::install_aot_native`, so an annotated function dispatches to `rustc -O3` machine
1334/// code from its first call. Uses the persistent cache, so a bundle pre-built by
1335/// `largo build --native-functions` is a cache hit — no `rustc` on the run path. A
1336/// function outside the scalar subset / failing to build is skipped (stays on VM+JIT).
1337#[cfg(not(target_arch = "wasm32"))]
1338pub fn aot_load_bundle(
1339    source: &str,
1340    cache_dir: &std::path::Path,
1341) -> Vec<(String, Box<dyn crate::vm::NativeFn>)> {
1342    native_export_function_names(source)
1343        .into_iter()
1344        .filter_map(|name| aot_build_function(source, &name, cache_dir).map(|nf| (name, nf)))
1345        .collect()
1346}
1347
1348/// Pre-build every `native`-annotated function in `source` into the AOT bundle under
1349/// `bundle_dir` (HOTSWAP §Axis-3 / P16 — the "tools to bundle them"). Returns the
1350/// `(function_name, cdylib_path)` manifest. Functions outside the sound scalar subset
1351/// are silently skipped — they keep running on VM+JIT, no gap at the seam. `Err` only
1352/// on a genuine parse/type error.
1353#[cfg(not(target_arch = "wasm32"))]
1354pub fn build_native_bundle(
1355    source: &str,
1356    bundle_dir: &std::path::Path,
1357) -> Result<Vec<(String, std::path::PathBuf)>, ParseError> {
1358    let mut manifest = Vec::new();
1359    for name in native_export_function_names(source) {
1360        let Some(module) = compile_function_to_native_rust(source, &name)? else {
1361            continue; // not in the scalar subset — stays on VM+JIT
1362        };
1363        if let Ok(so) = build_native_cdylib_cached(&module.rust, &name, bundle_dir) {
1364            manifest.push((name, so));
1365        }
1366    }
1367    Ok(manifest)
1368}
1369
1370/// Extract crate dependencies from `Stmt::Require` nodes.
1371///
1372/// Deduplicates by crate name: same name + same version keeps one copy.
1373/// Same name + different version returns a `ParseError`.
1374/// Preserves declaration order (first occurrence wins).
1375fn extract_dependencies(stmts: &[Stmt], interner: &Interner) -> Result<Vec<CrateDependency>, ParseError> {
1376    use std::collections::HashMap;
1377
1378    let mut seen: HashMap<String, String> = HashMap::new(); // name → version
1379    let mut deps: Vec<CrateDependency> = Vec::new();
1380
1381    for stmt in stmts {
1382        if let Stmt::Require { crate_name, version, features, span } = stmt {
1383            let name = interner.resolve(*crate_name).to_string();
1384            let ver = interner.resolve(*version).to_string();
1385
1386            if let Some(existing_ver) = seen.get(&name) {
1387                if *existing_ver != ver {
1388                    return Err(ParseError {
1389                        kind: crate::error::ParseErrorKind::Custom(format!(
1390                            "Conflicting versions for crate \"{}\": \"{}\" and \"{}\".",
1391                            name, existing_ver, ver
1392                        )),
1393                        span: *span,
1394                    });
1395                }
1396                // Same name + same version: skip duplicate
1397            } else {
1398                seen.insert(name.clone(), ver.clone());
1399                deps.push(CrateDependency {
1400                    name,
1401                    version: ver,
1402                    features: features.iter().map(|f| interner.resolve(*f).to_string()).collect(),
1403                });
1404            }
1405        }
1406    }
1407
1408    Ok(deps)
1409}
1410
1411/// Compile LOGOS source to Rust with ownership checking enabled.
1412///
1413/// This runs the lightweight ownership analysis pass that catches use-after-move
1414/// errors with control flow awareness. The analysis is fast enough to run on
1415/// every keystroke in an IDE.
1416///
1417/// # Arguments
1418///
1419/// * `source` - LOGOS source code as a string
1420///
1421/// # Returns
1422///
1423/// Generated Rust source code on success.
1424///
1425/// # Errors
1426///
1427/// Returns [`ParseError`] if:
1428/// - Any error from [`compile_to_rust`] occurs
1429/// - Ownership analysis detects use-after-move
1430/// - Ownership analysis detects use-after-borrow violations
1431///
1432/// # Example
1433///
1434/// ```
1435/// # use logicaffeine_compile::compile::compile_to_rust_checked;
1436/// // This will fail ownership checking
1437/// let source = "## Main\nLet x be 5.\nGive x to y.\nShow x.";
1438/// let result = compile_to_rust_checked(source);
1439/// assert!(result.is_err()); // "x has already been given away"
1440/// ```
1441///
1442/// # Use Case
1443///
1444/// Use this function with the `--check` CLI flag for instant feedback on
1445/// ownership errors before running the full Rust compilation.
1446pub fn compile_to_rust_checked(source: &str) -> Result<String, ParseError> {
1447    // Mixed document: ownership-check only the imperative stream (math blocks are
1448    // blanked, preserving line numbers). The math half is checked by the kernel.
1449    let (imperative_src, _) = crate::ui_bridge::partition_mixed(source);
1450    let source = imperative_src.as_str();
1451    let mut interner = Interner::new();
1452    let mut lexer = Lexer::new(source, &mut interner);
1453    let tokens = lexer.tokenize();
1454
1455    // Pass 1: Discovery - scan for type definitions and policies
1456    let (type_registry, policy_registry) = {
1457        let mut discovery = DiscoveryPass::new(&tokens, &mut interner);
1458        let result = discovery.run_full();
1459        (result.types, result.policies)
1460    };
1461    // Clone for codegen (parser takes ownership)
1462    let codegen_registry = type_registry.clone();
1463    let codegen_policies = policy_registry.clone();
1464
1465    let mut world_state = WorldState::new();
1466    let expr_arena = Arena::new();
1467    let term_arena = Arena::new();
1468    let np_arena = Arena::new();
1469    let sym_arena = Arena::new();
1470    let role_arena = Arena::new();
1471    let pp_arena = Arena::new();
1472    let stmt_arena: Arena<Stmt> = Arena::new();
1473    let imperative_expr_arena: Arena<Expr> = Arena::new();
1474    let type_expr_arena: Arena<TypeExpr> = Arena::new();
1475
1476    let ast_ctx = AstContext::with_types(
1477        &expr_arena,
1478        &term_arena,
1479        &np_arena,
1480        &sym_arena,
1481        &role_arena,
1482        &pp_arena,
1483        &stmt_arena,
1484        &imperative_expr_arena,
1485        &type_expr_arena,
1486    );
1487
1488    // Pass 2: Parse with type context
1489    let mut parser = Parser::new(tokens, &mut world_state, &mut interner, ast_ctx, type_registry);
1490    let stmts = parser.parse_program()?;
1491
1492    // Pass 2.5: Optimization - constant folding, propagation, and dead code elimination
1493    let stmts = crate::optimize::optimize_program(stmts, &imperative_expr_arena, &stmt_arena, &mut interner, &crate::optimization::OptimizationConfig::from_env());
1494
1495    // Pass 3: Escape analysis
1496    let mut escape_checker = EscapeChecker::new(&interner);
1497    escape_checker.check_program(&stmts).map_err(|e| {
1498        ParseError {
1499            kind: crate::error::ParseErrorKind::Custom(e.to_string()),
1500            span: e.span,
1501        }
1502    })?;
1503
1504    // Pass 4: Ownership analysis
1505    // Catches use-after-move errors with control flow awareness
1506    let mut ownership_checker = OwnershipChecker::new(&interner);
1507    ownership_checker.check_program(&stmts).map_err(|e| {
1508        ParseError {
1509            kind: crate::error::ParseErrorKind::Custom(e.to_string()),
1510            span: e.span,
1511        }
1512    })?;
1513
1514    let type_env = crate::analysis::check_program(&stmts, &interner, &codegen_registry)
1515        .map_err(|e| ParseError {
1516            kind: e.to_parse_error_kind(&interner),
1517            span: crate::token::Span::default(),
1518        })?;
1519    let rust_code = codegen_program(&stmts, &codegen_registry, &codegen_policies, &interner, &type_env, &crate::optimization::OptimizationConfig::from_env());
1520
1521    Ok(rust_code)
1522}
1523
1524/// Compile LOGOS source to Rust with full Z3 static verification.
1525///
1526/// This runs the Z3-based verifier on Assert statements before code generation,
1527/// proving that assertions hold for all possible inputs. This is the most
1528/// thorough compilation mode, suitable for high-assurance code.
1529///
1530/// # Arguments
1531///
1532/// * `source` - LOGOS source code as a string
1533///
1534/// # Returns
1535///
1536/// Generated Rust source code on success.
1537///
1538/// # Errors
1539///
1540/// Returns [`ParseError`] if:
1541/// - Any error from [`compile_to_rust`] occurs
1542/// - Z3 cannot prove an Assert statement
1543/// - Refinement type constraints cannot be satisfied
1544/// - Termination cannot be proven for loops with `decreasing`
1545///
1546/// # Example
1547///
1548/// ```no_run
1549/// # use logicaffeine_compile::compile::compile_to_rust_verified;
1550/// # use logicaffeine_compile::ParseError;
1551/// # fn main() -> Result<(), ParseError> {
1552/// let source = r#"
1553/// ## Main
1554/// Let x: { it: Int | it > 0 } be 5.
1555/// Assert that x > 0.
1556/// "#;
1557/// let rust_code = compile_to_rust_verified(source)?;
1558/// # Ok(())
1559/// # }
1560/// ```
1561///
1562/// # Feature Flag
1563///
1564/// This function requires the `verification` feature to be enabled:
1565///
1566/// ```toml
1567/// [dependencies]
1568/// logicaffeine_compile = { version = "...", features = ["verification"] }
1569/// ```
1570#[cfg(feature = "verification")]
1571pub fn compile_to_rust_verified(source: &str) -> Result<String, ParseError> {
1572    use crate::verification::VerificationPass;
1573
1574    let mut interner = Interner::new();
1575    let mut lexer = Lexer::new(source, &mut interner);
1576    let tokens = lexer.tokenize();
1577
1578    // Pass 1: Discovery - scan for type definitions and policies
1579    let (type_registry, policy_registry) = {
1580        let mut discovery = DiscoveryPass::new(&tokens, &mut interner);
1581        let result = discovery.run_full();
1582        (result.types, result.policies)
1583    };
1584    // Clone for codegen (parser takes ownership)
1585    let codegen_registry = type_registry.clone();
1586    let codegen_policies = policy_registry.clone();
1587
1588    let mut world_state = WorldState::new();
1589    let expr_arena = Arena::new();
1590    let term_arena = Arena::new();
1591    let np_arena = Arena::new();
1592    let sym_arena = Arena::new();
1593    let role_arena = Arena::new();
1594    let pp_arena = Arena::new();
1595    let stmt_arena: Arena<Stmt> = Arena::new();
1596    let imperative_expr_arena: Arena<Expr> = Arena::new();
1597    let type_expr_arena: Arena<TypeExpr> = Arena::new();
1598
1599    let ast_ctx = AstContext::with_types(
1600        &expr_arena,
1601        &term_arena,
1602        &np_arena,
1603        &sym_arena,
1604        &role_arena,
1605        &pp_arena,
1606        &stmt_arena,
1607        &imperative_expr_arena,
1608        &type_expr_arena,
1609    );
1610
1611    // Pass 2: Parse with type context
1612    let mut parser = Parser::new(tokens, &mut world_state, &mut interner, ast_ctx, type_registry);
1613    let stmts = parser.parse_program()?;
1614
1615    // Pass 3: Escape analysis
1616    let mut escape_checker = EscapeChecker::new(&interner);
1617    escape_checker.check_program(&stmts).map_err(|e| {
1618        ParseError {
1619            kind: crate::error::ParseErrorKind::Custom(e.to_string()),
1620            span: e.span,
1621        }
1622    })?;
1623
1624    // Pass 4: Static verification
1625    let mut verifier = VerificationPass::new(&interner);
1626    verifier.verify_program(&stmts).map_err(|e| {
1627        ParseError {
1628            kind: crate::error::ParseErrorKind::Custom(format!(
1629                "Verification Failed:\n\n{}",
1630                e
1631            )),
1632            span: crate::token::Span::default(),
1633        }
1634    })?;
1635
1636    let type_env = crate::analysis::check_program(&stmts, &interner, &codegen_registry)
1637        .map_err(|e| ParseError {
1638            kind: e.to_parse_error_kind(&interner),
1639            span: crate::token::Span::default(),
1640        })?;
1641    let rust_code = codegen_program(&stmts, &codegen_registry, &codegen_policies, &interner, &type_env, &crate::optimization::OptimizationConfig::from_env());
1642
1643    Ok(rust_code)
1644}
1645
1646/// Compile LOGOS source and write output to a directory as a Cargo project.
1647///
1648/// Creates a complete Cargo project structure with:
1649/// - `src/main.rs` containing the generated Rust code
1650/// - `Cargo.toml` with runtime dependencies
1651/// - `crates/` directory with runtime crate copies
1652///
1653/// # Arguments
1654///
1655/// * `source` - LOGOS source code as a string
1656/// * `output_dir` - Directory to create the Cargo project in
1657///
1658/// # Errors
1659///
1660/// Returns [`CompileError`] if:
1661/// - Compilation fails (wrapped as `CompileError::Parse`)
1662/// - File system operations fail (wrapped as `CompileError::Io`)
1663///
1664/// # Example
1665///
1666/// ```no_run
1667/// # use logicaffeine_compile::compile::{compile_to_dir, CompileError};
1668/// # use std::path::Path;
1669/// # fn main() -> Result<(), CompileError> {
1670/// let source = "## Main\nShow \"Hello\".";
1671/// compile_to_dir(source, Path::new("/tmp/my_project"))?;
1672/// // Now /tmp/my_project is a buildable Cargo project
1673/// # Ok(())
1674/// # }
1675/// ```
1676/// Everything [`rustc_check`] needs short of running cargo: generated Rust,
1677/// dependencies, the POPULATED rustc→LOGOS source map, and the interner the
1678/// program was compiled with — the substrate the diagnostic bridge translates
1679/// through.
1680pub struct CheckArtifacts {
1681    pub rust_code: String,
1682    pub dependencies: Vec<CrateDependency>,
1683    pub source_map: crate::sourcemap::SourceMap,
1684    pub interner: Interner,
1685}
1686
1687/// Compile for CHECKING: the same front as [`compile_program_full`] but with
1688/// the optimizer off and mapped codegen, so every generated line ties back to
1689/// its statement span and the map stays 1:1 (the optimizer may fuse or drop
1690/// statements; a check build has no use for its output anyway).
1691///
1692/// Deliberately does NOT bail on typecheck/escape/ownership findings — the
1693/// interactive pipeline already reports those; this path exists to surface
1694/// what only rustc can see, and a borrow error in the generated code comes
1695/// back TRANSLATED rather than pre-empted.
1696pub fn rustc_check_artifacts(source: &str) -> Result<CheckArtifacts, ParseError> {
1697    let user_source = source;
1698
1699    // The stdlib prelude is PREPENDED when referenced; spans from the parse
1700    // are offsets into the expanded text and shift back by the prelude length.
1701    let prelude_src = crate::loader::apply_prelude(user_source);
1702    let expanded: &str = prelude_src.as_ref();
1703    debug_assert!(
1704        expanded.ends_with(user_source),
1705        "apply_prelude must PREPEND; span translation depends on it"
1706    );
1707    let prelude_offset = expanded.len() - user_source.len();
1708
1709    let mut interner = Interner::new();
1710    let mut lexer = Lexer::new(expanded, &mut interner);
1711    let tokens = lexer.tokenize();
1712
1713    let (type_registry, policy_registry) = {
1714        let mut discovery = DiscoveryPass::new(&tokens, &mut interner);
1715        let result = discovery.run_full();
1716        (result.types, result.policies)
1717    };
1718    let codegen_registry = type_registry.clone();
1719    let codegen_policies = policy_registry.clone();
1720
1721    let mut world_state = WorldState::new();
1722    let expr_arena = Arena::new();
1723    let term_arena = Arena::new();
1724    let np_arena = Arena::new();
1725    let sym_arena = Arena::new();
1726    let role_arena = Arena::new();
1727    let pp_arena = Arena::new();
1728    let stmt_arena: Arena<Stmt> = Arena::new();
1729    let imperative_expr_arena: Arena<Expr> = Arena::new();
1730    let type_expr_arena: Arena<TypeExpr> = Arena::new();
1731
1732    let ast_ctx = AstContext::with_types(
1733        &expr_arena,
1734        &term_arena,
1735        &np_arena,
1736        &sym_arena,
1737        &role_arena,
1738        &pp_arena,
1739        &stmt_arena,
1740        &imperative_expr_arena,
1741        &type_expr_arena,
1742    );
1743
1744    let mut parser = Parser::new(tokens, &mut world_state, &mut interner, ast_ctx, type_registry);
1745    let stmts = parser.parse_program()?;
1746
1747    // Translate spans back into the USER buffer; prelude statements become
1748    // zero-width sentinels that mapped codegen skips.
1749    let stmt_spans: Vec<crate::token::Span> = parser
1750        .stmt_spans()
1751        .iter()
1752        .map(|span| {
1753            if span.start >= prelude_offset {
1754                crate::token::Span::new(span.start - prelude_offset, span.end - prelude_offset)
1755            } else {
1756                crate::token::Span::default()
1757            }
1758        })
1759        .collect();
1760
1761    // Exact division still resolves (type semantics, statement-count
1762    // preserving); the optimizer stays OFF so spans align 1:1.
1763    let resolved = crate::resolve_division::resolve_divisions(
1764        &stmts,
1765        &stmt_arena,
1766        &imperative_expr_arena,
1767        &interner,
1768        false,
1769    );
1770    let stmts: Vec<Stmt> = match resolved {
1771        Some(rw) => rw.to_vec(),
1772        None => stmts,
1773    };
1774
1775    // Best-effort env: findings are the interactive pipeline's job here.
1776    let (type_env, _findings) =
1777        crate::analysis::check_program_collect(&stmts, &interner, &codegen_registry);
1778
1779    let dependencies = extract_dependencies(&stmts, &interner)?;
1780
1781    let (rust_code, source_map) = crate::codegen::codegen_program_mapped(
1782        &stmts,
1783        &codegen_registry,
1784        &codegen_policies,
1785        &interner,
1786        &type_env,
1787        &crate::optimization::OptimizationConfig::all_off(),
1788        &stmt_spans,
1789        user_source,
1790    );
1791
1792    Ok(CheckArtifacts {
1793        rust_code,
1794        dependencies,
1795        source_map,
1796        interner,
1797    })
1798}
1799
1800/// Run rustc's analysis over a LOGOS program and translate every finding
1801/// back to English with real user-source spans — the flycheck engine.
1802///
1803/// `cache_dir` is a persistent per-workspace directory: the first run stages
1804/// the runtime crates and compiles them; later runs are incremental. Uses
1805/// `cargo check` (no codegen, no linking).
1806///
1807/// Fail-loud contract: `Ok(vec![])` means cargo ran AND found nothing. A
1808/// cargo failure that translates to no LOGOS finding is a `Build` error, not
1809/// a silent all-clear.
1810pub fn rustc_check(source: &str, cache_dir: &Path) -> Result<Vec<LogosError>, CompileError> {
1811    let artifacts = rustc_check_artifacts(source).map_err(CompileError::Parse)?;
1812    write_cargo_project(&artifacts.rust_code, &artifacts.dependencies, cache_dir)?;
1813
1814    let check_output = Command::new("cargo")
1815        .arg("check")
1816        .arg("--message-format=json")
1817        .current_dir(cache_dir)
1818        .output()
1819        .map_err(|e| CompileError::Io(e.to_string()))?;
1820
1821    let stdout = String::from_utf8_lossy(&check_output.stdout);
1822    let diagnostics = parse_rustc_json(&stdout);
1823    let findings =
1824        crate::diagnostic::translate_diagnostics_all(&diagnostics, &artifacts.source_map, &artifacts.interner);
1825
1826    if !check_output.status.success() && findings.is_empty() {
1827        let stderr = String::from_utf8_lossy(&check_output.stderr);
1828        return Err(CompileError::Build(stderr.to_string()));
1829    }
1830
1831    Ok(findings)
1832}
1833
1834pub fn compile_to_dir(source: &str, output_dir: &Path) -> Result<(), CompileError> {
1835    let output = compile_program_full(source).map_err(CompileError::Parse)?;
1836    write_cargo_project(&output.rust_code, &output.dependencies, output_dir)
1837}
1838
1839/// Write a generated program as a runnable cargo project: `src/main.rs`,
1840/// `Cargo.toml` (runtime path-deps + user `## Requires`), `.cargo/config.toml`,
1841/// and the staged runtime crates. Shared by [`compile_to_dir`] and
1842/// [`rustc_check`].
1843pub fn write_cargo_project(
1844    rust_code: &str,
1845    dependencies: &[CrateDependency],
1846    output_dir: &Path,
1847) -> Result<(), CompileError> {
1848    // Create output directory structure
1849    let src_dir = output_dir.join("src");
1850    fs::create_dir_all(&src_dir).map_err(|e| CompileError::Io(e.to_string()))?;
1851
1852    // Write main.rs (codegen already includes the use statements)
1853    let main_path = src_dir.join("main.rs");
1854    let mut file = fs::File::create(&main_path).map_err(|e| CompileError::Io(e.to_string()))?;
1855    file.write_all(rust_code.as_bytes()).map_err(|e| CompileError::Io(e.to_string()))?;
1856
1857    // Write Cargo.toml with runtime crate dependencies
1858    let mut cargo_toml = String::from(r#"[package]
1859name = "logos_output"
1860version = "0.1.0"
1861edition = "2021"
1862
1863[dependencies]
1864logicaffeine-data = { path = "./crates/logicaffeine_data" }
1865logicaffeine-system = { path = "./crates/logicaffeine_system", features = ["full"] }
1866tokio = { version = "1", features = ["rt-multi-thread", "macros"] }
1867"#);
1868
1869    // Append user-declared dependencies from ## Requires blocks — inside
1870    // [dependencies], before any later section header (a dep written after
1871    // a [target.*] or [profile.*] header would silently change meaning).
1872    for dep in dependencies {
1873        if dep.features.is_empty() {
1874            let _ = writeln!(cargo_toml, "{} = \"{}\"", dep.name, dep.version);
1875        } else {
1876            let feats = dep.features.iter()
1877                .map(|f| format!("\"{}\"", f))
1878                .collect::<Vec<_>>()
1879                .join(", ");
1880            let _ = writeln!(
1881                cargo_toml,
1882                "{} = {{ version = \"{}\", features = [{}] }}",
1883                dep.name, dep.version, feats
1884            );
1885        }
1886    }
1887
1888    cargo_toml.push_str(r#"
1889[target.'cfg(target_os = "linux")'.dependencies]
1890logicaffeine-system = { path = "./crates/logicaffeine_system", features = ["full", "io-uring"] }
1891"#);
1892
1893    cargo_toml.push_str("\n[profile.release]\nlto = true\nopt-level = 3\ncodegen-units = 1\npanic = \"abort\"\nstrip = true\n");
1894
1895    let cargo_path = output_dir.join("Cargo.toml");
1896    let mut file = fs::File::create(&cargo_path).map_err(|e| CompileError::Io(e.to_string()))?;
1897    file.write_all(cargo_toml.as_bytes()).map_err(|e| CompileError::Io(e.to_string()))?;
1898
1899    // Write .cargo/config.toml with target-cpu=native for optimal codegen.
1900    // Enables SIMD auto-vectorization and CPU-specific instruction selection.
1901    let cargo_config_dir = output_dir.join(".cargo");
1902    fs::create_dir_all(&cargo_config_dir).map_err(|e| CompileError::Io(e.to_string()))?;
1903    let config_content = "[build]\nrustflags = [\"-C\", \"target-cpu=native\"]\n";
1904    let config_path = cargo_config_dir.join("config.toml");
1905    fs::write(&config_path, config_content).map_err(|e| CompileError::Io(e.to_string()))?;
1906
1907    // Copy runtime crates to output directory
1908    copy_runtime_crates(output_dir)?;
1909
1910    Ok(())
1911}
1912
1913/// Copy the runtime crates to the output directory.
1914/// Copies logicaffeine_data and logicaffeine_system.
1915pub fn copy_runtime_crates(output_dir: &Path) -> Result<(), CompileError> {
1916    let crates_dir = output_dir.join("crates");
1917    fs::create_dir_all(&crates_dir).map_err(|e| CompileError::Io(e.to_string()))?;
1918
1919    // Find workspace root
1920    let workspace_root = find_workspace_root()?;
1921
1922    // Copy logicaffeine_data
1923    let data_src = workspace_root.join(CRATES_DATA_PATH);
1924    let data_dest = crates_dir.join("logicaffeine_data");
1925    copy_dir_recursive(&data_src, &data_dest)?;
1926    deworkspace_cargo_toml(&data_dest.join("Cargo.toml"))?;
1927
1928    // Copy logicaffeine_system
1929    let system_src = workspace_root.join(CRATES_SYSTEM_PATH);
1930    let system_dest = crates_dir.join("logicaffeine_system");
1931    copy_dir_recursive(&system_src, &system_dest)?;
1932    deworkspace_cargo_toml(&system_dest.join("Cargo.toml"))?;
1933
1934    // Also need to copy logicaffeine_base since both crates depend on it
1935    let base_src = workspace_root.join("crates/logicaffeine_base");
1936    let base_dest = crates_dir.join("logicaffeine_base");
1937    copy_dir_recursive(&base_src, &base_dest)?;
1938    deworkspace_cargo_toml(&base_dest.join("Cargo.toml"))?;
1939
1940    Ok(())
1941}
1942
1943/// Resolve workspace-inherited fields in a copied crate's Cargo.toml.
1944///
1945/// When runtime crates are copied to a standalone project, any fields using
1946/// `*.workspace = true` won't resolve because there's no parent workspace.
1947/// This rewrites them with concrete values (matching the workspace's settings).
1948fn deworkspace_cargo_toml(cargo_toml_path: &Path) -> Result<(), CompileError> {
1949    let content = fs::read_to_string(cargo_toml_path)
1950        .map_err(|e| CompileError::Io(e.to_string()))?;
1951
1952    let mut result = String::with_capacity(content.len());
1953    for line in content.lines() {
1954        let trimmed = line.trim();
1955        if trimmed == "edition.workspace = true" {
1956            result.push_str("edition = \"2021\"");
1957        } else if trimmed == "rust-version.workspace = true" {
1958            result.push_str("rust-version = \"1.75\"");
1959        } else if trimmed == "authors.workspace = true"
1960            || trimmed == "repository.workspace = true"
1961            || trimmed == "homepage.workspace = true"
1962            || trimmed == "documentation.workspace = true"
1963            || trimmed == "keywords.workspace = true"
1964            || trimmed == "categories.workspace = true"
1965            || trimmed == "license.workspace = true"
1966        {
1967            // Drop these lines — they're metadata not needed for compilation
1968            continue;
1969        } else if trimmed.contains(".workspace = true") {
1970            // Catch-all: drop any other workspace-inherited fields
1971            continue;
1972        } else if let Some(rewritten) = deworkspace_dep_line(trimmed)? {
1973            // A dependency-TABLE inheritance (`name = { workspace = true, … }`):
1974            // internal crates are staged side-by-side under `crates/`, so the
1975            // inherited entry rewrites to a sibling path (extra keys kept).
1976            result.push_str(&rewritten);
1977        } else {
1978            result.push_str(line);
1979        }
1980        result.push('\n');
1981    }
1982
1983    fs::write(cargo_toml_path, result)
1984        .map_err(|e| CompileError::Io(e.to_string()))?;
1985
1986    Ok(())
1987}
1988
1989/// Rewrite a `name = { workspace = true, … }` DEPENDENCY line for a staged
1990/// crate. Internal `logicaffeine-*` deps become sibling-path deps (the staged
1991/// layout puts every runtime crate under one `crates/` directory); any other
1992/// key on the line (`features`, `optional`, …) is preserved. A THIRD-PARTY
1993/// workspace-inherited dep fails LOUDLY here — at staging time, with the dep
1994/// named — instead of as an opaque cargo resolution error deep in a build log.
1995/// Returns `Ok(None)` for lines that are not workspace-dep tables.
1996fn deworkspace_dep_line(trimmed: &str) -> Result<Option<String>, CompileError> {
1997    let Some((name_part, rest)) = trimmed.split_once('=') else {
1998        return Ok(None);
1999    };
2000    let rest = rest.trim();
2001    if !rest.starts_with('{') || !rest.ends_with('}') || !rest.contains("workspace = true") {
2002        return Ok(None);
2003    }
2004    let name = name_part.trim();
2005    if !name.chars().all(|c| c.is_ascii_alphanumeric() || c == '-' || c == '_') {
2006        return Ok(None);
2007    }
2008    let extra_keys: Vec<&str> = rest[1..rest.len() - 1]
2009        .split(',')
2010        .map(str::trim)
2011        .filter(|k| !k.is_empty() && *k != "workspace = true")
2012        .collect();
2013    if !name.starts_with("logicaffeine-") {
2014        return Err(CompileError::Io(format!(
2015            "staged crate inherits third-party dependency `{}` from the workspace — \
2016             declare a concrete version in the crate's Cargo.toml (the staged copy \
2017             has no workspace root to inherit from)",
2018            name
2019        )));
2020    }
2021    let dir = name.replace('-', "_");
2022    let mut entry = format!("{} = {{ path = \"../{}\"", name, dir);
2023    for key in extra_keys {
2024        entry.push_str(", ");
2025        entry.push_str(key);
2026    }
2027    entry.push_str(" }");
2028    Ok(Some(entry))
2029}
2030
2031/// Find the workspace root directory.
2032fn find_workspace_root() -> Result<std::path::PathBuf, CompileError> {
2033    // 1. Explicit override via LOGOS_WORKSPACE env var
2034    if let Ok(workspace) = std::env::var("LOGOS_WORKSPACE") {
2035        let path = Path::new(&workspace);
2036        if path.join("Cargo.toml").exists() && path.join("crates").exists() {
2037            return Ok(path.to_path_buf());
2038        }
2039    }
2040
2041    // 2. Try CARGO_MANIFEST_DIR (works during cargo build of largo itself)
2042    if let Ok(manifest_dir) = std::env::var("CARGO_MANIFEST_DIR") {
2043        let path = Path::new(&manifest_dir);
2044        if let Some(parent) = path.parent().and_then(|p| p.parent()) {
2045            if parent.join("Cargo.toml").exists() {
2046                return Ok(parent.to_path_buf());
2047            }
2048        }
2049    }
2050
2051    // 3. Infer from the largo binary's own location
2052    //    e.g. /workspace/target/release/largo → /workspace
2053    if let Ok(exe) = std::env::current_exe() {
2054        if let Some(dir) = exe.parent() {
2055            // Walk up from the binary's directory
2056            let mut candidate = dir.to_path_buf();
2057            for _ in 0..5 {
2058                if candidate.join("Cargo.toml").exists() && candidate.join("crates").exists() {
2059                    return Ok(candidate);
2060                }
2061                if !candidate.pop() {
2062                    break;
2063                }
2064            }
2065        }
2066    }
2067
2068    // 4. Fallback to current directory traversal
2069    let mut current = std::env::current_dir()
2070        .map_err(|e| CompileError::Io(e.to_string()))?;
2071
2072    loop {
2073        if current.join("Cargo.toml").exists() && current.join("crates").exists() {
2074            return Ok(current);
2075        }
2076        if !current.pop() {
2077            return Err(CompileError::Io(
2078                "Could not find workspace root. Set LOGOS_WORKSPACE env var or run from within the workspace.".to_string()
2079            ));
2080        }
2081    }
2082}
2083
2084/// Recursively copy a directory.
2085/// Skips files that disappear during copy (race condition with parallel builds).
2086fn copy_dir_recursive(src: &Path, dst: &Path) -> Result<(), CompileError> {
2087    fs::create_dir_all(dst).map_err(|e| CompileError::Io(e.to_string()))?;
2088
2089    for entry in fs::read_dir(src).map_err(|e| CompileError::Io(e.to_string()))? {
2090        let entry = entry.map_err(|e| CompileError::Io(e.to_string()))?;
2091        let src_path = entry.path();
2092        let file_name = entry.file_name();
2093        let dst_path = dst.join(&file_name);
2094
2095        // Skip target directory, build artifacts, and lock files
2096        if file_name == "target"
2097            || file_name == ".git"
2098            || file_name == "Cargo.lock"
2099            || file_name == ".DS_Store"
2100        {
2101            continue;
2102        }
2103
2104        // Skip files that start with a dot (hidden files)
2105        if file_name.to_string_lossy().starts_with('.') {
2106            continue;
2107        }
2108
2109        // Check if path still exists (race condition protection)
2110        if !src_path.exists() {
2111            continue;
2112        }
2113
2114        if src_path.is_dir() {
2115            copy_dir_recursive(&src_path, &dst_path)?;
2116        } else if file_name == "Cargo.toml" {
2117            // Special handling for Cargo.toml: remove [workspace] line
2118            // which can interfere with nested crate dependencies
2119            match fs::read_to_string(&src_path) {
2120                Ok(content) => {
2121                    let filtered: String = content
2122                        .lines()
2123                        .filter(|line| !line.trim().starts_with("[workspace]"))
2124                        .collect::<Vec<_>>()
2125                        .join("\n");
2126                    fs::write(&dst_path, filtered)
2127                        .map_err(|e| CompileError::Io(e.to_string()))?;
2128                }
2129                Err(e) if e.kind() == std::io::ErrorKind::NotFound => continue,
2130                Err(e) => return Err(CompileError::Io(e.to_string())),
2131            }
2132        } else {
2133            match fs::copy(&src_path, &dst_path) {
2134                Ok(_) => {}
2135                Err(e) if e.kind() == std::io::ErrorKind::NotFound => continue,
2136                Err(e) => return Err(CompileError::Io(e.to_string())),
2137            }
2138        }
2139    }
2140
2141    Ok(())
2142}
2143
2144/// Compile and run a LOGOS program end-to-end.
2145///
2146/// This function performs the full compilation workflow:
2147/// 1. Compile LOGOS to Rust via [`compile_to_dir`]
2148/// 2. Run `cargo build` with JSON diagnostics
2149/// 3. Translate any rustc errors to LOGOS-friendly messages
2150/// 4. Run the compiled program via `cargo run`
2151///
2152/// # Arguments
2153///
2154/// * `source` - LOGOS source code as a string
2155/// * `output_dir` - Directory to create the temporary Cargo project in
2156///
2157/// # Returns
2158///
2159/// The stdout output of the executed program.
2160///
2161/// # Errors
2162///
2163/// Returns [`CompileError`] if:
2164/// - Compilation fails (see [`compile_to_dir`])
2165/// - Rust compilation fails (`CompileError::Build` or `CompileError::Ownership`)
2166/// - The program crashes at runtime (`CompileError::Runtime`)
2167///
2168/// # Diagnostic Translation
2169///
2170/// When rustc reports errors (e.g., E0382 for use-after-move), this function
2171/// uses the [`diagnostic`](crate::diagnostic) module to translate them into
2172/// LOGOS-friendly Socratic error messages.
2173///
2174/// # Example
2175///
2176/// ```no_run
2177/// # use logicaffeine_compile::compile::{compile_and_run, CompileError};
2178/// # use std::path::Path;
2179/// # fn main() -> Result<(), CompileError> {
2180/// let source = "## Main\nShow \"Hello, World!\".";
2181/// let output = compile_and_run(source, Path::new("/tmp/run"))?;
2182/// assert_eq!(output.trim(), "Hello, World!");
2183/// # Ok(())
2184/// # }
2185/// ```
2186pub fn compile_and_run(source: &str, output_dir: &Path) -> Result<String, CompileError> {
2187    // Pre-check: catch ownership errors (use-after-move) with friendly messages
2188    // before codegen runs (codegen defensively clones, masking these errors)
2189    compile_to_rust_checked(source).map_err(CompileError::Parse)?;
2190
2191    compile_to_dir(source, output_dir)?;
2192
2193    // Run cargo build with JSON message format for structured error parsing
2194    let build_output = Command::new("cargo")
2195        .arg("build")
2196        .arg("--message-format=json")
2197        .current_dir(output_dir)
2198        .output()
2199        .map_err(|e| CompileError::Io(e.to_string()))?;
2200
2201    if !build_output.status.success() {
2202        let stderr = String::from_utf8_lossy(&build_output.stderr);
2203        let stdout = String::from_utf8_lossy(&build_output.stdout);
2204
2205        // Try to parse JSON diagnostics and translate them
2206        let diagnostics = parse_rustc_json(&stdout);
2207
2208        if !diagnostics.is_empty() {
2209            // Create a basic source map with the LOGOS source
2210            let source_map = SourceMap::new(source.to_string());
2211            let interner = Interner::new();
2212
2213            if let Some(logos_error) = translate_diagnostics(&diagnostics, &source_map, &interner) {
2214                return Err(CompileError::Ownership(logos_error));
2215            }
2216        }
2217
2218        // Fallback to raw error if translation fails
2219        return Err(CompileError::Build(stderr.to_string()));
2220    }
2221
2222    // Run the compiled program
2223    let run_output = Command::new("cargo")
2224        .arg("run")
2225        .arg("--quiet")
2226        .current_dir(output_dir)
2227        .output()
2228        .map_err(|e| CompileError::Io(e.to_string()))?;
2229
2230    if !run_output.status.success() {
2231        let stderr = String::from_utf8_lossy(&run_output.stderr);
2232        return Err(CompileError::Runtime(stderr.to_string()));
2233    }
2234
2235    let stdout = String::from_utf8_lossy(&run_output.stdout);
2236    Ok(stdout.to_string())
2237}
2238
2239/// Compile a LOGOS source file.
2240/// For single-file compilation without dependencies.
2241pub fn compile_file(path: &Path) -> Result<String, CompileError> {
2242    let source = fs::read_to_string(path).map_err(|e| CompileError::Io(e.to_string()))?;
2243    compile_to_rust(&source).map_err(CompileError::Parse)
2244}
2245
2246/// Compile a multi-file LOGOS project with dependency resolution.
2247///
2248/// This function:
2249/// 1. Reads the entry file
2250/// 2. Scans for dependencies in the abstract (Markdown links)
2251/// 3. Recursively loads and discovers types from dependencies
2252/// 4. Compiles with the combined type registry
2253///
2254/// # Arguments
2255/// * `entry_file` - The main entry file to compile (root is derived from parent directory)
2256///
2257/// # Example
2258/// ```no_run
2259/// # use logicaffeine_compile::compile::compile_project;
2260/// # use std::path::Path;
2261/// let result = compile_project(Path::new("/project/main.md"));
2262/// ```
2263pub fn compile_project(entry_file: &Path) -> Result<CompileOutput, CompileError> {
2264    use crate::loader::Loader;
2265    use crate::analysis::discover_with_imports;
2266
2267    let root_path = entry_file.parent().unwrap_or(Path::new(".")).to_path_buf();
2268    let mut loader = Loader::new(root_path);
2269    let mut interner = Interner::new();
2270
2271    // Read the entry file
2272    let raw_source = fs::read_to_string(entry_file)
2273        .map_err(|e| CompileError::Io(format!("Failed to read entry file: {}", e)))?;
2274
2275    // Auto-import the demand-driven stdlib prelude (uuid.lg, io/net/crdt/time/…) — the SAME seam the
2276    // string-compile and interpreter paths use (see `apply_prelude`), so `largo build` resolves stdlib
2277    // functions like `md5`/`sha1`/`uuidV3`/`uuidParse`/`flush`. Demand-driven + idempotent: a program
2278    // that references no stdlib name compiles byte-identically to before.
2279    let source = crate::loader::apply_prelude(&raw_source).into_owned();
2280
2281    // Discover types from entry file and all imports
2282    let type_registry = discover_with_imports(entry_file, &source, &mut loader, &mut interner)
2283        .map_err(|e| CompileError::Io(e))?;
2284
2285    // Now compile with the discovered types
2286    compile_to_rust_with_registry_full(&source, type_registry, &mut interner)
2287        .map_err(CompileError::Parse)
2288}
2289
2290/// Compile LOGOS source with a pre-populated type registry, returning full output.
2291/// Returns both generated Rust code and extracted dependencies.
2292fn compile_to_rust_with_registry_full(
2293    source: &str,
2294    type_registry: crate::analysis::TypeRegistry,
2295    interner: &mut Interner,
2296) -> Result<CompileOutput, ParseError> {
2297    let mut lexer = Lexer::new(source, interner);
2298    let tokens = lexer.tokenize();
2299
2300    // Discovery pass for policies (types already discovered)
2301    let policy_registry = {
2302        let mut discovery = DiscoveryPass::new(&tokens, interner);
2303        discovery.run_full().policies
2304    };
2305
2306    let codegen_registry = type_registry.clone();
2307    let codegen_policies = policy_registry.clone();
2308
2309    let mut world_state = WorldState::new();
2310    let expr_arena = Arena::new();
2311    let term_arena = Arena::new();
2312    let np_arena = Arena::new();
2313    let sym_arena = Arena::new();
2314    let role_arena = Arena::new();
2315    let pp_arena = Arena::new();
2316    let stmt_arena: Arena<Stmt> = Arena::new();
2317    let imperative_expr_arena: Arena<Expr> = Arena::new();
2318    let type_expr_arena: Arena<TypeExpr> = Arena::new();
2319
2320    let ast_ctx = AstContext::with_types(
2321        &expr_arena,
2322        &term_arena,
2323        &np_arena,
2324        &sym_arena,
2325        &role_arena,
2326        &pp_arena,
2327        &stmt_arena,
2328        &imperative_expr_arena,
2329        &type_expr_arena,
2330    );
2331
2332    let mut parser = Parser::new(tokens, &mut world_state, interner, ast_ctx, type_registry);
2333    let stmts = parser.parse_program()?;
2334
2335    // Run the AST optimizer in the production (largo) compile path so
2336    // compiled binaries get the same optimizations the test path
2337    // (compile_program_full) already validates: closed-form / modulus
2338    // deferral, partial evaluation, constant folding, GVN, LICM,
2339    // deforestation, CTFE, supercompilation. Previously skipped here, which
2340    // left the whole optimize layer off for compiled programs. Confirmed a
2341    // clean win (geomean 1.388x -> 1.735x C-speed, all benchmarks verify
2342    // correct, no regressions).
2343    let stmts = crate::optimize::optimize_program(stmts, &imperative_expr_arena, &stmt_arena, interner, &crate::optimization::OptimizationConfig::from_env());
2344
2345    // Extract dependencies before escape analysis
2346    let mut dependencies = extract_dependencies(&stmts, interner)?;
2347
2348    // FFI: Auto-inject wasm-bindgen dependency if any function is exported for WASM
2349    let needs_wasm_bindgen = stmts.iter().any(|stmt| {
2350        if let Stmt::FunctionDef { is_exported: true, export_target: Some(target), .. } = stmt {
2351            interner.resolve(*target).eq_ignore_ascii_case("wasm")
2352        } else {
2353            false
2354        }
2355    });
2356    if needs_wasm_bindgen && !dependencies.iter().any(|d| d.name == "wasm-bindgen") {
2357        dependencies.push(CrateDependency {
2358            name: "wasm-bindgen".to_string(),
2359            version: "0.2".to_string(),
2360            features: vec![],
2361        });
2362    }
2363
2364    let mut escape_checker = EscapeChecker::new(interner);
2365    escape_checker.check_program(&stmts).map_err(|e| {
2366        ParseError {
2367            kind: crate::error::ParseErrorKind::Custom(e.to_string()),
2368            span: e.span,
2369        }
2370    })?;
2371
2372    let type_env = crate::analysis::check_program(&stmts, interner, &codegen_registry)
2373        .map_err(|e| ParseError {
2374            kind: e.to_parse_error_kind(interner),
2375            span: crate::token::Span::default(),
2376        })?;
2377    let rust_code = codegen_program(&stmts, &codegen_registry, &codegen_policies, interner, &type_env, &crate::optimization::OptimizationConfig::from_env());
2378
2379    // Universal ABI: Generate C header + bindings if any C exports exist
2380    let has_c = stmts.iter().any(|stmt| {
2381        if let Stmt::FunctionDef { is_exported: true, export_target, .. } = stmt {
2382            match export_target {
2383                None => true,
2384                Some(t) => interner.resolve(*t).eq_ignore_ascii_case("c"),
2385            }
2386        } else {
2387            false
2388        }
2389    });
2390
2391    let c_header = if has_c {
2392        Some(generate_c_header(&stmts, "module", interner, &codegen_registry))
2393    } else {
2394        None
2395    };
2396
2397    if has_c && !dependencies.iter().any(|d| d.name == "serde_json") {
2398        dependencies.push(CrateDependency {
2399            name: "serde_json".to_string(),
2400            version: "1".to_string(),
2401            features: vec![],
2402        });
2403    }
2404
2405    let python_bindings = if has_c {
2406        Some(generate_python_bindings(&stmts, "module", interner, &codegen_registry))
2407    } else {
2408        None
2409    };
2410
2411    let (typescript_bindings, typescript_types) = if has_c {
2412        let (js, dts) = generate_typescript_bindings(&stmts, "module", interner, &codegen_registry);
2413        (Some(js), Some(dts))
2414    } else {
2415        (None, None)
2416    };
2417
2418    Ok(CompileOutput { rust_code, dependencies, c_header, python_bindings, typescript_types, typescript_bindings })
2419}
2420
2421/// Errors that can occur during the LOGOS compilation pipeline.
2422///
2423/// This enum represents the different stages where compilation can fail,
2424/// from parsing through to runtime execution.
2425///
2426/// # Error Hierarchy
2427///
2428/// ```text
2429/// CompileError
2430/// ├── Parse      ← Lexing, parsing, or static analysis
2431/// ├── Io         ← File system operations
2432/// ├── Build      ← Rust compilation (cargo build)
2433/// ├── Ownership  ← Translated borrow checker errors
2434/// └── Runtime    ← Program execution failure
2435/// ```
2436///
2437/// # Error Translation
2438///
2439/// The `Ownership` variant contains LOGOS-friendly error messages translated
2440/// from rustc's borrow checker errors (E0382, E0505, E0597) using the
2441/// [`diagnostic`](crate::diagnostic) module.
2442#[derive(Debug)]
2443pub enum CompileError {
2444    /// Parsing or static analysis failed.
2445    ///
2446    /// This includes lexer errors, syntax errors, escape analysis failures,
2447    /// ownership analysis failures, and Z3 verification failures.
2448    Parse(ParseError),
2449
2450    /// File system operation failed.
2451    ///
2452    /// Typically occurs when reading source files or writing output.
2453    Io(String),
2454
2455    /// Rust compilation failed (`cargo build`).
2456    ///
2457    /// Contains the raw stderr output from rustc when diagnostic translation
2458    /// was not possible.
2459    Build(String),
2460
2461    /// Runtime execution failed.
2462    ///
2463    /// Contains stderr output from the executed program.
2464    Runtime(String),
2465
2466    /// Translated ownership/borrow checker error with LOGOS-friendly message.
2467    ///
2468    /// This variant is used when rustc reports errors like E0382 (use after move)
2469    /// and we can translate them into natural language error messages that
2470    /// reference the original LOGOS source.
2471    Ownership(LogosError),
2472}
2473
2474impl std::fmt::Display for CompileError {
2475    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2476        match self {
2477            CompileError::Parse(e) => write!(f, "Parse error: {:?}", e),
2478            CompileError::Io(e) => write!(f, "IO error: {}", e),
2479            CompileError::Build(e) => write!(f, "Build error: {}", e),
2480            CompileError::Runtime(e) => write!(f, "Runtime error: {}", e),
2481            CompileError::Ownership(e) => write!(f, "{}", e),
2482        }
2483    }
2484}
2485
2486impl std::error::Error for CompileError {}
2487
2488// ============================================================
2489// Futamura Projection Support — encode_program + verify_no_overhead
2490// ============================================================
2491
2492/// Encode a LogicAffeine program (given as source) into CProgram construction source.
2493///
2494/// Takes LogicAffeine source code (with or without `## Main` header) and returns
2495/// LogicAffeine source code that constructs the equivalent CProgram data structure.
2496/// The result defines a variable `prog` of type CProgram.
2497pub fn encode_program_source(source: &str) -> Result<String, ParseError> {
2498    let full_source = if source.contains("## Main") || source.contains("## To ") {
2499        source.to_string()
2500    } else {
2501        format!("## Main\n{}", source)
2502    };
2503
2504    let mut interner = Interner::new();
2505    let mut lexer = Lexer::new(&full_source, &mut interner);
2506    let tokens = lexer.tokenize();
2507
2508    let type_registry = {
2509        let mut discovery = DiscoveryPass::new(&tokens, &mut interner);
2510        let result = discovery.run_full();
2511        result.types
2512    };
2513
2514    // Collect variant constructors before the parser takes ownership of type_registry
2515    let mut variant_constructors: HashMap<String, Vec<String>> = HashMap::new();
2516    for (_type_name, type_def) in type_registry.iter_types() {
2517        if let crate::analysis::TypeDef::Enum { variants, .. } = type_def {
2518            for variant in variants {
2519                let vname = interner.resolve(variant.name).to_string();
2520                let field_names: Vec<String> = variant.fields.iter()
2521                    .map(|f| interner.resolve(f.name).to_string())
2522                    .collect();
2523                variant_constructors.insert(vname, field_names);
2524            }
2525        }
2526    }
2527
2528    let mut world_state = WorldState::new();
2529    let expr_arena = Arena::new();
2530    let term_arena = Arena::new();
2531    let np_arena = Arena::new();
2532    let sym_arena = Arena::new();
2533    let role_arena = Arena::new();
2534    let pp_arena = Arena::new();
2535    let stmt_arena: Arena<Stmt> = Arena::new();
2536    let imperative_expr_arena: Arena<Expr> = Arena::new();
2537    let type_expr_arena: Arena<TypeExpr> = Arena::new();
2538
2539    let ast_ctx = AstContext::with_types(
2540        &expr_arena, &term_arena, &np_arena, &sym_arena,
2541        &role_arena, &pp_arena, &stmt_arena, &imperative_expr_arena,
2542        &type_expr_arena,
2543    );
2544
2545    let mut parser = crate::parser::Parser::new(
2546        tokens, &mut world_state, &mut interner, ast_ctx, type_registry,
2547    );
2548    let stmts = parser.parse_program()?;
2549
2550    let mut functions: Vec<(String, Vec<String>, Vec<String>, String, Vec<&Stmt>)> = Vec::new();
2551    let mut main_stmts: Vec<&Stmt> = Vec::new();
2552
2553    for stmt in &stmts {
2554        if let Stmt::FunctionDef { name, params, body, return_type, is_native, .. } = stmt {
2555            if *is_native {
2556                continue; // Skip native function declarations — they have no encodable body
2557            }
2558            let fn_name = interner.resolve(*name).to_string();
2559            let param_names: Vec<String> = params
2560                .iter()
2561                .map(|(name, _)| interner.resolve(*name).to_string())
2562                .collect();
2563            let param_types: Vec<String> = params
2564                .iter()
2565                .map(|(_, ty)| decompile_type_expr(ty, &interner))
2566                .collect();
2567            let ret_type = return_type
2568                .map(|rt| decompile_type_expr(rt, &interner))
2569                .unwrap_or_else(|| "Nothing".to_string());
2570            let body_stmts: Vec<&Stmt> = body.iter().collect();
2571            functions.push((fn_name, param_names, param_types, ret_type, body_stmts));
2572        } else {
2573            main_stmts.push(stmt);
2574        }
2575    }
2576
2577    let mut counter = 0usize;
2578    let mut output = String::new();
2579    reset_inspect_otherwise_idx(); // deterministic `__inspectMatched_N` numbering, shared with the native builder
2580
2581    // Build the funcMap directly (Map of Text to CFunc) with fixed name, plus a parallel
2582    // `encodedFuncSeq` (Seq of CFunc, declaration order) — the map has no meaning-preserving
2583    // iteration order, so the wire serializer (which ships a `CProg with funcs …`) needs the seq.
2584    output.push_str("Let encodedFuncMap be a new Map of Text to CFunc.\n");
2585    output.push_str("Let encodedFuncSeq be a new Seq of CFunc.\n");
2586
2587    for (fn_name, params, param_types, ret_type, body) in &functions {
2588        let body_var = encode_stmt_list_src(body, &mut counter, &mut output, &interner, &variant_constructors);
2589
2590        let params_var = format!("params_{}", counter);
2591        counter += 1;
2592        output.push_str(&format!("Let {} be a new Seq of Text.\n", params_var));
2593        for p in params {
2594            output.push_str(&format!("Push \"{}\" to {}.\n", p, params_var));
2595        }
2596
2597        let param_types_var = format!("paramTypes_{}", counter);
2598        counter += 1;
2599        output.push_str(&format!("Let {} be a new Seq of Text.\n", param_types_var));
2600        for pt in param_types {
2601            output.push_str(&format!("Push \"{}\" to {}.\n", pt, param_types_var));
2602        }
2603
2604        let func_var = format!("func_{}", counter);
2605        counter += 1;
2606        output.push_str(&format!(
2607            "Let {} be a new CFuncDef with name \"{}\" and params {} and paramTypes {} and returnType \"{}\" and body {}.\n",
2608            func_var, fn_name, params_var, param_types_var, ret_type, body_var
2609        ));
2610        // `copy of` + emitted BEFORE the map `Set` (which MOVES func_var in AOT codegen): the seq
2611        // gets an identical clone, the map keeps the original — no use-after-move (E0382) when the
2612        // encoded program is compiled to Rust. The interpreter clones anyway, so this is a pure
2613        // codegen-soundness fix, behaviourally identical on every tier.
2614        output.push_str(&format!("Push copy of {} to encodedFuncSeq.\n", func_var));
2615        output.push_str(&format!(
2616            "Set item \"{}\" of encodedFuncMap to {}.\n",
2617            fn_name, func_var
2618        ));
2619    }
2620
2621    // Build main statement list with fixed name
2622    let main_var = encode_stmt_list_src(&main_stmts, &mut counter, &mut output, &interner, &variant_constructors);
2623    output.push_str(&format!("Let encodedMain be {}.\n", main_var));
2624
2625    Ok(output)
2626}
2627
2628/// Compact encoding: inlines simple expressions (literals, variables) to reduce
2629/// encoding size by ~3x. Same semantics as encode_program_source but produces
2630/// fewer Let statements.
2631pub fn encode_program_source_compact(source: &str) -> Result<String, ParseError> {
2632    let full_source = if source.contains("## Main") || source.contains("## To ") {
2633        source.to_string()
2634    } else {
2635        format!("## Main\n{}", source)
2636    };
2637
2638    let mut interner = Interner::new();
2639    let mut lexer = Lexer::new(&full_source, &mut interner);
2640    let tokens = lexer.tokenize();
2641
2642    let type_registry = {
2643        let mut discovery = DiscoveryPass::new(&tokens, &mut interner);
2644        let result = discovery.run_full();
2645        result.types
2646    };
2647
2648    let mut variant_constructors: HashMap<String, Vec<String>> = HashMap::new();
2649    for (_type_name, type_def) in type_registry.iter_types() {
2650        if let crate::analysis::TypeDef::Enum { variants, .. } = type_def {
2651            for variant in variants {
2652                let vname = interner.resolve(variant.name).to_string();
2653                let field_names: Vec<String> = variant.fields.iter()
2654                    .map(|f| interner.resolve(f.name).to_string())
2655                    .collect();
2656                variant_constructors.insert(vname, field_names);
2657            }
2658        }
2659    }
2660
2661    let mut world_state = WorldState::new();
2662    let expr_arena = Arena::new();
2663    let term_arena = Arena::new();
2664    let np_arena = Arena::new();
2665    let sym_arena = Arena::new();
2666    let role_arena = Arena::new();
2667    let pp_arena = Arena::new();
2668    let stmt_arena: Arena<Stmt> = Arena::new();
2669    let imperative_expr_arena: Arena<Expr> = Arena::new();
2670    let type_expr_arena: Arena<TypeExpr> = Arena::new();
2671
2672    let ast_ctx = AstContext::with_types(
2673        &expr_arena, &term_arena, &np_arena, &sym_arena,
2674        &role_arena, &pp_arena, &stmt_arena, &imperative_expr_arena,
2675        &type_expr_arena,
2676    );
2677
2678    let mut parser = crate::parser::Parser::new(
2679        tokens, &mut world_state, &mut interner, ast_ctx, type_registry,
2680    );
2681    let stmts = parser.parse_program()?;
2682
2683    let mut functions: Vec<(String, Vec<String>, Vec<String>, String, Vec<&Stmt>)> = Vec::new();
2684    let mut main_stmts: Vec<&Stmt> = Vec::new();
2685
2686    for stmt in &stmts {
2687        if let Stmt::FunctionDef { name, params, body, return_type, is_native, .. } = stmt {
2688            if *is_native { continue; }
2689            let fn_name = interner.resolve(*name).to_string();
2690            let param_names: Vec<String> = params
2691                .iter()
2692                .map(|(name, _)| interner.resolve(*name).to_string())
2693                .collect();
2694            let param_types: Vec<String> = params
2695                .iter()
2696                .map(|(_, ty)| decompile_type_expr(ty, &interner))
2697                .collect();
2698            let ret_type = return_type
2699                .map(|rt| decompile_type_expr(rt, &interner))
2700                .unwrap_or_else(|| "Nothing".to_string());
2701            let body_stmts: Vec<&Stmt> = body.iter().collect();
2702            functions.push((fn_name, param_names, param_types, ret_type, body_stmts));
2703        } else {
2704            main_stmts.push(stmt);
2705        }
2706    }
2707
2708    let mut counter = 0usize;
2709    let mut output = String::new();
2710
2711    output.push_str("Let encodedFuncMap be a new Map of Text to CFunc.\n");
2712
2713    for (fn_name, params, param_types, ret_type, body) in &functions {
2714        let body_var = encode_stmt_list_compact(body, &mut counter, &mut output, &interner, &variant_constructors);
2715
2716        let params_var = format!("params_{}", counter);
2717        counter += 1;
2718        output.push_str(&format!("Let {} be a new Seq of Text.\n", params_var));
2719        for p in params {
2720            output.push_str(&format!("Push \"{}\" to {}.\n", p, params_var));
2721        }
2722
2723        let param_types_var = format!("paramTypes_{}", counter);
2724        counter += 1;
2725        output.push_str(&format!("Let {} be a new Seq of Text.\n", param_types_var));
2726        for pt in param_types {
2727            output.push_str(&format!("Push \"{}\" to {}.\n", pt, param_types_var));
2728        }
2729
2730        let func_var = format!("func_{}", counter);
2731        counter += 1;
2732        output.push_str(&format!(
2733            "Let {} be a new CFuncDef with name \"{}\" and params {} and paramTypes {} and returnType \"{}\" and body {}.\n",
2734            func_var, fn_name, params_var, param_types_var, ret_type, body_var
2735        ));
2736        output.push_str(&format!(
2737            "Set item \"{}\" of encodedFuncMap to {}.\n",
2738            fn_name, func_var
2739        ));
2740    }
2741
2742    let main_var = encode_stmt_list_compact(&main_stmts, &mut counter, &mut output, &interner, &variant_constructors);
2743    output.push_str(&format!("Let encodedMain be {}.\n", main_var));
2744
2745    Ok(output)
2746}
2747
2748/// Returns an inline expression string for simple expressions (no Let variable needed).
2749/// Returns None for complex expressions that require a Let variable.
2750fn try_inline_expr(expr: &Expr, interner: &Interner) -> Option<String> {
2751    match expr {
2752        Expr::Literal(lit) => match lit {
2753            Literal::Number(n) => Some(format!("(a new CInt with value {})", n)),
2754            Literal::Boolean(b) => Some(format!("(a new CBool with value {})", b)),
2755            Literal::Text(s) => {
2756                let text = interner.resolve(*s);
2757                Some(format!("(a new CText with value \"{}\")", text))
2758            }
2759            Literal::Float(f) => {
2760                let fs = format!("{}", f);
2761                let fs = if fs.contains('.') { fs } else { format!("{}.0", fs) };
2762                Some(format!("(a new CFloat with value {})", fs))
2763            }
2764            Literal::Nothing => Some("(a new CText with value \"nothing\")".to_string()),
2765            _ => None,
2766        },
2767        Expr::Identifier(sym) => {
2768            let name = interner.resolve(*sym);
2769            Some(format!("(a new CVar with name \"{}\")", name))
2770        }
2771        Expr::Not { operand } => {
2772            if let Some(inner) = try_inline_expr(operand, interner) {
2773                Some(format!("(a new CNot with inner {})", inner))
2774            } else {
2775                None
2776            }
2777        }
2778        Expr::OptionNone => Some("(a new COptionNone)".to_string()),
2779        _ => None,
2780    }
2781}
2782
2783fn encode_expr_compact(expr: &Expr, counter: &mut usize, output: &mut String, interner: &Interner, variants: &HashMap<String, Vec<String>>) -> String {
2784    // Try inline first
2785    if let Some(inline) = try_inline_expr(expr, interner) {
2786        return inline;
2787    }
2788
2789    // Fall back to Let variable (reuse encode_expr_src logic but with compact children)
2790    let var = format!("e_{}", *counter);
2791    *counter += 1;
2792
2793    match expr {
2794        Expr::BinaryOp { op, left, right } => {
2795            let left_var = encode_expr_compact(left, counter, output, interner, variants);
2796            let right_var = encode_expr_compact(right, counter, output, interner, variants);
2797            let op_str = match op {
2798                BinaryOpKind::Add => "+",
2799                BinaryOpKind::Subtract => "-",
2800                BinaryOpKind::Multiply => "*",
2801                BinaryOpKind::Divide | BinaryOpKind::ExactDivide => "/",
2802                BinaryOpKind::FloorDivide => "//",
2803                BinaryOpKind::Modulo => "%",
2804                BinaryOpKind::Eq => "==",
2805                BinaryOpKind::NotEq => "!=",
2806                BinaryOpKind::Lt => "<",
2807                BinaryOpKind::Gt => ">",
2808                BinaryOpKind::LtEq => "<=",
2809                BinaryOpKind::GtEq => ">=",
2810                BinaryOpKind::And => "&&",
2811                BinaryOpKind::Or => "||",
2812                BinaryOpKind::Concat => "+",
2813                BinaryOpKind::SeqConcat => "followed by",
2814                BinaryOpKind::ApproxEq => "is approximately",
2815                BinaryOpKind::Pow => "**",
2816                BinaryOpKind::BitXor => "^",
2817                BinaryOpKind::BitAnd => "&",
2818                BinaryOpKind::BitOr => "|",
2819                BinaryOpKind::Shl => "<<",
2820                BinaryOpKind::Shr => ">>",
2821            };
2822            output.push_str(&format!(
2823                "Let {} be a new CBinOp with op \"{}\" and left {} and right {}.\n",
2824                var, op_str, left_var, right_var
2825            ));
2826        }
2827        Expr::Call { function, args } => {
2828            let fn_name = interner.resolve(*function);
2829            if let Some(field_names) = variants.get(fn_name) {
2830                let names_var = format!("nvNames_{}", *counter);
2831                *counter += 1;
2832                output.push_str(&format!("Let {} be a new Seq of Text.\n", names_var));
2833                let vals_var = format!("nvVals_{}", *counter);
2834                *counter += 1;
2835                output.push_str(&format!("Let {} be a new Seq of CExpr.\n", vals_var));
2836                for (i, arg) in args.iter().enumerate() {
2837                    let fname = field_names.get(i).map(|s| s.as_str()).unwrap_or("value");
2838                    output.push_str(&format!("Push \"{}\" to {}.\n", fname, names_var));
2839                    let arg_var = encode_expr_compact(arg, counter, output, interner, variants);
2840                    output.push_str(&format!("Push {} to {}.\n", arg_var, vals_var));
2841                }
2842                output.push_str(&format!(
2843                    "Let {} be a new CNewVariant with tag \"{}\" and fnames {} and fvals {}.\n",
2844                    var, fn_name, names_var, vals_var
2845                ));
2846            } else {
2847                let args_var = format!("callArgs_{}", *counter);
2848                *counter += 1;
2849                output.push_str(&format!("Let {} be a new Seq of CExpr.\n", args_var));
2850                for arg in args {
2851                    let arg_var = encode_expr_compact(arg, counter, output, interner, variants);
2852                    output.push_str(&format!("Push {} to {}.\n", arg_var, args_var));
2853                }
2854                output.push_str(&format!(
2855                    "Let {} be a new CCall with name \"{}\" and args {}.\n",
2856                    var, fn_name, args_var
2857                ));
2858            }
2859        }
2860        Expr::Index { collection, index } => {
2861            let coll_var = encode_expr_compact(collection, counter, output, interner, variants);
2862            let idx_var = encode_expr_compact(index, counter, output, interner, variants);
2863            output.push_str(&format!(
2864                "Let {} be a new CIndex with coll {} and idx {}.\n",
2865                var, coll_var, idx_var
2866            ));
2867        }
2868        Expr::Length { collection } => {
2869            let coll_var = encode_expr_compact(collection, counter, output, interner, variants);
2870            output.push_str(&format!("Let {} be a new CLen with target {}.\n", var, coll_var));
2871        }
2872        Expr::FieldAccess { object, field } => {
2873            let obj_var = encode_expr_compact(object, counter, output, interner, variants);
2874            let field_name = interner.resolve(*field);
2875            output.push_str(&format!(
2876                "Let {} be a new CMapGet with target {} and key (a new CText with value \"{}\").\n",
2877                var, obj_var, field_name
2878            ));
2879        }
2880        Expr::NewVariant { variant, fields, .. } => {
2881            let variant_name = interner.resolve(*variant);
2882            let names_var = format!("nvNames_{}", *counter);
2883            *counter += 1;
2884            output.push_str(&format!("Let {} be a new Seq of Text.\n", names_var));
2885            let vals_var = format!("nvVals_{}", *counter);
2886            *counter += 1;
2887            output.push_str(&format!("Let {} be a new Seq of CExpr.\n", vals_var));
2888            for (field_name, field_expr) in fields {
2889                let fname = interner.resolve(*field_name);
2890                output.push_str(&format!("Push \"{}\" to {}.\n", fname, names_var));
2891                let field_var = encode_expr_compact(field_expr, counter, output, interner, variants);
2892                output.push_str(&format!("Push {} to {}.\n", field_var, vals_var));
2893            }
2894            output.push_str(&format!(
2895                "Let {} be a new CNewVariant with tag \"{}\" and fnames {} and fvals {}.\n",
2896                var, variant_name, names_var, vals_var
2897            ));
2898        }
2899        Expr::New { type_name, init_fields, .. } => {
2900            let tn = interner.resolve(*type_name);
2901            if tn == "Seq" || tn == "List" {
2902                output.push_str(&format!("Let {} be a new CNewSeq.\n", var));
2903            } else if tn == "Set" {
2904                output.push_str(&format!("Let {} be a new CNewSet.\n", var));
2905            } else {
2906                let names_var = format!("nvNames_{}", *counter);
2907                *counter += 1;
2908                output.push_str(&format!("Let {} be a new Seq of Text.\n", names_var));
2909                let vals_var = format!("nvVals_{}", *counter);
2910                *counter += 1;
2911                output.push_str(&format!("Let {} be a new Seq of CExpr.\n", vals_var));
2912                for (field_name, field_expr) in init_fields {
2913                    let fname = interner.resolve(*field_name);
2914                    output.push_str(&format!("Push \"{}\" to {}.\n", fname, names_var));
2915                    let field_var = encode_expr_compact(field_expr, counter, output, interner, variants);
2916                    output.push_str(&format!("Push {} to {}.\n", field_var, vals_var));
2917                }
2918                output.push_str(&format!(
2919                    "Let {} be a new CNewVariant with tag \"{}\" and fnames {} and fvals {}.\n",
2920                    var, tn, names_var, vals_var
2921                ));
2922            }
2923        }
2924        Expr::InterpolatedString(parts) => {
2925            if parts.is_empty() {
2926                output.push_str(&format!("Let {} be (a new CText with value \"\").\n", var));
2927            } else {
2928                // Preserve as a first-class CInterpolatedString rather than desugaring to a
2929                // lossy `+` chain (see encode_expr_src for the rationale).
2930                let parts_var = format!("isparts_{}", *counter);
2931                *counter += 1;
2932                output.push_str(&format!("Let {} be a new Seq of CStringPart.\n", parts_var));
2933                for part in parts {
2934                    match part {
2935                        StringPart::Literal(sym) => {
2936                            let text = interner.resolve(*sym);
2937                            output.push_str(&format!(
2938                                "Push a new CLiteralPart with value \"{}\" to {}.\n", text, parts_var
2939                            ));
2940                        }
2941                        StringPart::Expr { value, .. } => {
2942                            let pv = encode_expr_compact(value, counter, output, interner, variants);
2943                            output.push_str(&format!(
2944                                "Push a new CExprPart with expr {} to {}.\n", pv, parts_var
2945                            ));
2946                        }
2947                    }
2948                }
2949                output.push_str(&format!(
2950                    "Let {} be a new CInterpolatedString with parts {}.\n", var, parts_var
2951                ));
2952            }
2953        }
2954        Expr::Range { start, end } => {
2955            let start_var = encode_expr_compact(start, counter, output, interner, variants);
2956            let end_var = encode_expr_compact(end, counter, output, interner, variants);
2957            output.push_str(&format!(
2958                "Let {} be a new CRange with start {} and end {}.\n",
2959                var, start_var, end_var
2960            ));
2961        }
2962        Expr::Copy { expr } => {
2963            let inner_var = encode_expr_compact(expr, counter, output, interner, variants);
2964            output.push_str(&format!("Let {} be a new CCopy with target {}.\n", var, inner_var));
2965        }
2966        Expr::Contains { collection, value } => {
2967            let coll_var = encode_expr_compact(collection, counter, output, interner, variants);
2968            let val_var = encode_expr_compact(value, counter, output, interner, variants);
2969            output.push_str(&format!(
2970                "Let {} be a new CContains with coll {} and elem {}.\n",
2971                var, coll_var, val_var
2972            ));
2973        }
2974        Expr::OptionSome { value } => {
2975            let inner_var = encode_expr_compact(value, counter, output, interner, variants);
2976            output.push_str(&format!(
2977                "Let {} be a new COptionSome with inner {}.\n", var, inner_var
2978            ));
2979        }
2980        Expr::Tuple(elems) => {
2981            let items_var = format!("tupItems_{}", *counter);
2982            *counter += 1;
2983            output.push_str(&format!("Let {} be a new Seq of CExpr.\n", items_var));
2984            for elem in elems {
2985                let elem_var = encode_expr_compact(elem, counter, output, interner, variants);
2986                output.push_str(&format!("Push {} to {}.\n", elem_var, items_var));
2987            }
2988            output.push_str(&format!(
2989                "Let {} be a new CTuple with items {}.\n", var, items_var
2990            ));
2991        }
2992        Expr::Closure { params, body, .. } => {
2993            let params_var = format!("clp_{}", *counter);
2994            *counter += 1;
2995            output.push_str(&format!("Let {} be a new Seq of Text.\n", params_var));
2996            let mut param_names = HashSet::new();
2997            for (sym, _) in params {
2998                let name = interner.resolve(*sym);
2999                param_names.insert(name.to_string());
3000                output.push_str(&format!("Push \"{}\" to {}.\n", name, params_var));
3001            }
3002            let body_var = format!("clb_{}", *counter);
3003            *counter += 1;
3004            output.push_str(&format!("Let {} be a new Seq of CStmt.\n", body_var));
3005            match body {
3006                ClosureBody::Expression(e) => {
3007                    let ret_expr = encode_expr_compact(e, counter, output, interner, variants);
3008                    let ret_var = format!("s_{}", *counter);
3009                    *counter += 1;
3010                    output.push_str(&format!("Let {} be a new CReturn with expr {}.\n", ret_var, ret_expr));
3011                    output.push_str(&format!("Push {} to {}.\n", ret_var, body_var));
3012                }
3013                ClosureBody::Block(stmts) => {
3014                    for s in stmts.iter() {
3015                        let sv = encode_stmt_compact(s, counter, output, interner, variants);
3016                        output.push_str(&format!("Push {} to {}.\n", sv, body_var));
3017                    }
3018                }
3019            }
3020            let bound: HashSet<String> = param_names;
3021            // Sort the captured free variables for a DETERMINISTIC encoding — a `HashSet`'s random
3022            // iteration order would make the wire bytes vary run-to-run (breaking content-addressing
3023            // AND the fast native-builder byte-identity). The capture set is order-independent
3024            // semantically, so sorting is a pure improvement.
3025            let mut free: Vec<String> = collect_free_vars_expr(expr, interner, &bound).into_iter().collect();
3026            free.sort();
3027            let cap_var = format!("clc_{}", *counter);
3028            *counter += 1;
3029            output.push_str(&format!("Let {} be a new Seq of Text.\n", cap_var));
3030            for fv in &free {
3031                output.push_str(&format!("Push \"{}\" to {}.\n", fv, cap_var));
3032            }
3033            output.push_str(&format!(
3034                "Let {} be a new CClosure with params {} and body {} and captured {}.\n",
3035                var, params_var, body_var, cap_var
3036            ));
3037        }
3038        Expr::CallExpr { callee, args } => {
3039            let callee_var = encode_expr_compact(callee, counter, output, interner, variants);
3040            let args_var = format!("cea_{}", *counter);
3041            *counter += 1;
3042            output.push_str(&format!("Let {} be a new Seq of CExpr.\n", args_var));
3043            for a in args {
3044                let av = encode_expr_compact(a, counter, output, interner, variants);
3045                output.push_str(&format!("Push {} to {}.\n", av, args_var));
3046            }
3047            output.push_str(&format!(
3048                "Let {} be a new CCallExpr with target {} and args {}.\n",
3049                var, callee_var, args_var
3050            ));
3051        }
3052        Expr::Slice { collection, start, end } => {
3053            let coll_var = encode_expr_compact(collection, counter, output, interner, variants);
3054            let start_var = encode_expr_compact(start, counter, output, interner, variants);
3055            let end_var = encode_expr_compact(end, counter, output, interner, variants);
3056            output.push_str(&format!(
3057                "Let {} be a new CSlice with coll {} and startIdx {} and endIdx {}.\n",
3058                var, coll_var, start_var, end_var
3059            ));
3060        }
3061        Expr::Union { left, right } => {
3062            let left_var = encode_expr_compact(left, counter, output, interner, variants);
3063            let right_var = encode_expr_compact(right, counter, output, interner, variants);
3064            output.push_str(&format!(
3065                "Let {} be a new CUnion with left {} and right {}.\n",
3066                var, left_var, right_var
3067            ));
3068        }
3069        Expr::Intersection { left, right } => {
3070            let left_var = encode_expr_compact(left, counter, output, interner, variants);
3071            let right_var = encode_expr_compact(right, counter, output, interner, variants);
3072            output.push_str(&format!(
3073                "Let {} be a new CIntersection with left {} and right {}.\n",
3074                var, left_var, right_var
3075            ));
3076        }
3077        Expr::Give { value } => {
3078            let inner_var = encode_expr_compact(value, counter, output, interner, variants);
3079            output.push_str(&format!("Let {} be {}.\n", var, inner_var));
3080        }
3081        Expr::Escape { code, .. } => {
3082            let code_str = interner.resolve(*code);
3083            output.push_str(&format!(
3084                "Let {} be a new CEscExpr with code \"{}\".\n",
3085                var, code_str.replace('\"', "\\\"")
3086            ));
3087        }
3088        _ => {
3089            // For unsupported expressions, use the non-compact version
3090            output.push_str(&format!("Let {} be (a new CText with value \"unsupported\").\n", var));
3091        }
3092    }
3093
3094    var
3095}
3096
3097fn encode_stmt_compact(stmt: &Stmt, counter: &mut usize, output: &mut String, interner: &Interner, variants: &HashMap<String, Vec<String>>) -> String {
3098    let var = format!("s_{}", *counter);
3099    *counter += 1;
3100
3101    match stmt {
3102        Stmt::Let { var: name, value, .. } => {
3103            let name_str = interner.resolve(*name);
3104            let expr_var = encode_expr_compact(value, counter, output, interner, variants);
3105            output.push_str(&format!(
3106                "Let {} be a new CLet with name \"{}\" and expr {}.\n",
3107                var, name_str, expr_var
3108            ));
3109        }
3110        Stmt::Set { target, value } => {
3111            let name_str = interner.resolve(*target);
3112            let expr_var = encode_expr_compact(value, counter, output, interner, variants);
3113            output.push_str(&format!(
3114                "Let {} be a new CSet with name \"{}\" and expr {}.\n",
3115                var, name_str, expr_var
3116            ));
3117        }
3118        Stmt::If { cond, then_block, else_block } => {
3119            let cond_var = encode_expr_compact(cond, counter, output, interner, variants);
3120            let then_stmts: Vec<&Stmt> = then_block.iter().collect();
3121            let then_var = encode_stmt_list_compact(&then_stmts, counter, output, interner, variants);
3122            let else_var = if let Some(els) = else_block {
3123                let else_stmts: Vec<&Stmt> = els.iter().collect();
3124                encode_stmt_list_compact(&else_stmts, counter, output, interner, variants)
3125            } else {
3126                let empty_var = format!("emptyBlock_{}", *counter);
3127                *counter += 1;
3128                output.push_str(&format!("Let {} be a new Seq of CStmt.\n", empty_var));
3129                empty_var
3130            };
3131            output.push_str(&format!(
3132                "Let {} be a new CIf with cond {} and thenBlock {} and elseBlock {}.\n",
3133                var, cond_var, then_var, else_var
3134            ));
3135        }
3136        Stmt::While { cond, body, .. } => {
3137            let cond_var = encode_expr_compact(cond, counter, output, interner, variants);
3138            let body_stmts: Vec<&Stmt> = body.iter().collect();
3139            let body_var = encode_stmt_list_compact(&body_stmts, counter, output, interner, variants);
3140            output.push_str(&format!(
3141                "Let {} be a new CWhile with cond {} and body {}.\n",
3142                var, cond_var, body_var
3143            ));
3144        }
3145        Stmt::Return { value } => {
3146            if let Some(val) = value {
3147                let expr_var = encode_expr_compact(val, counter, output, interner, variants);
3148                output.push_str(&format!("Let {} be a new CReturn with expr {}.\n", var, expr_var));
3149            } else {
3150                output.push_str(&format!("Let {} be a new CReturn with expr (a new CText with value \"nothing\").\n", var));
3151            }
3152        }
3153        Stmt::Show { object, .. } => {
3154            let expr_var = encode_expr_compact(object, counter, output, interner, variants);
3155            output.push_str(&format!("Let {} be a new CShow with expr {}.\n", var, expr_var));
3156        }
3157        Stmt::Repeat { pattern, iterable, body } => {
3158            let var_str = match pattern {
3159                Pattern::Identifier(sym) => interner.resolve(*sym).to_string(),
3160                Pattern::Tuple(syms) => {
3161                    if let Some(s) = syms.first() {
3162                        interner.resolve(*s).to_string()
3163                    } else {
3164                        "item".to_string()
3165                    }
3166                }
3167            };
3168            let coll_var = encode_expr_compact(iterable, counter, output, interner, variants);
3169            let body_stmts: Vec<&Stmt> = body.iter().collect();
3170            let body_var = encode_stmt_list_compact(&body_stmts, counter, output, interner, variants);
3171            output.push_str(&format!(
3172                "Let {} be a new CRepeat with var \"{}\" and coll {} and body {}.\n",
3173                var, var_str, coll_var, body_var
3174            ));
3175        }
3176        Stmt::Push { value, collection } => {
3177            let coll_name = extract_ident_name(collection, interner);
3178            let expr_var = encode_expr_compact(value, counter, output, interner, variants);
3179            output.push_str(&format!(
3180                "Let {} be a new CPush with expr {} and target \"{}\".\n",
3181                var, expr_var, coll_name
3182            ));
3183        }
3184        Stmt::SetIndex { collection, index, value } => {
3185            let target_str = extract_ident_name(collection, interner);
3186            let idx_var = encode_expr_compact(index, counter, output, interner, variants);
3187            let val_var = encode_expr_compact(value, counter, output, interner, variants);
3188            output.push_str(&format!(
3189                "Let {} be a new CSetIdx with target \"{}\" and idx {} and val {}.\n",
3190                var, target_str, idx_var, val_var
3191            ));
3192        }
3193        Stmt::SetField { object, field, value } => {
3194            let target_str = extract_ident_name(object, interner);
3195            let field_str = interner.resolve(*field);
3196            let val_var = encode_expr_compact(value, counter, output, interner, variants);
3197            output.push_str(&format!(
3198                "Let {} be a new CSetField with target \"{}\" and field \"{}\" and val {}.\n",
3199                var, target_str, field_str, val_var
3200            ));
3201        }
3202        Stmt::Break => {
3203            output.push_str(&format!("Let {} be a new CBreak.\n", var));
3204        }
3205        Stmt::Inspect { target, arms, .. } => {
3206            let target_var = encode_expr_compact(target, counter, output, interner, variants);
3207            let arms_var = format!("arms_{}", *counter);
3208            *counter += 1;
3209            output.push_str(&format!("Let {} be a new Seq of CMatchArm.\n", arms_var));
3210            for arm in arms {
3211                if let Some(variant_sym) = arm.variant {
3212                    let vname = interner.resolve(variant_sym);
3213                    let bindings_var = format!("bindings_{}", *counter);
3214                    *counter += 1;
3215                    output.push_str(&format!("Let {} be a new Seq of Text.\n", bindings_var));
3216                    for (_, binding_name) in &arm.bindings {
3217                        let bn = interner.resolve(*binding_name);
3218                        output.push_str(&format!("Push \"{}\" to {}.\n", bn, bindings_var));
3219                    }
3220                    let body_stmts: Vec<&Stmt> = arm.body.iter().collect();
3221                    let body_var = encode_stmt_list_compact(&body_stmts, counter, output, interner, variants);
3222                    let arm_var = format!("arm_{}", *counter);
3223                    *counter += 1;
3224                    output.push_str(&format!(
3225                        "Let {} be a new CWhen with variantName \"{}\" and bindings {} and body {}.\n",
3226                        arm_var, vname, bindings_var, body_var
3227                    ));
3228                    output.push_str(&format!("Push {} to {}.\n", arm_var, arms_var));
3229                } else {
3230                    let body_stmts: Vec<&Stmt> = arm.body.iter().collect();
3231                    let body_var = encode_stmt_list_compact(&body_stmts, counter, output, interner, variants);
3232                    let arm_var = format!("arm_{}", *counter);
3233                    *counter += 1;
3234                    output.push_str(&format!(
3235                        "Let {} be a new COtherwise with body {}.\n",
3236                        arm_var, body_var
3237                    ));
3238                    output.push_str(&format!("Push {} to {}.\n", arm_var, arms_var));
3239                }
3240            }
3241            output.push_str(&format!(
3242                "Let {} be a new CInspect with target {} and arms {}.\n",
3243                var, target_var, arms_var
3244            ));
3245        }
3246        _ => {
3247            // Delegate to non-compact encoder for unsupported statements
3248            return encode_stmt_src(stmt, counter, output, interner, variants);
3249        }
3250    }
3251
3252    var
3253}
3254
3255fn encode_stmt_list_compact(stmts: &[&Stmt], counter: &mut usize, output: &mut String, interner: &Interner, variants: &HashMap<String, Vec<String>>) -> String {
3256    let list_var = format!("stmts_{}", *counter);
3257    *counter += 1;
3258    output.push_str(&format!("Let {} be a new Seq of CStmt.\n", list_var));
3259    for s in stmts {
3260        let sv = encode_stmt_compact(s, counter, output, interner, variants);
3261        output.push_str(&format!("Push {} to {}.\n", sv, list_var));
3262    }
3263    list_var
3264}
3265
3266fn collect_free_vars_expr<'a>(expr: &'a Expr, interner: &Interner, bound: &HashSet<String>) -> HashSet<String> {
3267    let mut free = HashSet::new();
3268    match expr {
3269        Expr::Identifier(sym) => {
3270            let name = interner.resolve(*sym).to_string();
3271            if !bound.contains(&name) {
3272                free.insert(name);
3273            }
3274        }
3275        Expr::BinaryOp { left, right, .. } => {
3276            free.extend(collect_free_vars_expr(left, interner, bound));
3277            free.extend(collect_free_vars_expr(right, interner, bound));
3278        }
3279        Expr::Not { operand } => {
3280            free.extend(collect_free_vars_expr(operand, interner, bound));
3281        }
3282        Expr::Copy { expr: inner } => {
3283            free.extend(collect_free_vars_expr(inner, interner, bound));
3284        }
3285        Expr::CallExpr { callee, args } => {
3286            free.extend(collect_free_vars_expr(callee, interner, bound));
3287            for a in args {
3288                free.extend(collect_free_vars_expr(a, interner, bound));
3289            }
3290        }
3291        Expr::Index { collection, index } => {
3292            free.extend(collect_free_vars_expr(collection, interner, bound));
3293            free.extend(collect_free_vars_expr(index, interner, bound));
3294        }
3295        Expr::InterpolatedString(parts) => {
3296            for part in parts {
3297                if let StringPart::Expr { value, .. } = part {
3298                    free.extend(collect_free_vars_expr(value, interner, bound));
3299                }
3300            }
3301        }
3302        Expr::Closure { params, body, .. } => {
3303            let mut inner_bound = bound.clone();
3304            for (sym, _) in params {
3305                inner_bound.insert(interner.resolve(*sym).to_string());
3306            }
3307            match body {
3308                ClosureBody::Expression(e) => {
3309                    free.extend(collect_free_vars_expr(e, interner, &inner_bound));
3310                }
3311                ClosureBody::Block(stmts) => {
3312                    for s in stmts.iter() {
3313                        free.extend(collect_free_vars_stmt(s, interner, &inner_bound));
3314                    }
3315                }
3316            }
3317        }
3318        _ => {}
3319    }
3320    free
3321}
3322
3323fn collect_free_vars_stmt<'a>(stmt: &'a Stmt, interner: &Interner, bound: &HashSet<String>) -> HashSet<String> {
3324    let mut free = HashSet::new();
3325    match stmt {
3326        Stmt::Let { var, value, .. } => {
3327            free.extend(collect_free_vars_expr(value, interner, bound));
3328        }
3329        Stmt::Set { target, value, .. } => {
3330            let n = interner.resolve(*target).to_string();
3331            if !bound.contains(&n) {
3332                free.insert(n);
3333            }
3334            free.extend(collect_free_vars_expr(value, interner, bound));
3335        }
3336        Stmt::Show { object, .. } => {
3337            free.extend(collect_free_vars_expr(object, interner, bound));
3338        }
3339        Stmt::Return { value } => {
3340            if let Some(v) = value {
3341                free.extend(collect_free_vars_expr(v, interner, bound));
3342            }
3343        }
3344        _ => {}
3345    }
3346    free
3347}
3348
3349fn encode_expr_src(expr: &Expr, counter: &mut usize, output: &mut String, interner: &Interner, variants: &HashMap<String, Vec<String>>) -> String {
3350    let var = format!("e_{}", *counter);
3351    *counter += 1;
3352
3353    match expr {
3354        Expr::Literal(lit) => match lit {
3355            Literal::Number(n) => {
3356                output.push_str(&format!("Let {} be a new CInt with value {}.\n", var, n));
3357            }
3358            Literal::Boolean(b) => {
3359                output.push_str(&format!("Let {} be a new CBool with value {}.\n", var, b));
3360            }
3361            Literal::Text(s) => {
3362                let text = interner.resolve(*s);
3363                output.push_str(&format!("Let {} be a new CText with value \"{}\".\n", var, text));
3364            }
3365            Literal::Float(f) => {
3366                // Format faithfully: Rust's `{}` prints integer-valued floats without a
3367                // decimal point (9.0 -> "9"), which would re-parse as an Int and silently
3368                // turn float ops into integer ops. Guarantee a decimal point.
3369                let fs = format!("{}", f);
3370                let fs = if fs.contains('.') || fs.contains('e') || fs.contains('E') {
3371                    fs
3372                } else {
3373                    format!("{}.0", fs)
3374                };
3375                output.push_str(&format!("Let {} be a new CFloat with value {}.\n", var, fs));
3376            }
3377            Literal::Duration(nanos) => {
3378                let millis = nanos / 1_000_000;
3379                let amount_var = format!("e_{}", *counter);
3380                *counter += 1;
3381                output.push_str(&format!("Let {} be a new CInt with value {}.\n", amount_var, millis));
3382                output.push_str(&format!("Let {} be a new CDuration with amount {} and unit \"milliseconds\".\n", var, amount_var));
3383            }
3384            Literal::Nothing => {
3385                output.push_str(&format!("Let {} be a new CText with value \"nothing\".\n", var));
3386            }
3387            _ => {
3388                output.push_str(&format!("Let {} be a new CText with value \"unsupported\".\n", var));
3389            }
3390        },
3391        Expr::Identifier(sym) => {
3392            let name = interner.resolve(*sym);
3393            output.push_str(&format!("Let {} be a new CVar with name \"{}\".\n", var, name));
3394        }
3395        Expr::BinaryOp { op, left, right } => {
3396            let left_var = encode_expr_src(left, counter, output, interner, variants);
3397            let right_var = encode_expr_src(right, counter, output, interner, variants);
3398            let op_str = match op {
3399                BinaryOpKind::Add => "+",
3400                BinaryOpKind::Subtract => "-",
3401                BinaryOpKind::Multiply => "*",
3402                BinaryOpKind::Divide | BinaryOpKind::ExactDivide => "/",
3403                BinaryOpKind::FloorDivide => "//",
3404                BinaryOpKind::Modulo => "%",
3405                BinaryOpKind::Eq => "==",
3406                BinaryOpKind::NotEq => "!=",
3407                BinaryOpKind::Lt => "<",
3408                BinaryOpKind::Gt => ">",
3409                BinaryOpKind::LtEq => "<=",
3410                BinaryOpKind::GtEq => ">=",
3411                BinaryOpKind::And => "&&",
3412                BinaryOpKind::Or => "||",
3413                BinaryOpKind::Concat => "+",
3414                BinaryOpKind::SeqConcat => "followed by",
3415                BinaryOpKind::ApproxEq => "is approximately",
3416                BinaryOpKind::Pow => "**",
3417                BinaryOpKind::BitXor => "^",
3418                BinaryOpKind::BitAnd => "&",
3419                BinaryOpKind::BitOr => "|",
3420                BinaryOpKind::Shl => "<<",
3421                BinaryOpKind::Shr => ">>",
3422            };
3423            output.push_str(&format!(
3424                "Let {} be a new CBinOp with op \"{}\" and left {} and right {}.\n",
3425                var, op_str, left_var, right_var
3426            ));
3427        }
3428        Expr::Not { operand } => {
3429            let inner_var = encode_expr_src(operand, counter, output, interner, variants);
3430            output.push_str(&format!("Let {} be a new CNot with inner {}.\n", var, inner_var));
3431        }
3432        Expr::Call { function, args } => {
3433            let fn_name = interner.resolve(*function);
3434            if let Some(field_names) = variants.get(fn_name) {
3435                // Variant constructor call — encode as CNewVariant
3436                let names_var = format!("nvNames_{}", *counter);
3437                *counter += 1;
3438                output.push_str(&format!("Let {} be a new Seq of Text.\n", names_var));
3439                let vals_var = format!("nvVals_{}", *counter);
3440                *counter += 1;
3441                output.push_str(&format!("Let {} be a new Seq of CExpr.\n", vals_var));
3442                for (i, arg) in args.iter().enumerate() {
3443                    let fname = field_names.get(i).map(|s| s.as_str()).unwrap_or("value");
3444                    output.push_str(&format!("Push \"{}\" to {}.\n", fname, names_var));
3445                    let arg_var = encode_expr_src(arg, counter, output, interner, variants);
3446                    output.push_str(&format!("Push {} to {}.\n", arg_var, vals_var));
3447                }
3448                output.push_str(&format!(
3449                    "Let {} be a new CNewVariant with tag \"{}\" and fnames {} and fvals {}.\n",
3450                    var, fn_name, names_var, vals_var
3451                ));
3452            } else {
3453                let args_var = format!("callArgs_{}", *counter);
3454                *counter += 1;
3455                output.push_str(&format!("Let {} be a new Seq of CExpr.\n", args_var));
3456                for arg in args {
3457                    let arg_var = encode_expr_src(arg, counter, output, interner, variants);
3458                    output.push_str(&format!("Push {} to {}.\n", arg_var, args_var));
3459                }
3460                output.push_str(&format!(
3461                    "Let {} be a new CCall with name \"{}\" and args {}.\n",
3462                    var, fn_name, args_var
3463                ));
3464            }
3465        }
3466        Expr::Index { collection, index } => {
3467            let coll_var = encode_expr_src(collection, counter, output, interner, variants);
3468            let idx_var = encode_expr_src(index, counter, output, interner, variants);
3469            output.push_str(&format!(
3470                "Let {} be a new CIndex with coll {} and idx {}.\n",
3471                var, coll_var, idx_var
3472            ));
3473        }
3474        Expr::Length { collection } => {
3475            let coll_var = encode_expr_src(collection, counter, output, interner, variants);
3476            output.push_str(&format!("Let {} be a new CLen with target {}.\n", var, coll_var));
3477        }
3478        Expr::FieldAccess { object, field } => {
3479            let obj_var = encode_expr_src(object, counter, output, interner, variants);
3480            let field_name = interner.resolve(*field);
3481            let key_var = format!("e_{}", *counter);
3482            *counter += 1;
3483            output.push_str(&format!("Let {} be a new CText with value \"{}\".\n", key_var, field_name));
3484            output.push_str(&format!(
3485                "Let {} be a new CMapGet with target {} and key {}.\n",
3486                var, obj_var, key_var
3487            ));
3488        }
3489        Expr::NewVariant { variant, fields, .. } => {
3490            let variant_name = interner.resolve(*variant);
3491            let names_var = format!("nvNames_{}", *counter);
3492            *counter += 1;
3493            output.push_str(&format!("Let {} be a new Seq of Text.\n", names_var));
3494            let vals_var = format!("nvVals_{}", *counter);
3495            *counter += 1;
3496            output.push_str(&format!("Let {} be a new Seq of CExpr.\n", vals_var));
3497            for (field_name, field_expr) in fields {
3498                let fname = interner.resolve(*field_name);
3499                output.push_str(&format!("Push \"{}\" to {}.\n", fname, names_var));
3500                let field_var = encode_expr_src(field_expr, counter, output, interner, variants);
3501                output.push_str(&format!("Push {} to {}.\n", field_var, vals_var));
3502            }
3503            output.push_str(&format!(
3504                "Let {} be a new CNewVariant with tag \"{}\" and fnames {} and fvals {}.\n",
3505                var, variant_name, names_var, vals_var
3506            ));
3507        }
3508        Expr::New { type_name, init_fields, .. } => {
3509            let tn = interner.resolve(*type_name);
3510            if tn == "Seq" || tn == "List" {
3511                output.push_str(&format!("Let {} be a new CNewSeq.\n", var));
3512            } else if tn == "Set" {
3513                output.push_str(&format!("Let {} be a new CNewSet.\n", var));
3514            } else if tn == "Map" || tn.starts_with("Map ") {
3515                // Empty map creation — encode as CNew with "Map" type
3516                let names_var = format!("nvNames_{}", *counter);
3517                *counter += 1;
3518                output.push_str(&format!("Let {} be a new Seq of Text.\n", names_var));
3519                let vals_var = format!("nvVals_{}", *counter);
3520                *counter += 1;
3521                output.push_str(&format!("Let {} be a new Seq of CExpr.\n", vals_var));
3522                output.push_str(&format!(
3523                    "Let {} be a new CNew with typeName \"Map\" and fieldNames {} and fields {}.\n",
3524                    var, names_var, vals_var
3525                ));
3526            } else if init_fields.is_empty() {
3527                let names_var = format!("nvNames_{}", *counter);
3528                *counter += 1;
3529                output.push_str(&format!("Let {} be a new Seq of Text.\n", names_var));
3530                let vals_var = format!("nvVals_{}", *counter);
3531                *counter += 1;
3532                output.push_str(&format!("Let {} be a new Seq of CExpr.\n", vals_var));
3533                output.push_str(&format!(
3534                    "Let {} be a new CNewVariant with tag \"{}\" and fnames {} and fvals {}.\n",
3535                    var, tn, names_var, vals_var
3536                ));
3537            } else {
3538                let names_var = format!("nvNames_{}", *counter);
3539                *counter += 1;
3540                output.push_str(&format!("Let {} be a new Seq of Text.\n", names_var));
3541                let vals_var = format!("nvVals_{}", *counter);
3542                *counter += 1;
3543                output.push_str(&format!("Let {} be a new Seq of CExpr.\n", vals_var));
3544                for (field_name, field_expr) in init_fields {
3545                    let fname = interner.resolve(*field_name);
3546                    output.push_str(&format!("Push \"{}\" to {}.\n", fname, names_var));
3547                    let field_var = encode_expr_src(field_expr, counter, output, interner, variants);
3548                    output.push_str(&format!("Push {} to {}.\n", field_var, vals_var));
3549                }
3550                output.push_str(&format!(
3551                    "Let {} be a new CNewVariant with tag \"{}\" and fnames {} and fvals {}.\n",
3552                    var, tn, names_var, vals_var
3553                ));
3554            }
3555        }
3556        Expr::InterpolatedString(parts) => {
3557            if parts.is_empty() {
3558                output.push_str(&format!("Let {} be a new CText with value \"\".\n", var));
3559            } else {
3560                // Preserve the interpolation as a first-class CInterpolatedString — the IR,
3561                // PE (peExpr), decompiler, and self-interpreter (coreEval) all handle it and
3562                // coerce each part via valToText. Desugaring to a `+` chain was lossy: a
3563                // non-Text leading part produced `Int + Text`, which evalBinOp/applyBinOp do
3564                // not fold (→ VNothing → __unresolvable in the residual).
3565                let parts_var = format!("isparts_{}", *counter);
3566                *counter += 1;
3567                output.push_str(&format!("Let {} be a new Seq of CStringPart.\n", parts_var));
3568                for part in parts {
3569                    match part {
3570                        StringPart::Literal(sym) => {
3571                            let text = interner.resolve(*sym);
3572                            output.push_str(&format!(
3573                                "Push a new CLiteralPart with value \"{}\" to {}.\n", text, parts_var
3574                            ));
3575                        }
3576                        StringPart::Expr { value, .. } => {
3577                            let pv = encode_expr_src(value, counter, output, interner, variants);
3578                            output.push_str(&format!(
3579                                "Push a new CExprPart with expr {} to {}.\n", pv, parts_var
3580                            ));
3581                        }
3582                    }
3583                }
3584                output.push_str(&format!(
3585                    "Let {} be a new CInterpolatedString with parts {}.\n", var, parts_var
3586                ));
3587            }
3588        }
3589        Expr::Range { start, end } => {
3590            let start_var = encode_expr_src(start, counter, output, interner, variants);
3591            let end_var = encode_expr_src(end, counter, output, interner, variants);
3592            output.push_str(&format!(
3593                "Let {} be a new CRange with start {} and end {}.\n",
3594                var, start_var, end_var
3595            ));
3596        }
3597        Expr::Slice { collection, start, end } => {
3598            let coll_var = encode_expr_src(collection, counter, output, interner, variants);
3599            let start_var = encode_expr_src(start, counter, output, interner, variants);
3600            let end_var = encode_expr_src(end, counter, output, interner, variants);
3601            output.push_str(&format!(
3602                "Let {} be a new CSlice with coll {} and startIdx {} and endIdx {}.\n",
3603                var, coll_var, start_var, end_var
3604            ));
3605        }
3606        Expr::Copy { expr } => {
3607            let inner_var = encode_expr_src(expr, counter, output, interner, variants);
3608            output.push_str(&format!("Let {} be a new CCopy with target {}.\n", var, inner_var));
3609        }
3610        Expr::Contains { collection, value } => {
3611            let coll_var = encode_expr_src(collection, counter, output, interner, variants);
3612            let val_var = encode_expr_src(value, counter, output, interner, variants);
3613            output.push_str(&format!(
3614                "Let {} be a new CContains with coll {} and elem {}.\n",
3615                var, coll_var, val_var
3616            ));
3617        }
3618        Expr::Union { left, right } => {
3619            let left_var = encode_expr_src(left, counter, output, interner, variants);
3620            let right_var = encode_expr_src(right, counter, output, interner, variants);
3621            output.push_str(&format!(
3622                "Let {} be a new CUnion with left {} and right {}.\n",
3623                var, left_var, right_var
3624            ));
3625        }
3626        Expr::Intersection { left, right } => {
3627            let left_var = encode_expr_src(left, counter, output, interner, variants);
3628            let right_var = encode_expr_src(right, counter, output, interner, variants);
3629            output.push_str(&format!(
3630                "Let {} be a new CIntersection with left {} and right {}.\n",
3631                var, left_var, right_var
3632            ));
3633        }
3634        Expr::OptionSome { value } => {
3635            let inner_var = encode_expr_src(value, counter, output, interner, variants);
3636            output.push_str(&format!(
3637                "Let {} be a new COptionSome with inner {}.\n",
3638                var, inner_var
3639            ));
3640        }
3641        Expr::OptionNone => {
3642            output.push_str(&format!("Let {} be a new COptionNone.\n", var));
3643        }
3644        Expr::Tuple(elems) => {
3645            let items_var = format!("tupItems_{}", *counter);
3646            *counter += 1;
3647            output.push_str(&format!("Let {} be a new Seq of CExpr.\n", items_var));
3648            for elem in elems {
3649                let elem_var = encode_expr_src(elem, counter, output, interner, variants);
3650                output.push_str(&format!("Push {} to {}.\n", elem_var, items_var));
3651            }
3652            output.push_str(&format!(
3653                "Let {} be a new CTuple with items {}.\n",
3654                var, items_var
3655            ));
3656        }
3657        Expr::Closure { params, body, .. } => {
3658            let params_var = format!("clp_{}", *counter);
3659            *counter += 1;
3660            output.push_str(&format!("Let {} be a new Seq of Text.\n", params_var));
3661            let mut param_names = HashSet::new();
3662            for (sym, _) in params {
3663                let name = interner.resolve(*sym);
3664                param_names.insert(name.to_string());
3665                output.push_str(&format!("Push \"{}\" to {}.\n", name, params_var));
3666            }
3667            let body_var = format!("clb_{}", *counter);
3668            *counter += 1;
3669            output.push_str(&format!("Let {} be a new Seq of CStmt.\n", body_var));
3670            match body {
3671                ClosureBody::Expression(e) => {
3672                    let ret_expr = encode_expr_src(e, counter, output, interner, variants);
3673                    let ret_var = format!("s_{}", *counter);
3674                    *counter += 1;
3675                    output.push_str(&format!("Let {} be a new CReturn with expr {}.\n", ret_var, ret_expr));
3676                    output.push_str(&format!("Push {} to {}.\n", ret_var, body_var));
3677                }
3678                ClosureBody::Block(stmts) => {
3679                    for s in stmts.iter() {
3680                        let sv = encode_stmt_src(s, counter, output, interner, variants);
3681                        output.push_str(&format!("Push {} to {}.\n", sv, body_var));
3682                    }
3683                }
3684            }
3685            let bound: HashSet<String> = param_names;
3686            // Sort the captured free variables for a DETERMINISTIC encoding — a `HashSet`'s random
3687            // iteration order would make the wire bytes vary run-to-run (breaking content-addressing
3688            // AND the fast native-builder byte-identity). The capture set is order-independent
3689            // semantically, so sorting is a pure improvement.
3690            let mut free: Vec<String> = collect_free_vars_expr(expr, interner, &bound).into_iter().collect();
3691            free.sort();
3692            let cap_var = format!("clc_{}", *counter);
3693            *counter += 1;
3694            output.push_str(&format!("Let {} be a new Seq of Text.\n", cap_var));
3695            for fv in &free {
3696                output.push_str(&format!("Push \"{}\" to {}.\n", fv, cap_var));
3697            }
3698            output.push_str(&format!(
3699                "Let {} be a new CClosure with params {} and body {} and captured {}.\n",
3700                var, params_var, body_var, cap_var
3701            ));
3702        }
3703        Expr::CallExpr { callee, args } => {
3704            let callee_var = encode_expr_src(callee, counter, output, interner, variants);
3705            let args_var = format!("cea_{}", *counter);
3706            *counter += 1;
3707            output.push_str(&format!("Let {} be a new Seq of CExpr.\n", args_var));
3708            for a in args {
3709                let av = encode_expr_src(a, counter, output, interner, variants);
3710                output.push_str(&format!("Push {} to {}.\n", av, args_var));
3711            }
3712            output.push_str(&format!(
3713                "Let {} be a new CCallExpr with target {} and args {}.\n",
3714                var, callee_var, args_var
3715            ));
3716        }
3717        Expr::Give { value } => {
3718            let inner_var = encode_expr_src(value, counter, output, interner, variants);
3719            output.push_str(&format!("Let {} be {}.\n", var, inner_var));
3720        }
3721        Expr::Escape { code, .. } => {
3722            let code_str = interner.resolve(*code);
3723            output.push_str(&format!(
3724                "Let {} be a new CEscExpr with code \"{}\".\n",
3725                var, code_str.replace('\"', "\\\"")
3726            ));
3727        }
3728        Expr::List(elems) => {
3729            let items_var = format!("litems_{}", *counter);
3730            *counter += 1;
3731            output.push_str(&format!("Let {} be a new Seq of CExpr.\n", items_var));
3732            for elem in elems {
3733                let elem_var = encode_expr_src(elem, counter, output, interner, variants);
3734                output.push_str(&format!("Push {} to {}.\n", elem_var, items_var));
3735            }
3736            output.push_str(&format!(
3737                "Let {} be a new CList with items {}.\n",
3738                var, items_var
3739            ));
3740        }
3741        Expr::ManifestOf { zone } => {
3742            let zone_var = encode_expr_src(zone, counter, output, interner, variants);
3743            output.push_str(&format!(
3744                "Let {} be a new CManifestOf with zn {}.\n",
3745                var, zone_var
3746            ));
3747        }
3748        Expr::ChunkAt { index, zone } => {
3749            let idx_var = encode_expr_src(index, counter, output, interner, variants);
3750            let zone_var = encode_expr_src(zone, counter, output, interner, variants);
3751            output.push_str(&format!(
3752                "Let {} be a new CChunkAt with idx {} and zn {}.\n",
3753                var, idx_var, zone_var
3754            ));
3755        }
3756        Expr::WithCapacity { value, .. } => {
3757            // Capacity is an allocation hint with no runtime semantics (the interpreter ignores
3758            // it). Encode the inner value directly so the residual carries the value, not the
3759            // erased hint — no Core-IR node needed.
3760            return encode_expr_src(value, counter, output, interner, variants);
3761        }
3762        _ => {
3763            output.push_str(&format!("Let {} be a new CText with value \"unsupported\".\n", var));
3764        }
3765    }
3766
3767    var
3768}
3769
3770fn encode_stmt_src(stmt: &Stmt, counter: &mut usize, output: &mut String, interner: &Interner, variants: &HashMap<String, Vec<String>>) -> String {
3771    let var = format!("s_{}", *counter);
3772    *counter += 1;
3773
3774    match stmt {
3775        Stmt::Let { var: name, value, .. } => {
3776            let name_str = interner.resolve(*name);
3777            let expr_var = encode_expr_src(value, counter, output, interner, variants);
3778            output.push_str(&format!(
3779                "Let {} be a new CLet with name \"{}\" and expr {}.\n",
3780                var, name_str, expr_var
3781            ));
3782        }
3783        Stmt::Set { target, value } => {
3784            let name_str = interner.resolve(*target);
3785            let expr_var = encode_expr_src(value, counter, output, interner, variants);
3786            output.push_str(&format!(
3787                "Let {} be a new CSet with name \"{}\" and expr {}.\n",
3788                var, name_str, expr_var
3789            ));
3790        }
3791        Stmt::If { cond, then_block, else_block } => {
3792            let cond_var = encode_expr_src(cond, counter, output, interner, variants);
3793            let then_stmts: Vec<&Stmt> = then_block.iter().collect();
3794            let then_var = encode_stmt_list_src(&then_stmts, counter, output, interner, variants);
3795            let else_var = if let Some(els) = else_block {
3796                let else_stmts: Vec<&Stmt> = els.iter().collect();
3797                encode_stmt_list_src(&else_stmts, counter, output, interner, variants)
3798            } else {
3799                let empty_var = format!("emptyBlock_{}", *counter);
3800                *counter += 1;
3801                output.push_str(&format!("Let {} be a new Seq of CStmt.\n", empty_var));
3802                empty_var
3803            };
3804            output.push_str(&format!(
3805                "Let {} be a new CIf with cond {} and thenBlock {} and elseBlock {}.\n",
3806                var, cond_var, then_var, else_var
3807            ));
3808        }
3809        Stmt::While { cond, body, .. } => {
3810            let cond_var = encode_expr_src(cond, counter, output, interner, variants);
3811            let body_stmts: Vec<&Stmt> = body.iter().collect();
3812            let body_var = encode_stmt_list_src(&body_stmts, counter, output, interner, variants);
3813            output.push_str(&format!(
3814                "Let {} be a new CWhile with cond {} and body {}.\n",
3815                var, cond_var, body_var
3816            ));
3817        }
3818        Stmt::Splice { body } => {
3819            // A Splice encodes as an always-taken CIf: observationally
3820            // equivalent, because every desugar temporary is both defined and
3821            // consumed inside the body — no new pe_source node needed.
3822            let cond_var = format!("splice_cond_{}", *counter);
3823            *counter += 1;
3824            output.push_str(&format!("Let {} be a new CBool with value true.\n", cond_var));
3825            let body_stmts: Vec<&Stmt> = body.iter().collect();
3826            let body_var = encode_stmt_list_src(&body_stmts, counter, output, interner, variants);
3827            let empty_var = format!("emptyBlock_{}", *counter);
3828            *counter += 1;
3829            output.push_str(&format!("Let {} be a new Seq of CStmt.\n", empty_var));
3830            output.push_str(&format!(
3831                "Let {} be a new CIf with cond {} and thenBlock {} and elseBlock {}.\n",
3832                var, cond_var, body_var, empty_var
3833            ));
3834        }
3835        Stmt::Return { value } => {
3836            if let Some(expr) = value {
3837                let expr_var = encode_expr_src(expr, counter, output, interner, variants);
3838                output.push_str(&format!("Let {} be a new CReturn with expr {}.\n", var, expr_var));
3839            } else {
3840                let nothing_var = format!("e_{}", *counter);
3841                *counter += 1;
3842                output.push_str(&format!("Let {} be a new CInt with value 0.\n", nothing_var));
3843                output.push_str(&format!("Let {} be a new CReturn with expr {}.\n", var, nothing_var));
3844            }
3845        }
3846        Stmt::Show { object, .. } => {
3847            let expr_var = encode_expr_src(object, counter, output, interner, variants);
3848            output.push_str(&format!("Let {} be a new CShow with expr {}.\n", var, expr_var));
3849        }
3850        Stmt::Call { function, args } => {
3851            let fn_name = interner.resolve(*function);
3852            let args_var = format!("callSArgs_{}", *counter);
3853            *counter += 1;
3854            output.push_str(&format!("Let {} be a new Seq of CExpr.\n", args_var));
3855            for arg in args {
3856                let arg_var = encode_expr_src(arg, counter, output, interner, variants);
3857                output.push_str(&format!("Push {} to {}.\n", arg_var, args_var));
3858            }
3859            output.push_str(&format!(
3860                "Let {} be a new CCallS with name \"{}\" and args {}.\n",
3861                var, fn_name, args_var
3862            ));
3863        }
3864        Stmt::Push { value, collection } => {
3865            let val_var = encode_expr_src(value, counter, output, interner, variants);
3866            let coll_name = extract_ident_name(collection, interner);
3867            output.push_str(&format!(
3868                "Let {} be a new CPush with expr {} and target \"{}\".\n",
3869                var, val_var, coll_name
3870            ));
3871        }
3872        Stmt::SetIndex { collection, index, value } => {
3873            let coll_name = extract_ident_name(collection, interner);
3874            let idx_var = encode_expr_src(index, counter, output, interner, variants);
3875            let val_var = encode_expr_src(value, counter, output, interner, variants);
3876            output.push_str(&format!(
3877                "Let {} be a new CSetIdx with target \"{}\" and idx {} and val {}.\n",
3878                var, coll_name, idx_var, val_var
3879            ));
3880        }
3881        Stmt::SetField { object, field, value } => {
3882            let map_name = extract_ident_name(object, interner);
3883            let field_name = interner.resolve(*field);
3884            let key_var = format!("e_{}", *counter);
3885            *counter += 1;
3886            output.push_str(&format!("Let {} be a new CText with value \"{}\".\n", key_var, field_name));
3887            let val_var = encode_expr_src(value, counter, output, interner, variants);
3888            output.push_str(&format!(
3889                "Let {} be a new CMapSet with target \"{}\" and key {} and val {}.\n",
3890                var, map_name, key_var, val_var
3891            ));
3892        }
3893        Stmt::Pop { collection, .. } => {
3894            let coll_name = extract_ident_name(collection, interner);
3895            output.push_str(&format!(
3896                "Let {} be a new CPop with target \"{}\".\n",
3897                var, coll_name
3898            ));
3899        }
3900        Stmt::Add { value, collection } => {
3901            let val_var = encode_expr_src(value, counter, output, interner, variants);
3902            let coll_name = extract_ident_name(collection, interner);
3903            output.push_str(&format!(
3904                "Let {} be a new CAdd with elem {} and target \"{}\".\n",
3905                var, val_var, coll_name
3906            ));
3907        }
3908        Stmt::Remove { value, collection } => {
3909            let val_var = encode_expr_src(value, counter, output, interner, variants);
3910            let coll_name = extract_ident_name(collection, interner);
3911            output.push_str(&format!(
3912                "Let {} be a new CRemove with elem {} and target \"{}\".\n",
3913                var, val_var, coll_name
3914            ));
3915        }
3916        Stmt::Inspect { .. } => {
3917            return String::new(); // Handled by encode_stmts_src
3918        }
3919        Stmt::Repeat { .. } => {
3920            return String::new(); // Handled by encode_stmts_src
3921        }
3922        Stmt::Break => {
3923            output.push_str(&format!("Let {} be a new CBreak.\n", var));
3924        }
3925        Stmt::RuntimeAssert { condition, hard } => {
3926            let cond_var = encode_expr_src(condition, counter, output, interner, variants);
3927            let msg_var = format!("e_{}", *counter);
3928            *counter += 1;
3929            output.push_str(&format!("Let {} be a new CText with value \"assertion failed\".\n", msg_var));
3930            // `Require that` (hard) encodes as a distinct CStmt variant `CHardAssert`
3931            // (NOT `CRequire`, which is the `## Requires` dependency directive) so the
3932            // self-encoding round-trip preserves the enforced/dev distinction.
3933            let variant = if *hard { "CHardAssert" } else { "CRuntimeAssert" };
3934            output.push_str(&format!(
3935                "Let {} be a new {} with cond {} and msg {}.\n",
3936                var, variant, cond_var, msg_var
3937            ));
3938        }
3939        Stmt::Give { object, recipient } => {
3940            let expr_var = encode_expr_src(object, counter, output, interner, variants);
3941            let target_name = extract_ident_name(recipient, interner);
3942            output.push_str(&format!(
3943                "Let {} be a new CGive with expr {} and target \"{}\".\n",
3944                var, expr_var, target_name
3945            ));
3946        }
3947        Stmt::Escape { code, .. } => {
3948            let code_str = interner.resolve(*code);
3949            output.push_str(&format!(
3950                "Let {} be a new CEscStmt with code \"{}\".\n",
3951                var, code_str.replace('\"', "\\\"")
3952            ));
3953        }
3954        Stmt::Sleep { milliseconds } => {
3955            let dur_var = encode_expr_src(milliseconds, counter, output, interner, variants);
3956            output.push_str(&format!(
3957                "Let {} be a new CSleep with duration {}.\n",
3958                var, dur_var
3959            ));
3960        }
3961        Stmt::ReadFrom { var: read_var, source } => {
3962            let var_name = interner.resolve(*read_var);
3963            match source {
3964                ReadSource::Console => {
3965                    output.push_str(&format!(
3966                        "Let {} be a new CReadConsole with target \"{}\".\n",
3967                        var, var_name
3968                    ));
3969                }
3970                ReadSource::File(path_expr) => {
3971                    let path_var = encode_expr_src(path_expr, counter, output, interner, variants);
3972                    output.push_str(&format!(
3973                        "Let {} be a new CReadFile with path {} and target \"{}\".\n",
3974                        var, path_var, var_name
3975                    ));
3976                }
3977            }
3978        }
3979        Stmt::WriteFile { content, path } => {
3980            let path_var = encode_expr_src(path, counter, output, interner, variants);
3981            let content_var = encode_expr_src(content, counter, output, interner, variants);
3982            output.push_str(&format!(
3983                "Let {} be a new CWriteFile with path {} and content {}.\n",
3984                var, path_var, content_var
3985            ));
3986        }
3987        Stmt::Check { source_text, .. } => {
3988            let pred_var = format!("e_{}", *counter);
3989            *counter += 1;
3990            output.push_str(&format!("Let {} be a new CBool with value true.\n", pred_var));
3991            let msg_var = format!("e_{}", *counter);
3992            *counter += 1;
3993            output.push_str(&format!("Let {} be a new CText with value \"{}\".\n", msg_var, source_text.replace('\"', "\\\"")));
3994            output.push_str(&format!(
3995                "Let {} be a new CCheck with predicate {} and msg {}.\n",
3996                var, pred_var, msg_var
3997            ));
3998        }
3999        Stmt::Assert { .. } => {
4000            let prop_var = format!("e_{}", *counter);
4001            *counter += 1;
4002            output.push_str(&format!("Let {} be a new CBool with value true.\n", prop_var));
4003            output.push_str(&format!(
4004                "Let {} be a new CAssert with proposition {}.\n",
4005                var, prop_var
4006            ));
4007        }
4008        Stmt::Trust { justification, .. } => {
4009            let prop_var = format!("e_{}", *counter);
4010            *counter += 1;
4011            output.push_str(&format!("Let {} be a new CBool with value true.\n", prop_var));
4012            let just_str = interner.resolve(*justification);
4013            output.push_str(&format!(
4014                "Let {} be a new CTrust with proposition {} and justification \"{}\".\n",
4015                var, prop_var, just_str
4016            ));
4017        }
4018        Stmt::Require { crate_name, .. } => {
4019            let dep_name = interner.resolve(*crate_name);
4020            output.push_str(&format!(
4021                "Let {} be a new CRequire with dependency \"{}\".\n",
4022                var, dep_name
4023            ));
4024        }
4025        Stmt::MergeCrdt { source, target } => {
4026            let source_var = encode_expr_src(source, counter, output, interner, variants);
4027            // The target may be a struct (`a`) or a struct field (`local's active`); both
4028            // round-trip as surface syntax.
4029            let target_name = extract_ident_name(target, interner);
4030            output.push_str(&format!(
4031                "Let {} be a new CMerge with target \"{}\" and other {}.\n",
4032                var, target_name, source_var
4033            ));
4034        }
4035        Stmt::IncreaseCrdt { object, field, amount } => {
4036            let amount_var = encode_expr_src(amount, counter, output, interner, variants);
4037            // `Increase c's points` — the target is the FIELD, so carry both the object and
4038            // the field name (the residual must say `Increase c's points`, not `Increase c`).
4039            let target_name =
4040                format!("{}'s {}", extract_ident_name(object, interner), interner.resolve(*field));
4041            output.push_str(&format!(
4042                "Let {} be a new CIncrease with target \"{}\" and amount {}.\n",
4043                var, target_name, amount_var
4044            ));
4045        }
4046        Stmt::DecreaseCrdt { object, field, amount } => {
4047            let amount_var = encode_expr_src(amount, counter, output, interner, variants);
4048            let target_name =
4049                format!("{}'s {}", extract_ident_name(object, interner), interner.resolve(*field));
4050            output.push_str(&format!(
4051                "Let {} be a new CDecrease with target \"{}\" and amount {}.\n",
4052                var, target_name, amount_var
4053            ));
4054        }
4055        Stmt::AppendToSequence { sequence, value } => {
4056            let value_var = encode_expr_src(value, counter, output, interner, variants);
4057            let target_name = extract_ident_name(sequence, interner);
4058            output.push_str(&format!(
4059                "Let {} be a new CAppendToSeq with target \"{}\" and value {}.\n",
4060                var, target_name, value_var
4061            ));
4062        }
4063        Stmt::ResolveConflict { object, field, .. } => {
4064            let target_name =
4065                format!("{}'s {}", extract_ident_name(object, interner), interner.resolve(*field));
4066            output.push_str(&format!(
4067                "Let {} be a new CResolve with target \"{}\".\n",
4068                var, target_name
4069            ));
4070        }
4071        Stmt::Sync { var: sync_var, topic } => {
4072            let topic_var = encode_expr_src(topic, counter, output, interner, variants);
4073            let var_name = interner.resolve(*sync_var);
4074            output.push_str(&format!(
4075                "Let {} be a new CSync with target \"{}\" and channel {}.\n",
4076                var, var_name, topic_var
4077            ));
4078        }
4079        Stmt::Mount { var: mount_var, path } => {
4080            let path_var = encode_expr_src(path, counter, output, interner, variants);
4081            let var_name = interner.resolve(*mount_var);
4082            output.push_str(&format!(
4083                "Let {} be a new CMount with target \"{}\" and path {}.\n",
4084                var, var_name, path_var
4085            ));
4086        }
4087        Stmt::Concurrent { tasks } => {
4088            let branches_var = format!("e_{}", *counter);
4089            *counter += 1;
4090            output.push_str(&format!("Let {} be a new Seq of Seq of CStmt.\n", branches_var));
4091            // One inner branch per task — collapsing them loses the per-task
4092            // branch structure the executor / PE handlers iterate over.
4093            for stmt in tasks.iter() {
4094                let branch_var = format!("e_{}", *counter);
4095                *counter += 1;
4096                output.push_str(&format!("Let {} be a new Seq of CStmt.\n", branch_var));
4097                let sv = encode_stmt_src(stmt, counter, output, interner, variants);
4098                if !sv.is_empty() {
4099                    output.push_str(&format!("Push {} to {}.\n", sv, branch_var));
4100                }
4101                output.push_str(&format!("Push {} to {}.\n", branch_var, branches_var));
4102            }
4103            output.push_str(&format!(
4104                "Let {} be a new CConcurrent with branches {}.\n",
4105                var, branches_var
4106            ));
4107        }
4108        Stmt::Parallel { tasks } => {
4109            let branches_var = format!("e_{}", *counter);
4110            *counter += 1;
4111            output.push_str(&format!("Let {} be a new Seq of Seq of CStmt.\n", branches_var));
4112            // One inner branch per task (see Concurrent above).
4113            for stmt in tasks.iter() {
4114                let branch_var = format!("e_{}", *counter);
4115                *counter += 1;
4116                output.push_str(&format!("Let {} be a new Seq of CStmt.\n", branch_var));
4117                let sv = encode_stmt_src(stmt, counter, output, interner, variants);
4118                if !sv.is_empty() {
4119                    output.push_str(&format!("Push {} to {}.\n", sv, branch_var));
4120                }
4121                output.push_str(&format!("Push {} to {}.\n", branch_var, branches_var));
4122            }
4123            output.push_str(&format!(
4124                "Let {} be a new CParallel with branches {}.\n",
4125                var, branches_var
4126            ));
4127        }
4128        Stmt::LaunchTask { function, args } | Stmt::LaunchTaskWithHandle { function, args, .. } => {
4129            let func_name = interner.resolve(*function);
4130            let args_var = format!("e_{}", *counter);
4131            *counter += 1;
4132            output.push_str(&format!("Let {} be a new Seq of CExpr.\n", args_var));
4133            for arg in args {
4134                let av = encode_expr_src(arg, counter, output, interner, variants);
4135                output.push_str(&format!("Push {} to {}.\n", av, args_var));
4136            }
4137            let body_var = format!("e_{}", *counter);
4138            *counter += 1;
4139            output.push_str(&format!("Let {} be a new Seq of CStmt.\n", body_var));
4140            let call_var = format!("e_{}", *counter);
4141            *counter += 1;
4142            output.push_str(&format!(
4143                "Let {} be a new CCallS with name \"{}\" and args {}.\n",
4144                call_var, func_name, args_var
4145            ));
4146            output.push_str(&format!("Push {} to {}.\n", call_var, body_var));
4147            let handle_name = if let Stmt::LaunchTaskWithHandle { handle, .. } = stmt {
4148                interner.resolve(*handle).to_string()
4149            } else {
4150                "_task".to_string()
4151            };
4152            output.push_str(&format!(
4153                "Let {} be a new CLaunchTask with body {} and handle \"{}\".\n",
4154                var, body_var, handle_name
4155            ));
4156        }
4157        Stmt::StopTask { handle } => {
4158            let handle_var = encode_expr_src(handle, counter, output, interner, variants);
4159            output.push_str(&format!(
4160                "Let {} be a new CStopTask with handle {}.\n",
4161                var, handle_var
4162            ));
4163        }
4164        Stmt::CreatePipe { var: pipe_var, capacity, .. } => {
4165            let cap = capacity.unwrap_or(32);
4166            let cap_var = format!("e_{}", *counter);
4167            *counter += 1;
4168            output.push_str(&format!("Let {} be a new CInt with value {}.\n", cap_var, cap));
4169            let pipe_name = interner.resolve(*pipe_var);
4170            output.push_str(&format!(
4171                "Let {} be a new CCreatePipe with name \"{}\" and capacity {}.\n",
4172                var, pipe_name, cap_var
4173            ));
4174        }
4175        Stmt::SendPipe { value, pipe } => {
4176            let val_var = encode_expr_src(value, counter, output, interner, variants);
4177            let pipe_name = match pipe {
4178                Expr::Identifier(sym) => interner.resolve(*sym).to_string(),
4179                _ => "pipe".to_string(),
4180            };
4181            output.push_str(&format!(
4182                "Let {} be a new CSendPipe with chan \"{}\" and value {}.\n",
4183                var, pipe_name, val_var
4184            ));
4185        }
4186        Stmt::ReceivePipe { var: recv_var, pipe } => {
4187            let recv_name = interner.resolve(*recv_var);
4188            let pipe_name = match pipe {
4189                Expr::Identifier(sym) => interner.resolve(*sym).to_string(),
4190                _ => "pipe".to_string(),
4191            };
4192            output.push_str(&format!(
4193                "Let {} be a new CReceivePipe with chan \"{}\" and target \"{}\".\n",
4194                var, pipe_name, recv_name
4195            ));
4196        }
4197        Stmt::TrySendPipe { value, pipe, .. } => {
4198            let val_var = encode_expr_src(value, counter, output, interner, variants);
4199            let pipe_name = match pipe {
4200                Expr::Identifier(sym) => interner.resolve(*sym).to_string(),
4201                _ => "pipe".to_string(),
4202            };
4203            output.push_str(&format!(
4204                "Let {} be a new CTrySendPipe with chan \"{}\" and value {}.\n",
4205                var, pipe_name, val_var
4206            ));
4207        }
4208        Stmt::TryReceivePipe { var: recv_var, pipe } => {
4209            let recv_name = interner.resolve(*recv_var);
4210            let pipe_name = match pipe {
4211                Expr::Identifier(sym) => interner.resolve(*sym).to_string(),
4212                _ => "pipe".to_string(),
4213            };
4214            output.push_str(&format!(
4215                "Let {} be a new CTryReceivePipe with chan \"{}\" and target \"{}\".\n",
4216                var, pipe_name, recv_name
4217            ));
4218        }
4219        Stmt::Select { branches } => {
4220            let branches_var = format!("e_{}", *counter);
4221            *counter += 1;
4222            output.push_str(&format!("Let {} be a new Seq of CSelectBranch.\n", branches_var));
4223            for branch in branches {
4224                match branch {
4225                    SelectBranch::Receive { var: recv_var, pipe, body } => {
4226                        let recv_name = interner.resolve(*recv_var);
4227                        let pipe_name = match pipe {
4228                            Expr::Identifier(sym) => interner.resolve(*sym).to_string(),
4229                            _ => "pipe".to_string(),
4230                        };
4231                        let body_var = format!("e_{}", *counter);
4232                        *counter += 1;
4233                        output.push_str(&format!("Let {} be a new Seq of CStmt.\n", body_var));
4234                        for stmt in body.iter() {
4235                            let sv = encode_stmt_src(stmt, counter, output, interner, variants);
4236                            if !sv.is_empty() {
4237                                output.push_str(&format!("Push {} to {}.\n", sv, body_var));
4238                            }
4239                        }
4240                        let branch_var = format!("e_{}", *counter);
4241                        *counter += 1;
4242                        output.push_str(&format!(
4243                            "Let {} be a new CSelectRecv with chan \"{}\" and var \"{}\" and body {}.\n",
4244                            branch_var, pipe_name, recv_name, body_var
4245                        ));
4246                        output.push_str(&format!("Push {} to {}.\n", branch_var, branches_var));
4247                    }
4248                    SelectBranch::Timeout { milliseconds, body } => {
4249                        let dur_var = encode_expr_src(milliseconds, counter, output, interner, variants);
4250                        let body_var = format!("e_{}", *counter);
4251                        *counter += 1;
4252                        output.push_str(&format!("Let {} be a new Seq of CStmt.\n", body_var));
4253                        for stmt in body.iter() {
4254                            let sv = encode_stmt_src(stmt, counter, output, interner, variants);
4255                            if !sv.is_empty() {
4256                                output.push_str(&format!("Push {} to {}.\n", sv, body_var));
4257                            }
4258                        }
4259                        let branch_var = format!("e_{}", *counter);
4260                        *counter += 1;
4261                        output.push_str(&format!(
4262                            "Let {} be a new CSelectTimeout with duration {} and body {}.\n",
4263                            branch_var, dur_var, body_var
4264                        ));
4265                        output.push_str(&format!("Push {} to {}.\n", branch_var, branches_var));
4266                    }
4267                }
4268            }
4269            output.push_str(&format!(
4270                "Let {} be a new CSelect with branches {}.\n",
4271                var, branches_var
4272            ));
4273        }
4274        Stmt::Spawn { agent_type, name } => {
4275            let agent_name = interner.resolve(*agent_type);
4276            let target_name = interner.resolve(*name);
4277            output.push_str(&format!(
4278                "Let {} be a new CSpawn with agentType \"{}\" and target \"{}\".\n",
4279                var, agent_name, target_name
4280            ));
4281        }
4282        Stmt::SendMessage { message, destination, .. } => {
4283            let target_var = encode_expr_src(destination, counter, output, interner, variants);
4284            let msg_var = encode_expr_src(message, counter, output, interner, variants);
4285            output.push_str(&format!(
4286                "Let {} be a new CSendMessage with target {} and msg {}.\n",
4287                var, target_var, msg_var
4288            ));
4289        }
4290        // A batch stream send is encoded for the projection-1 self-interpreter exactly like a send,
4291        // so it is NEVER dropped from the residual (the catch-all would silently drop it). The PE
4292        // dialects pass it through unchanged — networking is opaque to specialization.
4293        Stmt::StreamMessage { values, destination } => {
4294            let target_var = encode_expr_src(destination, counter, output, interner, variants);
4295            let vals_var = encode_expr_src(values, counter, output, interner, variants);
4296            output.push_str(&format!(
4297                "Let {} be a new CStreamMessage with target {} and values {}.\n",
4298                var, target_var, vals_var
4299            ));
4300        }
4301        Stmt::AwaitMessage { into, .. } => {
4302            let await_name = interner.resolve(*into);
4303            output.push_str(&format!(
4304                "Let {} be a new CAwaitMessage with target \"{}\".\n",
4305                var, await_name
4306            ));
4307        }
4308        // The PNP `secure` binding is not modeled by the partial-evaluation self-interpreter (a
4309        // PNP program is a live-networking program, not a PE target); the address encoding is unchanged.
4310        Stmt::Listen { address, secure: _ } => {
4311            let addr_var = encode_expr_src(address, counter, output, interner, variants);
4312            output.push_str(&format!(
4313                "Let {} be a new CListen with addr {} and handler \"default\".\n",
4314                var, addr_var
4315            ));
4316        }
4317        Stmt::ConnectTo { address, secure: _ } => {
4318            let addr_var = encode_expr_src(address, counter, output, interner, variants);
4319            output.push_str(&format!(
4320                "Let {} be a new CConnectTo with addr {} and target \"conn\".\n",
4321                var, addr_var
4322            ));
4323        }
4324        Stmt::Zone { name, body, .. } => {
4325            let zone_name = interner.resolve(*name);
4326            let body_var = format!("e_{}", *counter);
4327            *counter += 1;
4328            output.push_str(&format!("Let {} be a new Seq of CStmt.\n", body_var));
4329            for stmt in body.iter() {
4330                let sv = encode_stmt_src(stmt, counter, output, interner, variants);
4331                if !sv.is_empty() {
4332                    output.push_str(&format!("Push {} to {}.\n", sv, body_var));
4333                }
4334            }
4335            output.push_str(&format!(
4336                "Let {} be a new CZone with name \"{}\" and kind \"heap\" and body {}.\n",
4337                var, zone_name, body_var
4338            ));
4339        }
4340        Stmt::LetPeerAgent { var: pa_var, address } => {
4341            let addr_var = encode_expr_src(address, counter, output, interner, variants);
4342            let pa_name = interner.resolve(*pa_var);
4343            output.push_str(&format!(
4344                "Let {} be a new CConnectTo with addr {} and target \"{}\".\n",
4345                var, addr_var, pa_name
4346            ));
4347        }
4348        // ── DECLARATIONS — no projection-1 BODY encoding (intentionally empty) ──
4349        // These are handled at the PROGRAM level, not as body statements: functions are encoded into
4350        // `encodedFuncMap` as `CFuncDef`; struct/type declarations live in the type catalog; theorems
4351        // and definitions are proof-layer only, with no runtime effect to specialize. They contribute
4352        // nothing to a statement body.
4353        //
4354        // ⚠️  THIS MATCH IS EXHAUSTIVE ON PURPOSE — there is NO `_` wildcard. ⚠️  Every one of the 52
4355        // EXECUTABLE statements above has a real encoding arm, and these 4 declarations are the only
4356        // ones that encode to nothing. Adding a new statement to `Stmt` will FAIL TO COMPILE here
4357        // until it is given an explicit arm — so a statement can NEVER be silently dropped from the
4358        // Futamura projection again (this is exactly how the CStreamMessage drop hid). Do not "fix" a
4359        // build break here by re-adding a `_ =>` wildcard; add the statement's real encoding instead.
4360        Stmt::FunctionDef { .. }
4361        | Stmt::StructDef { .. }
4362        | Stmt::Theorem(_)
4363        | Stmt::Definition(_)
4364        | Stmt::Axiom(_)
4365        | Stmt::Theory(_) => {
4366            return String::new();
4367        }
4368    }
4369
4370    var
4371}
4372
4373fn encode_stmts_src(stmt: &Stmt, counter: &mut usize, output: &mut String, interner: &Interner, variants: &HashMap<String, Vec<String>>) -> Vec<String> {
4374    match stmt {
4375        Stmt::Inspect { target, arms, .. } => {
4376            let mut otherwise_stmts: Vec<&Stmt> = Vec::new();
4377            let mut variant_arms: Vec<(&MatchArm, Vec<&Stmt>)> = Vec::new();
4378
4379            for arm in arms {
4380                if arm.variant.is_none() {
4381                    otherwise_stmts = arm.body.iter().collect();
4382                } else {
4383                    let body_refs: Vec<&Stmt> = arm.body.iter().collect();
4384                    variant_arms.push((arm, body_refs));
4385                }
4386            }
4387
4388            if variant_arms.is_empty() {
4389                let mut result = Vec::new();
4390                for s in &otherwise_stmts {
4391                    for v in encode_stmts_src(s, counter, output, interner, variants) {
4392                        result.push(v);
4393                    }
4394                }
4395                return result;
4396            }
4397
4398            // Flat CIf encoding: each arm becomes an independent CIf with empty else.
4399            // Since Inspect arms are mutually exclusive (exactly one tag matches),
4400            // flat CIf is semantically equivalent to nested CIf chains but avoids
4401            // deep nesting that the interpreter's inline CIf handler can't navigate.
4402            let has_otherwise = !otherwise_stmts.is_empty();
4403            let mut result = Vec::new();
4404
4405            // If there's an Otherwise block, track whether any arm matched
4406            let matched_var_name = if has_otherwise {
4407                // The flag name uses the DETERMINISTIC per-program Inspect-Otherwise index (shared with
4408                // the native builder), NOT the global intermediate counter — so the fast builder can
4409                // reproduce this desugaring byte-for-byte. The `counter` bump is kept so every OTHER
4410                // (source-only, non-wire) intermediate name downstream is unchanged.
4411                let name = format!("__inspectMatched_{}", next_inspect_otherwise_idx());
4412                *counter += 1;
4413                let false_expr = format!("e_{}", *counter);
4414                *counter += 1;
4415                output.push_str(&format!("Let {} be a new CBool with value false.\n", false_expr));
4416                let let_stmt = format!("s_{}", *counter);
4417                *counter += 1;
4418                output.push_str(&format!(
4419                    "Let {} be a new CLet with name \"{}\" and expr {}.\n",
4420                    let_stmt, name, false_expr
4421                ));
4422                result.push(let_stmt);
4423                Some(name)
4424            } else {
4425                None
4426            };
4427
4428            // Each variant arm becomes: CIf(tag == "Variant", [bindings + body], [])
4429            for (arm, body_stmts) in &variant_arms {
4430                let variant_name = interner.resolve(arm.variant.unwrap());
4431
4432                // Condition: tag == "VariantName"
4433                let tag_target = encode_expr_src(target, counter, output, interner, variants);
4434                let tag_key = format!("e_{}", *counter);
4435                *counter += 1;
4436                output.push_str(&format!("Let {} be a new CText with value \"__tag\".\n", tag_key));
4437                let tag_get = format!("e_{}", *counter);
4438                *counter += 1;
4439                output.push_str(&format!(
4440                    "Let {} be a new CMapGet with target {} and key {}.\n",
4441                    tag_get, tag_target, tag_key
4442                ));
4443                let variant_text = format!("e_{}", *counter);
4444                *counter += 1;
4445                output.push_str(&format!("Let {} be a new CText with value \"{}\".\n", variant_text, variant_name));
4446                let cond_var = format!("e_{}", *counter);
4447                *counter += 1;
4448                output.push_str(&format!(
4449                    "Let {} be a new CBinOp with op \"==\" and left {} and right {}.\n",
4450                    cond_var, tag_get, variant_text
4451                ));
4452
4453                // Then-block: [optionally set matched flag, bindings, body]
4454                let then_list = format!("stmtList_{}", *counter);
4455                *counter += 1;
4456                output.push_str(&format!("Let {} be a new Seq of CStmt.\n", then_list));
4457
4458                // Set matched flag if needed
4459                if let Some(ref mname) = matched_var_name {
4460                    let true_expr = format!("e_{}", *counter);
4461                    *counter += 1;
4462                    output.push_str(&format!("Let {} be a new CBool with value true.\n", true_expr));
4463                    let set_stmt = format!("s_{}", *counter);
4464                    *counter += 1;
4465                    output.push_str(&format!(
4466                        "Let {} be a new CSet with name \"{}\" and expr {}.\n",
4467                        set_stmt, mname, true_expr
4468                    ));
4469                    output.push_str(&format!("Push {} to {}.\n", set_stmt, then_list));
4470                }
4471
4472                // Bindings
4473                for (field_name, binding_name) in &arm.bindings {
4474                    let field_str = interner.resolve(*field_name);
4475                    let bind_str = interner.resolve(*binding_name);
4476                    let bind_target = encode_expr_src(target, counter, output, interner, variants);
4477                    let fkey = format!("e_{}", *counter);
4478                    *counter += 1;
4479                    output.push_str(&format!("Let {} be a new CText with value \"{}\".\n", fkey, field_str));
4480                    let fget = format!("e_{}", *counter);
4481                    *counter += 1;
4482                    output.push_str(&format!(
4483                        "Let {} be a new CMapGet with target {} and key {}.\n",
4484                        fget, bind_target, fkey
4485                    ));
4486                    let bind_let = format!("s_{}", *counter);
4487                    *counter += 1;
4488                    output.push_str(&format!(
4489                        "Let {} be a new CLet with name \"{}\" and expr {}.\n",
4490                        bind_let, bind_str, fget
4491                    ));
4492                    output.push_str(&format!("Push {} to {}.\n", bind_let, then_list));
4493                }
4494
4495                // Body statements (use encode_stmts_src for Inspect/Repeat)
4496                for body_stmt in body_stmts {
4497                    match body_stmt {
4498                        Stmt::Inspect { .. } | Stmt::Repeat { .. } => {
4499                            let vars = encode_stmts_src(body_stmt, counter, output, interner, variants);
4500                            for v in vars {
4501                                output.push_str(&format!("Push {} to {}.\n", v, then_list));
4502                            }
4503                        }
4504                        _ => {
4505                            let bvar = encode_stmt_src(body_stmt, counter, output, interner, variants);
4506                            if !bvar.is_empty() {
4507                                output.push_str(&format!("Push {} to {}.\n", bvar, then_list));
4508                            }
4509                        }
4510                    }
4511                }
4512
4513                // Empty else block
4514                let empty_else = format!("stmtList_{}", *counter);
4515                *counter += 1;
4516                output.push_str(&format!("Let {} be a new Seq of CStmt.\n", empty_else));
4517
4518                // CIf node
4519                let if_var = format!("s_{}", *counter);
4520                *counter += 1;
4521                output.push_str(&format!(
4522                    "Let {} be a new CIf with cond {} and thenBlock {} and elseBlock {}.\n",
4523                    if_var, cond_var, then_list, empty_else
4524                ));
4525
4526                result.push(if_var);
4527            }
4528
4529            // Otherwise: CIf(CNot(__inspectMatched), otherwise_body, [])
4530            if let Some(ref mname) = matched_var_name {
4531                let matched_ref = format!("e_{}", *counter);
4532                *counter += 1;
4533                output.push_str(&format!("Let {} be a new CVar with name \"{}\".\n", matched_ref, mname));
4534                let not_matched = format!("e_{}", *counter);
4535                *counter += 1;
4536                output.push_str(&format!("Let {} be a new CNot with inner {}.\n", not_matched, matched_ref));
4537
4538                let otherwise_block = encode_stmt_list_src(&otherwise_stmts, counter, output, interner, variants);
4539                let empty_else = format!("stmtList_{}", *counter);
4540                *counter += 1;
4541                output.push_str(&format!("Let {} be a new Seq of CStmt.\n", empty_else));
4542
4543                let otherwise_if = format!("s_{}", *counter);
4544                *counter += 1;
4545                output.push_str(&format!(
4546                    "Let {} be a new CIf with cond {} and thenBlock {} and elseBlock {}.\n",
4547                    otherwise_if, not_matched, otherwise_block, empty_else
4548                ));
4549                result.push(otherwise_if);
4550            }
4551
4552            result
4553        }
4554        Stmt::Repeat { pattern, iterable, body, .. } => {
4555            let loop_var_name = match pattern {
4556                Pattern::Identifier(sym) => interner.resolve(*sym).to_string(),
4557                Pattern::Tuple(syms) => {
4558                    if let Some(s) = syms.first() {
4559                        interner.resolve(*s).to_string()
4560                    } else {
4561                        "item".to_string()
4562                    }
4563                }
4564            };
4565
4566            // Range-based repeat: encode as CRepeatRange
4567            if let Expr::Range { start, end } = iterable {
4568                let start_var = encode_expr_src(start, counter, output, interner, variants);
4569                let end_var = encode_expr_src(end, counter, output, interner, variants);
4570                let body_stmts: Vec<&Stmt> = body.iter().collect();
4571                let body_var = encode_stmt_list_src(&body_stmts, counter, output, interner, variants);
4572                let rr = format!("s_{}", *counter);
4573                *counter += 1;
4574                output.push_str(&format!(
4575                    "Let {} be a new CRepeatRange with var \"{}\" and start {} and end {} and body {}.\n",
4576                    rr, loop_var_name, start_var, end_var, body_var
4577                ));
4578                return vec![rr];
4579            }
4580
4581            // Collection-based repeat: encode as CRepeat
4582            let coll_var = encode_expr_src(iterable, counter, output, interner, variants);
4583            let body_stmts: Vec<&Stmt> = body.iter().collect();
4584            let body_var = encode_stmt_list_src(&body_stmts, counter, output, interner, variants);
4585            let rep = format!("s_{}", *counter);
4586            *counter += 1;
4587            output.push_str(&format!(
4588                "Let {} be a new CRepeat with var \"{}\" and coll {} and body {}.\n",
4589                rep, loop_var_name, coll_var, body_var
4590            ));
4591            vec![rep]
4592        }
4593        // A CRDT op through a struct field (`Add x to p's guests`, `Append x to d's lines`)
4594        // — force the base struct dynamic FIRST, so the PE doesn't statically fold a struct
4595        // it then mutates, then emit the op itself.
4596        Stmt::Add { collection, .. }
4597        | Stmt::Remove { collection, .. }
4598        | Stmt::AppendToSequence { sequence: collection, .. } => {
4599            let mut result = Vec::new();
4600            if let Some(fd) = emit_force_dynamic(collection, counter, output, interner) {
4601                result.push(fd);
4602            }
4603            let v = encode_stmt_src(stmt, counter, output, interner, variants);
4604            if !v.is_empty() {
4605                result.push(v);
4606            }
4607            result
4608        }
4609        // A CRDT op that mutates the WHOLE struct (`Increase c's score`, `Merge b into a`) —
4610        // force the struct's root variable dynamic first, so the PE doesn't fold a counter /
4611        // register it then mutates.
4612        Stmt::IncreaseCrdt { object, .. }
4613        | Stmt::DecreaseCrdt { object, .. }
4614        | Stmt::ResolveConflict { object, .. }
4615        | Stmt::MergeCrdt { target: object, .. } => {
4616            let mut result = Vec::new();
4617            if let Some(fd) = emit_force_dynamic_struct(object, counter, output, interner) {
4618                result.push(fd);
4619            }
4620            let v = encode_stmt_src(stmt, counter, output, interner, variants);
4621            if !v.is_empty() {
4622                result.push(v);
4623            }
4624            result
4625        }
4626        _ => {
4627            let v = encode_stmt_src(stmt, counter, output, interner, variants);
4628            if v.is_empty() {
4629                vec![]
4630            } else {
4631                vec![v]
4632            }
4633        }
4634    }
4635}
4636
4637fn encode_stmt_list_src(stmts: &[&Stmt], counter: &mut usize, output: &mut String, interner: &Interner, variants: &HashMap<String, Vec<String>>) -> String {
4638    let list_var = format!("stmtList_{}", *counter);
4639    *counter += 1;
4640    output.push_str(&format!("Let {} be a new Seq of CStmt.\n", list_var));
4641
4642    for stmt in stmts {
4643        for stmt_var in encode_stmts_src(stmt, counter, output, interner, variants) {
4644            output.push_str(&format!("Push {} to {}.\n", stmt_var, list_var));
4645        }
4646    }
4647
4648    list_var
4649}
4650
4651fn extract_ident_name(expr: &Expr, interner: &Interner) -> String {
4652    match expr {
4653        Expr::Identifier(sym) => interner.resolve(*sym).to_string(),
4654        // A struct-field target (`p's guests`) round-trips as its LOGOS surface syntax, so
4655        // the PE residual decompiles back to a valid field access the tree-walker re-parses
4656        // — `Add x to p's guests`, `Append x to d's lines`. Without this the target collapsed
4657        // to "unknown" and the residual referenced an undefined variable.
4658        Expr::FieldAccess { object, field } => {
4659            format!("{}'s {}", extract_ident_name(object, interner), interner.resolve(*field))
4660        }
4661        _ => "unknown".to_string(),
4662    }
4663}
4664
4665/// The ROOT variable a CRDT target mutates: `p's guests` → `p`, a bare `s` → `s`. The PE
4666/// can't fold a struct it later mutates through a field, so when this differs from the
4667/// surface target (i.e. the target IS a struct field) the encoder emits a [`CForceDynamic`]
4668/// marker that forces `p` dynamic. Computed in Rust so the self-interpreter — which must
4669/// compile to statically-typed Rust for self-application — never parses the field path.
4670fn crdt_base_var(expr: &Expr, interner: &Interner) -> Option<String> {
4671    match expr {
4672        Expr::FieldAccess { object, .. } => Some(crdt_base_var_root(object, interner)),
4673        _ => None,
4674    }
4675}
4676
4677fn crdt_base_var_root(expr: &Expr, interner: &Interner) -> String {
4678    match expr {
4679        Expr::Identifier(sym) => interner.resolve(*sym).to_string(),
4680        Expr::FieldAccess { object, .. } => crdt_base_var_root(object, interner),
4681        _ => "unknown".to_string(),
4682    }
4683}
4684
4685/// Emit a `CForceDynamic` node binding `base` and return its node var. Prepended before a
4686/// CRDT op so the PE invalidates the (struct) binding before it can fold it.
4687fn emit_force_dynamic_named(base: &str, counter: &mut usize, output: &mut String) -> String {
4688    let v = format!("s_{}", *counter);
4689    *counter += 1;
4690    output.push_str(&format!(
4691        "Let {} be a new CForceDynamic with name \"{}\".\n",
4692        v, base
4693    ));
4694    v
4695}
4696
4697/// `CForceDynamic` for a COLLECTION op (`Add`/`Remove`/`Append`): only a struct-field target
4698/// needs it (a bare-variable collection is one the PE already tracks). `None` for bare vars.
4699fn emit_force_dynamic(
4700    collection: &Expr,
4701    counter: &mut usize,
4702    output: &mut String,
4703    interner: &Interner,
4704) -> Option<String> {
4705    let base = crdt_base_var(collection, interner)?;
4706    Some(emit_force_dynamic_named(&base, counter, output))
4707}
4708
4709/// `CForceDynamic` for a struct-MUTATING CRDT op (`Increase`/`Decrease`/`Resolve`/`Merge`):
4710/// the whole struct is mutated, so its root variable is ALWAYS forced dynamic.
4711fn emit_force_dynamic_struct(
4712    object: &Expr,
4713    counter: &mut usize,
4714    output: &mut String,
4715    interner: &Interner,
4716) -> Option<String> {
4717    match object {
4718        Expr::Identifier(_) | Expr::FieldAccess { .. } => {
4719            Some(emit_force_dynamic_named(&crdt_base_var_root(object, interner), counter, output))
4720        }
4721        _ => None,
4722    }
4723}
4724
4725
4726/// First Futamura Projection: PE(interpreter, program) = compiled_program
4727///
4728/// Specializes the interpreter with respect to a fixed program, producing
4729/// a compiled version with no interpretive overhead. For a self-interpreter
4730/// (where source and target language are the same), this produces the
4731/// program itself, with static optimizations applied.
4732///
4733/// The pipeline:
4734/// 1. Parse the program source
4735/// 2. Run the optimizer (fold, propagate, PE, DCE)
4736/// 3. Decompile the optimized AST back to source
4737/// 4. Verify no interpretive overhead remains
4738pub fn projection1_source(_core_types: &str, _interpreter: &str, program: &str) -> Result<String, String> {
4739    let full_source = if program.contains("## Main") || program.contains("## To ") {
4740        program.to_string()
4741    } else {
4742        format!("## Main\n{}", program)
4743    };
4744
4745    let mut interner = Interner::new();
4746    let mut lexer = Lexer::new(&full_source, &mut interner);
4747    let tokens = lexer.tokenize();
4748
4749    let type_registry = {
4750        let mut discovery = DiscoveryPass::new(&tokens, &mut interner);
4751        let result = discovery.run_full();
4752        result.types
4753    };
4754
4755    let mut world_state = WorldState::new();
4756    let expr_arena = Arena::new();
4757    let term_arena = Arena::new();
4758    let np_arena = Arena::new();
4759    let sym_arena = Arena::new();
4760    let role_arena = Arena::new();
4761    let pp_arena = Arena::new();
4762    let stmt_arena: Arena<Stmt> = Arena::new();
4763    let imperative_expr_arena: Arena<Expr> = Arena::new();
4764    let type_expr_arena: Arena<TypeExpr> = Arena::new();
4765
4766    let ast_ctx = AstContext::with_types(
4767        &expr_arena, &term_arena, &np_arena, &sym_arena,
4768        &role_arena, &pp_arena, &stmt_arena, &imperative_expr_arena,
4769        &type_expr_arena,
4770    );
4771
4772    let mut parser = crate::parser::Parser::new(
4773        tokens, &mut world_state, &mut interner, ast_ctx, type_registry,
4774    );
4775    let stmts = parser.parse_program().map_err(|e| format!("Parse error: {:?}", e))?;
4776
4777    // First Futamura Projection: PE(interpreter, program) = compiled_program.
4778    // Use the projection-safe optimizer: fold + propagate + PE + CTFE.
4779    // This preserves control-flow structure (If/While) while still folding
4780    // constants, propagating values, and specializing function calls.
4781    let optimized = crate::optimize::optimize_for_projection(
4782        stmts, &imperative_expr_arena, &stmt_arena, &mut interner,
4783        &crate::optimization::OptimizationConfig::from_env(),
4784    );
4785
4786    let mut output = String::new();
4787
4788    for stmt in &optimized {
4789        if matches!(stmt, Stmt::FunctionDef { .. }) {
4790            decompile_stmt(stmt, &interner, &mut output, 0);
4791            output.push('\n');
4792        }
4793    }
4794
4795    output.push_str("## Main\n");
4796    for stmt in &optimized {
4797        if !matches!(stmt, Stmt::FunctionDef { .. }) {
4798            decompile_stmt(stmt, &interner, &mut output, 0);
4799        }
4800    }
4801
4802    // Re-attach the program's type / struct definitions so a `new <Shared struct>`
4803    // in the residual can default-fill its CRDT fields (matches the genuine PE's
4804    // `projection1_source_real_fast`). Without this the residual reads an unset
4805    // field → "Field 'guests' not found".
4806    Ok(prepend_type_definitions(&full_source, output))
4807}
4808
4809fn decompile_stmt(stmt: &Stmt, interner: &Interner, out: &mut String, indent: usize) {
4810    let pad = "    ".repeat(indent);
4811    match stmt {
4812        Stmt::FunctionDef { name, params, body, return_type, .. } => {
4813            let fn_name = interner.resolve(*name);
4814            let param_strs: Vec<String> = params
4815                .iter()
4816                .map(|(name, ty)| {
4817                    let pname = interner.resolve(*name);
4818                    format!("{}: {}", pname, decompile_type_expr(ty, interner))
4819                })
4820                .collect();
4821            let ret_str = if let Some(rt) = return_type {
4822                format!(" -> {}", decompile_type_expr(rt, interner))
4823            } else {
4824                String::new()
4825            };
4826            out.push_str(&format!("{}## To {} ({}){}:\n", pad, fn_name, param_strs.join(", "), ret_str));
4827            for s in body.iter() {
4828                decompile_stmt(s, interner, out, indent + 1);
4829            }
4830        }
4831        Stmt::Let { var, value, mutable, .. } => {
4832            let name = interner.resolve(*var);
4833            let expr_str = decompile_expr(value, interner);
4834            if *mutable {
4835                out.push_str(&format!("{}Let mutable {} be {}.\n", pad, name, expr_str));
4836            } else {
4837                out.push_str(&format!("{}Let {} be {}.\n", pad, name, expr_str));
4838            }
4839        }
4840        Stmt::Set { target, value } => {
4841            let name = interner.resolve(*target);
4842            let expr_str = decompile_expr(value, interner);
4843            out.push_str(&format!("{}Set {} to {}.\n", pad, name, expr_str));
4844        }
4845        Stmt::Show { object, .. } => {
4846            let expr_str = decompile_expr(object, interner);
4847            out.push_str(&format!("{}Show {}.\n", pad, expr_str));
4848        }
4849        Stmt::Return { value } => {
4850            if let Some(expr) = value {
4851                let expr_str = decompile_expr(expr, interner);
4852                out.push_str(&format!("{}Return {}.\n", pad, expr_str));
4853            } else {
4854                out.push_str(&format!("{}Return.\n", pad));
4855            }
4856        }
4857        Stmt::If { cond, then_block, else_block } => {
4858            let cond_str = decompile_expr(cond, interner);
4859            out.push_str(&format!("{}If {}:\n", pad, cond_str));
4860            for s in then_block.iter() {
4861                decompile_stmt(s, interner, out, indent + 1);
4862            }
4863            if let Some(els) = else_block {
4864                out.push_str(&format!("{}Otherwise:\n", pad));
4865                for s in els.iter() {
4866                    decompile_stmt(s, interner, out, indent + 1);
4867                }
4868            }
4869        }
4870        Stmt::While { cond, body, .. } => {
4871            let cond_str = decompile_expr(cond, interner);
4872            out.push_str(&format!("{}While {}:\n", pad, cond_str));
4873            for s in body.iter() {
4874                decompile_stmt(s, interner, out, indent + 1);
4875            }
4876        }
4877        Stmt::Call { function, args } => {
4878            let fn_name = interner.resolve(*function);
4879            let arg_strs: Vec<String> = args.iter().map(|a| decompile_expr(a, interner)).collect();
4880            if arg_strs.is_empty() {
4881                out.push_str(&format!("{}{}().\n", pad, fn_name));
4882            } else {
4883                out.push_str(&format!("{}{}({}).\n", pad, fn_name, arg_strs.join(", ")));
4884            }
4885        }
4886        Stmt::Push { value, collection } => {
4887            let val_str = decompile_expr(value, interner);
4888            let coll_str = decompile_expr(collection, interner);
4889            out.push_str(&format!("{}Push {} to {}.\n", pad, val_str, coll_str));
4890        }
4891        Stmt::SetIndex { collection, index, value } => {
4892            let coll_str = decompile_expr(collection, interner);
4893            let idx_str = decompile_expr(index, interner);
4894            let val_str = decompile_expr(value, interner);
4895            out.push_str(&format!("{}Set item {} of {} to {}.\n", pad, idx_str, coll_str, val_str));
4896        }
4897        Stmt::SetField { object, field, value } => {
4898            let obj_str = decompile_expr(object, interner);
4899            let field_name = interner.resolve(*field);
4900            let val_str = decompile_expr(value, interner);
4901            out.push_str(&format!("{}Set {} of {} to {}.\n", pad, field_name, obj_str, val_str));
4902        }
4903        Stmt::Repeat { pattern, iterable, body, .. } => {
4904            let var_name = match pattern {
4905                Pattern::Identifier(sym) => interner.resolve(*sym).to_string(),
4906                Pattern::Tuple(syms) => {
4907                    syms.iter().map(|s| interner.resolve(*s).to_string()).collect::<Vec<_>>().join(", ")
4908                }
4909            };
4910            let iter_str = decompile_expr(iterable, interner);
4911            out.push_str(&format!("{}Repeat for {} in {}:\n", pad, var_name, iter_str));
4912            for s in body.iter() {
4913                decompile_stmt(s, interner, out, indent + 1);
4914            }
4915        }
4916        Stmt::Inspect { target, arms, .. } => {
4917            let target_str = decompile_expr(target, interner);
4918            out.push_str(&format!("{}Inspect {}:\n", pad, target_str));
4919            for arm in arms {
4920                if let Some(variant) = arm.variant {
4921                    let variant_name = interner.resolve(variant);
4922                    let bindings: Vec<String> = arm.bindings.iter()
4923                        .map(|(_, b)| interner.resolve(*b).to_string())
4924                        .collect();
4925                    if bindings.is_empty() {
4926                        out.push_str(&format!("{}    When {}:\n", pad, variant_name));
4927                    } else {
4928                        out.push_str(&format!("{}    When {}({}):\n", pad, variant_name, bindings.join(", ")));
4929                    }
4930                } else {
4931                    out.push_str(&format!("{}    Otherwise:\n", pad));
4932                }
4933                for s in arm.body.iter() {
4934                    decompile_stmt(s, interner, out, indent + 2);
4935                }
4936            }
4937        }
4938        Stmt::Pop { collection, into } => {
4939            let coll_str = decompile_expr(collection, interner);
4940            if let Some(target) = into {
4941                let target_name = interner.resolve(*target);
4942                out.push_str(&format!("{}Pop from {} into {}.\n", pad, coll_str, target_name));
4943            } else {
4944                out.push_str(&format!("{}Pop from {}.\n", pad, coll_str));
4945            }
4946        }
4947        Stmt::Break => {
4948            out.push_str(&format!("{}Break.\n", pad));
4949        }
4950        Stmt::RuntimeAssert { condition, hard } => {
4951            let cond_str = decompile_expr(condition, interner);
4952            // Preserve the enforced/dev distinction: `Require that` (hard) survives the
4953            // round-trip rather than silently becoming the dev-only `Assert that`.
4954            let kw = if *hard { "Require that" } else { "Assert that" };
4955            out.push_str(&format!("{}{} {}.\n", pad, kw, cond_str));
4956        }
4957        Stmt::Add { value, collection } => {
4958            let val_str = decompile_expr(value, interner);
4959            let coll_str = decompile_expr(collection, interner);
4960            out.push_str(&format!("{}Add {} to {}.\n", pad, val_str, coll_str));
4961        }
4962        Stmt::Remove { value, collection } => {
4963            let val_str = decompile_expr(value, interner);
4964            let coll_str = decompile_expr(collection, interner);
4965            out.push_str(&format!("{}Remove {} from {}.\n", pad, val_str, coll_str));
4966        }
4967        Stmt::Zone { name, body, .. } => {
4968            let zone_name = interner.resolve(*name);
4969            out.push_str(&format!("{}Inside a new zone called \"{}\":\n", pad, zone_name));
4970            for s in body.iter() {
4971                decompile_stmt(s, interner, out, indent + 1);
4972            }
4973        }
4974        Stmt::ReadFrom { var, .. } => {
4975            let var_name = interner.resolve(*var);
4976            out.push_str(&format!("{}Read {} from the console.\n", pad, var_name));
4977        }
4978        Stmt::WriteFile { content, path } => {
4979            let content_str = decompile_expr(content, interner);
4980            let path_str = decompile_expr(path, interner);
4981            out.push_str(&format!("{}Write {} to file {}.\n", pad, content_str, path_str));
4982        }
4983        Stmt::Sleep { milliseconds } => {
4984            let ms = decompile_expr(milliseconds, interner);
4985            out.push_str(&format!("{}Sleep {}.\n", pad, ms));
4986        }
4987        // CRDT mutations: a Shared struct the program mutates survives the
4988        // projection unfolded (the optimizer keeps the binding dynamic), so the
4989        // residual MUST re-emit these or it silently changes the program's value.
4990        Stmt::IncreaseCrdt { object, field, amount } => {
4991            let obj = decompile_expr(object, interner);
4992            let fld = interner.resolve(*field);
4993            let amt = decompile_expr(amount, interner);
4994            out.push_str(&format!("{}Increase {}'s {} by {}.\n", pad, obj, fld, amt));
4995        }
4996        Stmt::DecreaseCrdt { object, field, amount } => {
4997            let obj = decompile_expr(object, interner);
4998            let fld = interner.resolve(*field);
4999            let amt = decompile_expr(amount, interner);
5000            out.push_str(&format!("{}Decrease {}'s {} by {}.\n", pad, obj, fld, amt));
5001        }
5002        Stmt::MergeCrdt { source, target } => {
5003            let src = decompile_expr(source, interner);
5004            let tgt = decompile_expr(target, interner);
5005            out.push_str(&format!("{}Merge {} into {}.\n", pad, src, tgt));
5006        }
5007        Stmt::AppendToSequence { sequence, value } => {
5008            let seq = decompile_expr(sequence, interner);
5009            let val = decompile_expr(value, interner);
5010            out.push_str(&format!("{}Append {} to {}.\n", pad, val, seq));
5011        }
5012        Stmt::ResolveConflict { object, field, value } => {
5013            let obj = decompile_expr(object, interner);
5014            let fld = interner.resolve(*field);
5015            let val = decompile_expr(value, interner);
5016            out.push_str(&format!("{}Resolve {}'s {} to {}.\n", pad, obj, fld, val));
5017        }
5018        _ => {
5019            // Remaining system-level statements (networking, concurrency) are not
5020            // produced by the optimizer and don't appear in P1 residuals.
5021        }
5022    }
5023}
5024
5025fn decompile_expr(expr: &Expr, interner: &Interner) -> String {
5026    match expr {
5027        Expr::Literal(lit) => match lit {
5028            Literal::Number(n) => n.to_string(),
5029            Literal::Float(f) => format!("{}", f),
5030            Literal::Boolean(b) => if *b { "true".to_string() } else { "false".to_string() },
5031            Literal::Text(s) => format!("\"{}\"", interner.resolve(*s)),
5032            Literal::Nothing => "nothing".to_string(),
5033            Literal::Char(c) => format!("'{}'", c),
5034            Literal::Duration(ns) => format!("{}", ns),
5035            Literal::Date(days) => format!("{}", days),
5036            Literal::Moment(ns) => format!("{}", ns),
5037            Literal::Span { months, days } => format!("{} months {} days", months, days),
5038            Literal::Time(ns) => format!("{}", ns),
5039        },
5040        Expr::Identifier(sym) => interner.resolve(*sym).to_string(),
5041        Expr::BinaryOp { op, left, right } => {
5042            let l = if matches!(left, Expr::BinaryOp { .. }) {
5043                format!("({})", decompile_expr(left, interner))
5044            } else {
5045                decompile_expr(left, interner)
5046            };
5047            let r = if matches!(right, Expr::BinaryOp { .. }) {
5048                format!("({})", decompile_expr(right, interner))
5049            } else {
5050                decompile_expr(right, interner)
5051            };
5052            // Shift operations are introduced by bit-strength optimization.
5053            // Map back to equivalent multiply/divide for LOGOS source.
5054            if matches!(op, BinaryOpKind::Shl) {
5055                // n << k = n * 2^k
5056                if let Expr::Literal(Literal::Number(k)) = right {
5057                    let multiplier = 1i64 << k;
5058                    return format!("{} * {}", l, multiplier);
5059                }
5060            }
5061            if matches!(op, BinaryOpKind::Shr) {
5062                // n >> k = n / 2^k
5063                if let Expr::Literal(Literal::Number(k)) = right {
5064                    let divisor = 1i64 << k;
5065                    return format!("{} / {}", l, divisor);
5066                }
5067            }
5068            let op_str = match op {
5069                BinaryOpKind::Add => "+",
5070                BinaryOpKind::Subtract => "-",
5071                BinaryOpKind::Multiply => "*",
5072                BinaryOpKind::Divide | BinaryOpKind::ExactDivide => "/",
5073                BinaryOpKind::FloorDivide => "//",
5074                BinaryOpKind::Modulo => "%",
5075                BinaryOpKind::Eq => "equals",
5076                BinaryOpKind::NotEq => "is not",
5077                BinaryOpKind::Lt => "is less than",
5078                BinaryOpKind::Gt => "is greater than",
5079                BinaryOpKind::LtEq => "is at most",
5080                BinaryOpKind::GtEq => "is at least",
5081                BinaryOpKind::And => "and",
5082                BinaryOpKind::Or => "or",
5083                BinaryOpKind::Concat => "+",
5084                BinaryOpKind::SeqConcat => "followed by",
5085                BinaryOpKind::ApproxEq => "is approximately",
5086                BinaryOpKind::Pow => "**",
5087                BinaryOpKind::BitXor => "+",
5088                BinaryOpKind::BitAnd => "&",
5089                BinaryOpKind::BitOr => "|",
5090                BinaryOpKind::Shl => "*",
5091                BinaryOpKind::Shr => "/",
5092            };
5093            format!("{} {} {}", l, op_str, r)
5094        }
5095        Expr::Not { operand } => {
5096            // Parenthesize the operand so the negation's scope survives a
5097            // re-parse of the residual: `not (a and b)` must NOT become
5098            // `(not a) and b`.
5099            let inner = decompile_expr(operand, interner);
5100            format!("not ({})", inner)
5101        }
5102        Expr::Call { function, args } => {
5103            let fn_name = interner.resolve(*function);
5104            let arg_strs: Vec<String> = args.iter().map(|a| decompile_expr(a, interner)).collect();
5105            if arg_strs.is_empty() {
5106                format!("{}()", fn_name)
5107            } else {
5108                format!("{}({})", fn_name, arg_strs.join(", "))
5109            }
5110        }
5111        Expr::Index { collection, index } => {
5112            let coll = decompile_expr(collection, interner);
5113            let idx = decompile_expr(index, interner);
5114            format!("item {} of {}", idx, coll)
5115        }
5116        Expr::Length { collection } => {
5117            let coll = decompile_expr(collection, interner);
5118            format!("length of {}", coll)
5119        }
5120        Expr::FieldAccess { object, field } => {
5121            let obj = decompile_expr(object, interner);
5122            let field_name = interner.resolve(*field);
5123            // Possessive `obj's field` is the canonical, universally-parseable
5124            // field-read surface form (the parsers produce FieldAccess only from
5125            // it). `field of obj` does not round-trip (e.g. after `Show`/`Add`).
5126            format!("{}'s {}", obj, field_name)
5127        }
5128        Expr::New { type_name, .. } => {
5129            let tn = interner.resolve(*type_name);
5130            format!("a new {}", tn)
5131        }
5132        Expr::NewVariant { variant, fields, .. } => {
5133            let vn = interner.resolve(*variant);
5134            if fields.is_empty() {
5135                format!("a new {}", vn)
5136            } else {
5137                let parts: Vec<String> = fields.iter().map(|(name, val)| {
5138                    let n = interner.resolve(*name);
5139                    let v = decompile_expr(val, interner);
5140                    format!("{} {}", n, v)
5141                }).collect();
5142                format!("a new {} with {}", vn, parts.join(" and "))
5143            }
5144        }
5145        Expr::InterpolatedString(parts) => {
5146            let mut result = String::new();
5147            for part in parts {
5148                match part {
5149                    StringPart::Literal(sym) => {
5150                        result.push_str(&interner.resolve(*sym));
5151                    }
5152                    StringPart::Expr { value, debug, .. } => {
5153                        let expr_str = decompile_expr(value, interner);
5154                        if *debug {
5155                            result.push_str(&format!("{{{}=}}", expr_str));
5156                        } else {
5157                            result.push_str(&format!("{{{}}}", expr_str));
5158                        }
5159                    }
5160                }
5161            }
5162            format!("\"{}\"", result)
5163        }
5164        Expr::Slice { collection, start, end } => {
5165            let coll = decompile_expr(collection, interner);
5166            let s = decompile_expr(start, interner);
5167            let e = decompile_expr(end, interner);
5168            format!("{} {} through {}", coll, s, e)
5169        }
5170        Expr::Copy { expr } => {
5171            let inner = decompile_expr(expr, interner);
5172            format!("copy of {}", inner)
5173        }
5174        Expr::Give { value } => {
5175            let inner = decompile_expr(value, interner);
5176            format!("Give {}", inner)
5177        }
5178        Expr::Contains { collection, value } => {
5179            let coll = decompile_expr(collection, interner);
5180            let val = decompile_expr(value, interner);
5181            format!("{} contains {}", coll, val)
5182        }
5183        Expr::Union { left, right } => {
5184            let l = decompile_expr(left, interner);
5185            let r = decompile_expr(right, interner);
5186            format!("{} union {}", l, r)
5187        }
5188        Expr::Intersection { left, right } => {
5189            let l = decompile_expr(left, interner);
5190            let r = decompile_expr(right, interner);
5191            format!("{} intersection {}", l, r)
5192        }
5193        Expr::List(elems) => {
5194            let parts: Vec<String> = elems.iter().map(|e| decompile_expr(e, interner)).collect();
5195            format!("[{}]", parts.join(", "))
5196        }
5197        Expr::Tuple(elems) => {
5198            let parts: Vec<String> = elems.iter().map(|e| decompile_expr(e, interner)).collect();
5199            format!("({})", parts.join(", "))
5200        }
5201        Expr::Range { start, end } => {
5202            let s = decompile_expr(start, interner);
5203            let e = decompile_expr(end, interner);
5204            format!("{} to {}", s, e)
5205        }
5206        Expr::OptionSome { value } => {
5207            let inner = decompile_expr(value, interner);
5208            format!("some {}", inner)
5209        }
5210        Expr::OptionNone => "none".to_string(),
5211        Expr::WithCapacity { value, capacity } => {
5212            let val = decompile_expr(value, interner);
5213            let cap = decompile_expr(capacity, interner);
5214            format!("{} with capacity {}", val, cap)
5215        }
5216        Expr::Escape { language, code } => {
5217            let lang = interner.resolve(*language);
5218            let src = interner.resolve(*code);
5219            format!("Escape to {}:\n{}", lang, src)
5220        }
5221        Expr::ManifestOf { zone } => {
5222            let z = decompile_expr(zone, interner);
5223            format!("the manifest of {}", z)
5224        }
5225        Expr::ChunkAt { index, zone } => {
5226            let idx = decompile_expr(index, interner);
5227            let z = decompile_expr(zone, interner);
5228            format!("the chunk at {} in {}", idx, z)
5229        }
5230        Expr::Closure { params, body, return_type } => {
5231            let param_strs: Vec<String> = params.iter().map(|(name, ty)| {
5232                let n = interner.resolve(*name);
5233                let t = decompile_type_expr(ty, interner);
5234                format!("{}: {}", n, t)
5235            }).collect();
5236            let ret = if let Some(rt) = return_type {
5237                format!(" -> {}", decompile_type_expr(rt, interner))
5238            } else {
5239                String::new()
5240            };
5241            match body {
5242                ClosureBody::Expression(expr) => {
5243                    let e = decompile_expr(expr, interner);
5244                    format!("({}){} -> {}", param_strs.join(", "), ret, e)
5245                }
5246                ClosureBody::Block(_) => {
5247                    format!("({}){} -> [block]", param_strs.join(", "), ret)
5248                }
5249            }
5250        }
5251        Expr::CallExpr { callee, args } => {
5252            let c = decompile_expr(callee, interner);
5253            let arg_strs: Vec<String> = args.iter().map(|a| decompile_expr(a, interner)).collect();
5254            format!("{}({})", c, arg_strs.join(", "))
5255        }
5256    }
5257}
5258
5259fn decompile_type_expr(ty: &TypeExpr, interner: &Interner) -> String {
5260    match ty {
5261        TypeExpr::Primitive(sym) => interner.resolve(*sym).to_string(),
5262        TypeExpr::Named(sym) => interner.resolve(*sym).to_string(),
5263        TypeExpr::Generic { base, params } => {
5264            let base_str = interner.resolve(*base);
5265            let param_strs: Vec<String> = params.iter().map(|p| decompile_type_expr(p, interner)).collect();
5266            format!("{} of {}", base_str, param_strs.join(" and "))
5267        }
5268        TypeExpr::Function { inputs, output } => {
5269            let in_strs: Vec<String> = inputs.iter().map(|t| decompile_type_expr(t, interner)).collect();
5270            let out_str = decompile_type_expr(output, interner);
5271            format!("fn({}) -> {}", in_strs.join(", "), out_str)
5272        }
5273        TypeExpr::Refinement { base, .. } => {
5274            decompile_type_expr(base, interner)
5275        }
5276        TypeExpr::Persistent { inner } => {
5277            format!("Persistent {}", decompile_type_expr(inner, interner))
5278        }
5279        TypeExpr::Mutable { inner } => {
5280            format!("mutable {}", decompile_type_expr(inner, interner))
5281        }
5282    }
5283}
5284
5285/// Verify that a LogicAffeine program has no interpretive overhead.
5286///
5287/// Checks the AST for patterns that indicate unresolved interpreter dispatch:
5288/// - Inspect on CStmt/CExpr/CVal variants
5289/// - References to Core constructor types (CInt, CShow, etc.)
5290/// - Environment lookups on literal strings
5291pub fn verify_no_overhead_source(source: &str) -> Result<(), String> {
5292    let mut interner = Interner::new();
5293    let mut lexer = Lexer::new(source, &mut interner);
5294    let tokens = lexer.tokenize();
5295
5296    let type_registry = {
5297        let mut discovery = DiscoveryPass::new(&tokens, &mut interner);
5298        let result = discovery.run_full();
5299        result.types
5300    };
5301
5302    let mut world_state = WorldState::new();
5303    let expr_arena = Arena::new();
5304    let term_arena = Arena::new();
5305    let np_arena = Arena::new();
5306    let sym_arena = Arena::new();
5307    let role_arena = Arena::new();
5308    let pp_arena = Arena::new();
5309    let stmt_arena: Arena<Stmt> = Arena::new();
5310    let imperative_expr_arena: Arena<Expr> = Arena::new();
5311    let type_expr_arena: Arena<TypeExpr> = Arena::new();
5312
5313    let ast_ctx = AstContext::with_types(
5314        &expr_arena, &term_arena, &np_arena, &sym_arena,
5315        &role_arena, &pp_arena, &stmt_arena, &imperative_expr_arena,
5316        &type_expr_arena,
5317    );
5318
5319    let mut parser = crate::parser::Parser::new(
5320        tokens, &mut world_state, &mut interner, ast_ctx, type_registry,
5321    );
5322    let stmts = parser.parse_program().map_err(|e| format!("Parse error: {:?}", e))?;
5323
5324    verify_no_overhead_stmts(&stmts, &interner)
5325}
5326
5327const CORE_VARIANT_NAMES: &[&str] = &[
5328    "CInt", "CBool", "CText", "CVar", "CBinOp", "CNot",
5329    "CCall", "CIndex", "CLen", "CMapGet",
5330    "CLet", "CSet", "CIf", "CWhile", "CReturn", "CShow",
5331    "CCallS", "CPush", "CSetIdx", "CMapSet",
5332    "CFuncDef", "CProg",
5333    "CManifestOf", "CChunkAt",
5334    "VInt", "VBool", "VText", "VSeq", "VMap", "VError", "VNothing",
5335];
5336
5337/// Interpreter *carrier* type names — the umbrella data types the self-interpreter/PE thread
5338/// through their state. A Jones-optimal residual holds none of them: constructing
5339/// `a new Map of Text to CVal` (the environment), or binding a `CExpr`-typed variable, is
5340/// surviving interpreter state, not dissolved computation.
5341const CORE_TYPE_NAMES: &[&str] = &[
5342    "CVal", "CExpr", "CStmt", "CFunc", "CFuncDef", "CProgram", "CProg",
5343    "CoreOut", "CoreOutR", "PEState", "PEStateR", "PEMiniState", "PEMiniR",
5344];
5345
5346fn verify_no_overhead_stmts(stmts: &[Stmt], interner: &Interner) -> Result<(), String> {
5347    for stmt in stmts {
5348        check_stmt_overhead(stmt, interner)?;
5349    }
5350    Ok(())
5351}
5352
5353fn check_stmt_overhead(stmt: &Stmt, interner: &Interner) -> Result<(), String> {
5354    match stmt {
5355        Stmt::Inspect { arms, .. } => {
5356            for arm in arms {
5357                if let Some(variant) = arm.variant {
5358                    let variant_name = interner.resolve(variant);
5359                    if CORE_VARIANT_NAMES.contains(&variant_name) {
5360                        return Err(format!(
5361                            "Interpretive overhead: Inspect dispatches on Core variant '{}'",
5362                            variant_name
5363                        ));
5364                    }
5365                }
5366                for s in arm.body.iter() {
5367                    check_stmt_overhead(s, interner)?;
5368                }
5369            }
5370        }
5371        Stmt::If { cond, then_block, else_block } => {
5372            check_expr_overhead(cond, interner)?;
5373            for s in then_block.iter() {
5374                check_stmt_overhead(s, interner)?;
5375            }
5376            if let Some(els) = else_block {
5377                for s in els.iter() {
5378                    check_stmt_overhead(s, interner)?;
5379                }
5380            }
5381        }
5382        Stmt::While { cond, body, .. } => {
5383            check_expr_overhead(cond, interner)?;
5384            for s in body.iter() {
5385                check_stmt_overhead(s, interner)?;
5386            }
5387        }
5388        Stmt::FunctionDef { body, .. } => {
5389            for s in body.iter() {
5390                check_stmt_overhead(s, interner)?;
5391            }
5392        }
5393        Stmt::Repeat { body, .. } => {
5394            for s in body.iter() {
5395                check_stmt_overhead(s, interner)?;
5396            }
5397        }
5398        Stmt::Let { value, .. } | Stmt::Set { value, .. } | Stmt::Show { object: value, .. } => {
5399            check_expr_overhead(value, interner)?;
5400        }
5401        Stmt::Return { value } => {
5402            if let Some(v) = value {
5403                check_expr_overhead(v, interner)?;
5404            }
5405        }
5406        _ => {}
5407    }
5408    Ok(())
5409}
5410
5411fn check_expr_overhead(expr: &Expr, interner: &Interner) -> Result<(), String> {
5412    match expr {
5413        Expr::Index { collection, index } => {
5414            // Check for `item X of env` where X is a literal string (env lookup overhead)
5415            if let Expr::Identifier(coll_sym) = collection {
5416                let coll_name = interner.resolve(*coll_sym);
5417                if coll_name == "env" {
5418                    if let Expr::Literal(Literal::Text(_)) = index {
5419                        return Err(
5420                            "Interpretive overhead: environment lookup 'item ... of env' on literal key".to_string()
5421                        );
5422                    }
5423                }
5424            }
5425            check_expr_overhead(collection, interner)?;
5426            check_expr_overhead(index, interner)?;
5427        }
5428        Expr::New { type_name, .. } => {
5429            let tn = interner.resolve(*type_name);
5430            if CORE_VARIANT_NAMES.contains(&tn) {
5431                return Err(format!(
5432                    "Interpretive overhead: Core type constructor 'new {}'", tn
5433                ));
5434            }
5435        }
5436        Expr::NewVariant { variant, .. } => {
5437            let vn = interner.resolve(*variant);
5438            if CORE_VARIANT_NAMES.contains(&vn) {
5439                return Err(format!(
5440                    "Interpretive overhead: Core variant constructor '{}'", vn
5441                ));
5442            }
5443        }
5444        Expr::Call { function, args } => {
5445            let fn_name = interner.resolve(*function);
5446            if CORE_VARIANT_NAMES.contains(&fn_name) {
5447                return Err(format!(
5448                    "Interpretive overhead: Core variant call '{}'", fn_name
5449                ));
5450            }
5451            for a in args {
5452                check_expr_overhead(a, interner)?;
5453            }
5454        }
5455        Expr::BinaryOp { left, right, .. } => {
5456            check_expr_overhead(left, interner)?;
5457            check_expr_overhead(right, interner)?;
5458        }
5459        Expr::Not { operand } => {
5460            check_expr_overhead(operand, interner)?;
5461        }
5462        Expr::Length { collection } => {
5463            check_expr_overhead(collection, interner)?;
5464        }
5465        Expr::FieldAccess { object, .. } => {
5466            check_expr_overhead(object, interner)?;
5467        }
5468        _ => {}
5469    }
5470    Ok(())
5471}
5472
5473/// Names whose appearance as a call function in a residual betrays surviving
5474/// interpreter dispatch — the residual is still threading through the PE or the
5475/// self-interpreter instead of having been specialized away.
5476const DISPATCH_FN_NAMES: &[&str] = &[
5477    "peExpr", "peBlock", "coreEval", "coreExecBlock", "applyBinOp",
5478];
5479
5480/// The Jones-optimality oracle: count the units of interpreter dispatch remaining
5481/// in a residual program.
5482///
5483/// A residual that is genuinely Jones-optimal has dissolved the interpreter entirely
5484/// and returns zero. One unit is counted for each of:
5485/// - an `Inspect` arm dispatching on a Core IR variant (`CInt`, `CIf`, …);
5486/// - an environment/function-map lookup `item <literal> of env` / `... of funcs`;
5487/// - a Core IR constructor (`new CInt`, a `CInt(...)` call, a `VInt` variant, …);
5488/// - a call to a known dispatch function ([`DISPATCH_FN_NAMES`]).
5489///
5490/// Unlike [`verify_no_overhead_source`], this walks the whole tree and accumulates a
5491/// count rather than short-circuiting on the first violation.
5492pub fn count_dispatch(source: &str) -> usize {
5493    let mut interner = Interner::new();
5494    let mut lexer = Lexer::new(source, &mut interner);
5495    let tokens = lexer.tokenize();
5496
5497    let type_registry = {
5498        let mut discovery = DiscoveryPass::new(&tokens, &mut interner);
5499        let result = discovery.run_full();
5500        result.types
5501    };
5502
5503    let mut world_state = WorldState::new();
5504    let expr_arena = Arena::new();
5505    let term_arena = Arena::new();
5506    let np_arena = Arena::new();
5507    let sym_arena = Arena::new();
5508    let role_arena = Arena::new();
5509    let pp_arena = Arena::new();
5510    let stmt_arena: Arena<Stmt> = Arena::new();
5511    let imperative_expr_arena: Arena<Expr> = Arena::new();
5512    let type_expr_arena: Arena<TypeExpr> = Arena::new();
5513
5514    let ast_ctx = AstContext::with_types(
5515        &expr_arena, &term_arena, &np_arena, &sym_arena,
5516        &role_arena, &pp_arena, &stmt_arena, &imperative_expr_arena,
5517        &type_expr_arena,
5518    );
5519
5520    let mut parser = crate::parser::Parser::new(
5521        tokens, &mut world_state, &mut interner, ast_ctx, type_registry,
5522    );
5523    let stmts = match parser.parse_program() {
5524        Ok(s) => s,
5525        // A residual we cannot parse cannot be asserted Jones-optimal; surface that as
5526        // a non-zero count rather than a false clean bill of health.
5527        Err(_) => return usize::MAX,
5528    };
5529
5530    let mut count = 0usize;
5531    for stmt in &stmts {
5532        count_stmt_dispatch(stmt, &interner, &mut count);
5533    }
5534    count
5535}
5536
5537fn count_block_dispatch(block: &[Stmt], interner: &Interner, count: &mut usize) {
5538    for s in block {
5539        count_stmt_dispatch(s, interner, count);
5540    }
5541}
5542
5543/// Does a type expression name any interpreter carrier type ([`CORE_TYPE_NAMES`]) or Core
5544/// variant? Exhaustive over `TypeExpr` — a new type form stops the build until handled.
5545fn type_expr_mentions_core(t: &TypeExpr, interner: &Interner) -> bool {
5546    let is_core = |s| {
5547        let n = interner.resolve(s);
5548        CORE_TYPE_NAMES.contains(&n) || CORE_VARIANT_NAMES.contains(&n)
5549    };
5550    match t {
5551        TypeExpr::Primitive(s) | TypeExpr::Named(s) => is_core(*s),
5552        TypeExpr::Generic { base, params } => {
5553            is_core(*base) || params.iter().any(|p| type_expr_mentions_core(p, interner))
5554        }
5555        TypeExpr::Function { inputs, output } => {
5556            inputs.iter().any(|p| type_expr_mentions_core(p, interner))
5557                || type_expr_mentions_core(output, interner)
5558        }
5559        TypeExpr::Refinement { base, .. } => type_expr_mentions_core(base, interner),
5560        TypeExpr::Persistent { inner } | TypeExpr::Mutable { inner } => {
5561            type_expr_mentions_core(inner, interner)
5562        }
5563    }
5564}
5565
5566/// Scan raw escaped foreign code for whole-word Core-IR / dispatch names. An `Escape`
5567/// block embedding `CInt(..)` or `coreEval(..)` is interpreter overhead the AST walk
5568/// cannot see inside, so it is flagged lexically.
5569fn count_escape_code_dispatch(text: &str, count: &mut usize) {
5570    for word in text.split(|c: char| !c.is_alphanumeric() && c != '_') {
5571        if !word.is_empty()
5572            && (CORE_VARIANT_NAMES.contains(&word) || DISPATCH_FN_NAMES.contains(&word))
5573        {
5574            *count += 1;
5575        }
5576    }
5577}
5578
5579/// Exhaustive by construction — NO `_` arm. A new `Stmt` variant stops the build here
5580/// until it is classified, so the language cannot grow a statement that hides interpreter
5581/// dispatch from the Jones oracle.
5582fn count_stmt_dispatch(stmt: &Stmt, interner: &Interner, count: &mut usize) {
5583    match stmt {
5584        Stmt::Splice { body } => {
5585            for inner in body.iter() {
5586                count_stmt_dispatch(inner, interner, count);
5587            }
5588        }
5589        Stmt::Let { value, ty, .. } => {
5590            if let Some(t) = ty {
5591                if type_expr_mentions_core(t, interner) {
5592                    *count += 1;
5593                }
5594            }
5595            count_expr_dispatch(value, interner, count);
5596        }
5597        Stmt::Set { value, .. } => count_expr_dispatch(value, interner, count),
5598        Stmt::Call { function, args } => {
5599            let fn_name = interner.resolve(*function);
5600            if CORE_VARIANT_NAMES.contains(&fn_name) || DISPATCH_FN_NAMES.contains(&fn_name) {
5601                *count += 1;
5602            }
5603            for a in args {
5604                count_expr_dispatch(a, interner, count);
5605            }
5606        }
5607        Stmt::If { cond, then_block, else_block } => {
5608            count_expr_dispatch(cond, interner, count);
5609            count_block_dispatch(then_block, interner, count);
5610            if let Some(els) = else_block {
5611                count_block_dispatch(els, interner, count);
5612            }
5613        }
5614        Stmt::While { cond, body, decreasing } => {
5615            count_expr_dispatch(cond, interner, count);
5616            count_block_dispatch(body, interner, count);
5617            if let Some(d) = decreasing {
5618                count_expr_dispatch(d, interner, count);
5619            }
5620        }
5621        Stmt::Repeat { iterable, body, .. } => {
5622            count_expr_dispatch(iterable, interner, count);
5623            count_block_dispatch(body, interner, count);
5624        }
5625        Stmt::Return { value } => {
5626            if let Some(v) = value {
5627                count_expr_dispatch(v, interner, count);
5628            }
5629        }
5630        Stmt::RuntimeAssert { condition, .. } => count_expr_dispatch(condition, interner, count),
5631        Stmt::Give { object, recipient } | Stmt::Show { object, recipient } => {
5632            count_expr_dispatch(object, interner, count);
5633            count_expr_dispatch(recipient, interner, count);
5634        }
5635        Stmt::SetField { object, value, .. } => {
5636            count_expr_dispatch(object, interner, count);
5637            count_expr_dispatch(value, interner, count);
5638        }
5639        Stmt::FunctionDef { body, .. } => count_block_dispatch(body, interner, count),
5640        Stmt::Inspect { target, arms, .. } => {
5641            count_expr_dispatch(target, interner, count);
5642            for arm in arms {
5643                if let Some(variant) = arm.variant {
5644                    if CORE_VARIANT_NAMES.contains(&interner.resolve(variant)) {
5645                        *count += 1;
5646                    }
5647                }
5648                count_block_dispatch(arm.body, interner, count);
5649            }
5650        }
5651        Stmt::Push { value, collection }
5652        | Stmt::Add { value, collection }
5653        | Stmt::Remove { value, collection } => {
5654            count_expr_dispatch(value, interner, count);
5655            count_expr_dispatch(collection, interner, count);
5656        }
5657        Stmt::Pop { collection, .. } => count_expr_dispatch(collection, interner, count),
5658        Stmt::SetIndex { collection, index, value } => {
5659            count_expr_dispatch(collection, interner, count);
5660            count_expr_dispatch(index, interner, count);
5661            count_expr_dispatch(value, interner, count);
5662        }
5663        Stmt::Zone { body, .. } => count_block_dispatch(body, interner, count),
5664        Stmt::Concurrent { tasks } | Stmt::Parallel { tasks } => {
5665            count_block_dispatch(tasks, interner, count)
5666        }
5667        Stmt::ReadFrom { source, .. } => {
5668            if let ReadSource::File(path) = source {
5669                count_expr_dispatch(path, interner, count);
5670            }
5671        }
5672        Stmt::WriteFile { content, path } => {
5673            count_expr_dispatch(content, interner, count);
5674            count_expr_dispatch(path, interner, count);
5675        }
5676        Stmt::SendMessage { message, destination, .. } => {
5677            count_expr_dispatch(message, interner, count);
5678            count_expr_dispatch(destination, interner, count);
5679        }
5680        Stmt::AwaitMessage { source, .. } => count_expr_dispatch(source, interner, count),
5681        Stmt::StreamMessage { values, destination } => {
5682            count_expr_dispatch(values, interner, count);
5683            count_expr_dispatch(destination, interner, count);
5684        }
5685        Stmt::MergeCrdt { source, target } => {
5686            count_expr_dispatch(source, interner, count);
5687            count_expr_dispatch(target, interner, count);
5688        }
5689        Stmt::IncreaseCrdt { object, amount, .. } | Stmt::DecreaseCrdt { object, amount, .. } => {
5690            count_expr_dispatch(object, interner, count);
5691            count_expr_dispatch(amount, interner, count);
5692        }
5693        Stmt::AppendToSequence { sequence, value } => {
5694            count_expr_dispatch(sequence, interner, count);
5695            count_expr_dispatch(value, interner, count);
5696        }
5697        Stmt::ResolveConflict { object, value, .. } => {
5698            count_expr_dispatch(object, interner, count);
5699            count_expr_dispatch(value, interner, count);
5700        }
5701        Stmt::Listen { address, .. } | Stmt::ConnectTo { address, .. } | Stmt::LetPeerAgent { address, .. } => {
5702            count_expr_dispatch(address, interner, count)
5703        }
5704        Stmt::Sleep { milliseconds } => count_expr_dispatch(milliseconds, interner, count),
5705        Stmt::Sync { topic, .. } => count_expr_dispatch(topic, interner, count),
5706        Stmt::Mount { path, .. } => count_expr_dispatch(path, interner, count),
5707        Stmt::LaunchTask { function, args } | Stmt::LaunchTaskWithHandle { function, args, .. } => {
5708            let fn_name = interner.resolve(*function);
5709            if CORE_VARIANT_NAMES.contains(&fn_name) || DISPATCH_FN_NAMES.contains(&fn_name) {
5710                *count += 1;
5711            }
5712            for a in args {
5713                count_expr_dispatch(a, interner, count);
5714            }
5715        }
5716        Stmt::SendPipe { value, pipe } | Stmt::TrySendPipe { value, pipe, .. } => {
5717            count_expr_dispatch(value, interner, count);
5718            count_expr_dispatch(pipe, interner, count);
5719        }
5720        Stmt::ReceivePipe { pipe, .. } | Stmt::TryReceivePipe { pipe, .. } => {
5721            count_expr_dispatch(pipe, interner, count)
5722        }
5723        Stmt::StopTask { handle } => count_expr_dispatch(handle, interner, count),
5724        Stmt::Select { branches } => {
5725            for b in branches {
5726                match b {
5727                    SelectBranch::Receive { pipe, body, .. } => {
5728                        count_expr_dispatch(pipe, interner, count);
5729                        count_block_dispatch(body, interner, count);
5730                    }
5731                    SelectBranch::Timeout { milliseconds, body } => {
5732                        count_expr_dispatch(milliseconds, interner, count);
5733                        count_block_dispatch(body, interner, count);
5734                    }
5735                }
5736            }
5737        }
5738        Stmt::Escape { code, .. } => count_escape_code_dispatch(interner.resolve(*code), count),
5739        // Leaves and declarations: no executable `Expr` children reach the oracle.
5740        // `Assert`/`Trust` carry a `LogicExpr` (proof layer), not an executable `Expr`.
5741        Stmt::Break
5742        | Stmt::Assert { .. }
5743        | Stmt::Trust { .. }
5744        | Stmt::StructDef { .. }
5745        | Stmt::Spawn { .. }
5746        | Stmt::Check { .. }
5747        | Stmt::CreatePipe { .. }
5748        | Stmt::Theorem(_)
5749        | Stmt::Definition(_)
5750        | Stmt::Axiom(_)
5751        | Stmt::Theory(_)
5752        | Stmt::Require { .. } => {}
5753    }
5754}
5755
5756/// Exhaustive by construction — NO `_` arm. Every sub-expression is recursed, so a Core
5757/// constructor or dispatch call cannot hide inside a container (`List`/`Tuple`), an
5758/// operator, a constructor field, an interpolant, or a closure body.
5759fn count_expr_dispatch(expr: &Expr, interner: &Interner, count: &mut usize) {
5760    match expr {
5761        Expr::Literal(_) | Expr::Identifier(_) | Expr::OptionNone => {}
5762        Expr::BinaryOp { left, right, .. }
5763        | Expr::Union { left, right }
5764        | Expr::Intersection { left, right } => {
5765            count_expr_dispatch(left, interner, count);
5766            count_expr_dispatch(right, interner, count);
5767        }
5768        Expr::Not { operand } => count_expr_dispatch(operand, interner, count),
5769        Expr::Call { function, args } => {
5770            let fn_name = interner.resolve(*function);
5771            if CORE_VARIANT_NAMES.contains(&fn_name) || DISPATCH_FN_NAMES.contains(&fn_name) {
5772                *count += 1;
5773            }
5774            for a in args {
5775                count_expr_dispatch(a, interner, count);
5776            }
5777        }
5778        Expr::Index { collection, index } => {
5779            // Any `item ... of env|funcs` is surviving interpreter-state threading, whatever
5780            // the key: a Jones-optimal residual holds no environment/function map at all.
5781            if let Expr::Identifier(coll_sym) = collection {
5782                let coll_name = interner.resolve(*coll_sym);
5783                if coll_name == "env" || coll_name == "funcs" {
5784                    *count += 1;
5785                }
5786            }
5787            count_expr_dispatch(collection, interner, count);
5788            count_expr_dispatch(index, interner, count);
5789        }
5790        Expr::Slice { collection, start, end } => {
5791            count_expr_dispatch(collection, interner, count);
5792            count_expr_dispatch(start, interner, count);
5793            count_expr_dispatch(end, interner, count);
5794        }
5795        Expr::Copy { expr: inner } => count_expr_dispatch(inner, interner, count),
5796        Expr::Give { value } => count_expr_dispatch(value, interner, count),
5797        Expr::Length { collection } => count_expr_dispatch(collection, interner, count),
5798        Expr::Contains { collection, value } => {
5799            count_expr_dispatch(collection, interner, count);
5800            count_expr_dispatch(value, interner, count);
5801        }
5802        Expr::ManifestOf { zone } => count_expr_dispatch(zone, interner, count),
5803        Expr::ChunkAt { index, zone } => {
5804            count_expr_dispatch(index, interner, count);
5805            count_expr_dispatch(zone, interner, count);
5806        }
5807        Expr::List(items) | Expr::Tuple(items) => {
5808            for e in items {
5809                count_expr_dispatch(e, interner, count);
5810            }
5811        }
5812        Expr::Range { start, end } => {
5813            count_expr_dispatch(start, interner, count);
5814            count_expr_dispatch(end, interner, count);
5815        }
5816        Expr::FieldAccess { object, .. } => count_expr_dispatch(object, interner, count),
5817        Expr::New { type_name, type_args, init_fields } => {
5818            let tn = interner.resolve(*type_name);
5819            if CORE_VARIANT_NAMES.contains(&tn) || CORE_TYPE_NAMES.contains(&tn) {
5820                *count += 1;
5821            }
5822            // A collection over a Core carrier type — `a new Map of Text to CVal` — is the
5823            // interpreter's environment/function map surviving in the residual.
5824            for ta in type_args {
5825                if type_expr_mentions_core(ta, interner) {
5826                    *count += 1;
5827                }
5828            }
5829            for (_, v) in init_fields {
5830                count_expr_dispatch(v, interner, count);
5831            }
5832        }
5833        Expr::NewVariant { variant, fields, .. } => {
5834            if CORE_VARIANT_NAMES.contains(&interner.resolve(*variant)) {
5835                *count += 1;
5836            }
5837            for (_, v) in fields {
5838                count_expr_dispatch(v, interner, count);
5839            }
5840        }
5841        Expr::Escape { code, .. } => count_escape_code_dispatch(interner.resolve(*code), count),
5842        Expr::OptionSome { value } => count_expr_dispatch(value, interner, count),
5843        Expr::WithCapacity { value, capacity } => {
5844            count_expr_dispatch(value, interner, count);
5845            count_expr_dispatch(capacity, interner, count);
5846        }
5847        Expr::Closure { body, .. } => match body {
5848            ClosureBody::Expression(e) => count_expr_dispatch(e, interner, count),
5849            ClosureBody::Block(b) => count_block_dispatch(b, interner, count),
5850        },
5851        Expr::CallExpr { callee, args } => {
5852            count_expr_dispatch(callee, interner, count);
5853            for a in args {
5854                count_expr_dispatch(a, interner, count);
5855            }
5856        }
5857        Expr::InterpolatedString(parts) => {
5858            for p in parts {
5859                if let StringPart::Expr { value, .. } = p {
5860                    count_expr_dispatch(value, interner, count);
5861                }
5862            }
5863        }
5864    }
5865}
5866
5867/// Returns the source text of the partial evaluator written in LogicAffeine.
5868///
5869/// This PE operates on CProgram representations using explicit environments
5870/// and static dispatch. It is first-order (no closures) and uses only
5871/// literal string function names (no dynamic dispatch).
5872pub fn pe_source_text() -> &'static str {
5873    include_str!("optimize/pe_source.logos")
5874}
5875
5876pub fn decompile_source_text() -> &'static str {
5877    include_str!("optimize/decompile_source.logos")
5878}
5879
5880pub fn pe_bti_source_text() -> &'static str {
5881    include_str!("optimize/pe_bti_source.logos")
5882}
5883
5884pub fn pe_mini_source_text() -> &'static str {
5885    include_str!("optimize/pe_mini_source.logos")
5886}
5887
5888/// The canonical inductive type catalog the Futamura projections are built on
5889/// (`CStmt`/`CExpr`/`CVal`/…). Every statement the tree-walker executes has a
5890/// `C…` constructor here; the partial-evaluator dialects dispatch on it. Exposed
5891/// so cross-tier lock tests can prove this catalog and every tier stay in sync —
5892/// a statement added to the language must appear here or it is dropped from the
5893/// projections (see `tier_parity_lock.rs`).
5894pub fn core_types_for_pe_source() -> &'static str {
5895    CORE_TYPES_FOR_PE
5896}
5897
5898const CORE_TYPES_FOR_PE: &str = r#"
5899## A CExpr is one of:
5900    A CInt with value Int.
5901    A CFloat with value Real.
5902    A CBool with value Bool.
5903    A CText with value Text.
5904    A CVar with name Text.
5905    A CBinOp with op Text and left CExpr and right CExpr.
5906    A CNot with inner CExpr.
5907    A CCall with name Text and args Seq of CExpr.
5908    A CIndex with coll CExpr and idx CExpr.
5909    A CLen with target CExpr.
5910    A CMapGet with target CExpr and key CExpr.
5911    A CNewSeq.
5912    A CNewVariant with tag Text and fnames Seq of Text and fvals Seq of CExpr.
5913    A CList with items Seq of CExpr.
5914    A CRange with start CExpr and end CExpr.
5915    A CSlice with coll CExpr and startIdx CExpr and endIdx CExpr.
5916    A CCopy with target CExpr.
5917    A CNewSet.
5918    A CContains with coll CExpr and elem CExpr.
5919    A CUnion with left CExpr and right CExpr.
5920    A CIntersection with left CExpr and right CExpr.
5921    A COptionSome with inner CExpr.
5922    A COptionNone.
5923    A CTuple with items Seq of CExpr.
5924    A CNew with typeName Text and fieldNames Seq of Text and fields Seq of CExpr.
5925    A CFieldAccess with target CExpr and field Text.
5926    A CClosure with params Seq of Text and body Seq of CStmt and captured Seq of Text.
5927    A CCallExpr with target CExpr and args Seq of CExpr.
5928    A CInterpolatedString with parts Seq of CStringPart.
5929    A CDuration with amount CExpr and unit Text.
5930    A CTimeNow.
5931    A CDateToday.
5932    A CEscExpr with code Text.
5933    A CManifestOf with zn CExpr.
5934    A CChunkAt with idx CExpr and zn CExpr.
5935
5936## A CStringPart is one of:
5937    A CLiteralPart with value Text.
5938    A CExprPart with expr CExpr.
5939
5940## A CStmt is one of:
5941    A CLet with name Text and expr CExpr.
5942    A CSet with name Text and expr CExpr.
5943    A CIf with cond CExpr and thenBlock Seq of CStmt and elseBlock Seq of CStmt.
5944    A CWhile with cond CExpr and body Seq of CStmt.
5945    A CReturn with expr CExpr.
5946    A CShow with expr CExpr.
5947    A CCallS with name Text and args Seq of CExpr.
5948    A CPush with expr CExpr and target Text.
5949    A CSetIdx with target Text and idx CExpr and val CExpr.
5950    A CMapSet with target Text and key CExpr and val CExpr.
5951    A CPop with target Text.
5952    A CRepeat with var Text and coll CExpr and body Seq of CStmt.
5953    A CRepeatRange with var Text and start CExpr and end CExpr and body Seq of CStmt.
5954    A CBreak.
5955    A CAdd with elem CExpr and target Text.
5956    A CRemove with elem CExpr and target Text.
5957    A CForceDynamic with name Text.
5958    A CSetField with target Text and field Text and val CExpr.
5959    A CStructDef with name Text and fieldNames Seq of Text.
5960    A CInspect with target CExpr and arms Seq of CMatchArm.
5961    A CEnumDef with name Text and variants Seq of Text.
5962    A CRuntimeAssert with cond CExpr and msg CExpr.
5963    A CHardAssert with cond CExpr and msg CExpr.
5964    A CGive with expr CExpr and target Text.
5965    A CEscStmt with code Text.
5966    A CSleep with duration CExpr.
5967    A CReadConsole with target Text.
5968    A CReadFile with path CExpr and target Text.
5969    A CWriteFile with path CExpr and content CExpr.
5970    A CCheck with predicate CExpr and msg CExpr.
5971    A CAssert with proposition CExpr.
5972    A CTrust with proposition CExpr and justification Text.
5973    A CRequire with dependency Text.
5974    A CMerge with target Text and other CExpr.
5975    A CIncrease with target Text and amount CExpr.
5976    A CDecrease with target Text and amount CExpr.
5977    A CAppendToSeq with target Text and value CExpr.
5978    A CResolve with target Text.
5979    A CSync with target Text and channel CExpr.
5980    A CMount with target Text and path CExpr.
5981    A CConcurrent with branches Seq of Seq of CStmt.
5982    A CParallel with branches Seq of Seq of CStmt.
5983    A CLaunchTask with body Seq of CStmt and handle Text.
5984    A CStopTask with handle CExpr.
5985    A CSelect with branches Seq of CSelectBranch.
5986    A CCreatePipe with name Text and capacity CExpr.
5987    A CSendPipe with chan Text and value CExpr.
5988    A CReceivePipe with chan Text and target Text.
5989    A CTrySendPipe with chan Text and value CExpr.
5990    A CTryReceivePipe with chan Text and target Text.
5991    A CSpawn with agentType Text and target Text.
5992    A CSendMessage with target CExpr and msg CExpr.
5993    A CStreamMessage with target CExpr and values CExpr.
5994    A CAwaitMessage with target Text.
5995    A CListen with addr CExpr and handler Text.
5996    A CConnectTo with addr CExpr and target Text.
5997    A CZone with name Text and kind Text and body Seq of CStmt.
5998
5999## A CSelectBranch is one of:
6000    A CSelectRecv with chan Text and var Text and body Seq of CStmt.
6001    A CSelectTimeout with duration CExpr and body Seq of CStmt.
6002
6003## A CMatchArm is one of:
6004    A CWhen with variantName Text and bindings Seq of Text and body Seq of CStmt.
6005    A COtherwise with body Seq of CStmt.
6006
6007## A CFunc is one of:
6008    A CFuncDef with name Text and params Seq of Text and paramTypes Seq of Text and returnType Text and body Seq of CStmt.
6009
6010## A CProgram is one of:
6011    A CProg with funcs Seq of CFunc and main Seq of CStmt.
6012
6013## A PEState is one of:
6014    A PEStateR with env Map of Text to CVal and funcs Map of Text to CFunc and depth Int and staticEnv Map of Text to CExpr and specResults Map of Text to CExpr and onStack Seq of Text.
6015
6016## A CVal is one of:
6017    A VInt with value Int.
6018    A VFloat with value Real.
6019    A VBool with value Bool.
6020    A VText with value Text.
6021    A VSeq with items Seq of CVal.
6022    A VMap with entries Map of Text to CVal.
6023    A VError with msg Text.
6024    A VNothing.
6025    A VSet with items Seq of CVal.
6026    A VOption with inner CVal and present Bool.
6027    A VTuple with items Seq of CVal.
6028    A VStruct with typeName Text and fields Map of Text to CVal.
6029    A VVariant with typeName Text and variantName Text and fields Seq of CVal.
6030    A VClosure with params Seq of Text and body Seq of CStmt and capturedEnv Map of Text to CVal.
6031    A VDuration with millis Int.
6032    A VDate with year Int and month Int and day Int.
6033    A VMoment with millis Int.
6034    A VSpan with startMillis Int and endMillis Int.
6035    A VTime with hour Int and minute Int and second Int.
6036    A VCrdt with kind Text and state Map of Text to CVal.
6037"#;
6038
6039/// Encodes the partial evaluator as CProgram construction source code.
6040///
6041/// Returns PE function definitions (so peBlock etc. are callable) followed by
6042/// LOGOS statements that construct the PE's functions as CFunc data in
6043/// `encodedFuncMap` and its main block in `encodedMain`.
6044/// The parser handles `## To` blocks anywhere in the token stream, so
6045/// function definitions placed after `## Main` are parsed correctly.
6046pub fn quote_pe_source() -> Result<String, String> {
6047    let pe_source = pe_source_text();
6048    let full_source = format!("{}\n{}", CORE_TYPES_FOR_PE, pe_source);
6049    let encoded = encode_program_source(&full_source).map_err(|e| format!("Failed to encode PE: {:?}", e))?;
6050    Ok(format!("{}\n{}", pe_source, encoded))
6051}
6052
6053/// Second Futamura Projection: PE(PE, interpreter) = compiler
6054///
6055/// Specializes the partial evaluator with respect to a fixed interpreter,
6056/// producing a compiler that takes any CProgram as input and produces
6057/// optimized residual CStmt/CExpr data.
6058///
6059/// For the Core self-interpreter (which is the identity evaluator on CProgram
6060/// data), PE(PE, int) resolves to the PE itself operating directly on program
6061/// data — the interpreter's dispatch loop is the CExpr/CStmt case analysis
6062/// that the PE already implements. The result is the PE with its entry points
6063/// renamed to compileExpr/compileBlock: these ARE the specialized compiler
6064/// functions with no PE dispatch overhead (BTA, memoization, etc. are absent
6065/// because the interpreter's representation IS the PE's representation).
6066///
6067/// NOTE: this is the renamed-PE REPRESENTATION of P2 — it ASSERTS the collapse by renaming. The
6068/// GENUINE projection that DEMONSTRATES it by actually running `PE(pe_source, pe_mini)` is
6069/// [`genuine_projection2_residual`] (and [`run_genuine_p2_on_target`]). Enforcement gates use the
6070/// genuine path; `shortcut_quarantine_lock` forbids any Jones-certifying gate from using this rename.
6071pub fn projection2_source() -> Result<String, String> {
6072    let pe_source = pe_source_text();
6073
6074    let compiler_source = replace_word(&replace_word(&pe_source, "peExpr", "compileExpr"), "peBlock", "compileBlock");
6075
6076    Ok(format!("{}\n{}", CORE_TYPES_FOR_PE, compiler_source))
6077}
6078
6079/// Third Futamura Projection: PE(PE, PE) = compiler_generator
6080///
6081/// Specializes the partial evaluator with respect to itself, producing a
6082/// compiler generator (cogen). Feed it any interpreter → it produces a
6083/// compiler for that interpreter's language.
6084///
6085/// Chain: cogen(int) → compiler → compiler(P) → compiled
6086///
6087/// For the CExpr/CStmt representation, PE(PE, PE) yields the PE with entry
6088/// points renamed to cogenExpr/cogenBlock. This works because the PE's
6089/// self-application is idempotent: the PE already operates on the same
6090/// representation it would specialize, so PE(PE, PE) = PE (up to naming).
6091/// The cogen handles different interpreters (Core, RPN, etc.) by processing
6092/// their encoded CProgram representations.
6093///
6094/// NOTE: the renamed-PE REPRESENTATION of P3 (asserts the collapse by renaming). The GENUINE cogen
6095/// that actually runs `PE(pe_source, pe_bti)` is [`genuine_projection3_residual`]. Enforcement uses
6096/// the genuine path; `shortcut_quarantine_lock` keeps this rename out of any Jones certificate.
6097pub fn projection3_source() -> Result<String, String> {
6098    let pe_source = pe_source_text();
6099
6100    let cogen_source = replace_word(&replace_word(&pe_source, "peExpr", "cogenExpr"), "peBlock", "cogenBlock");
6101
6102    Ok(format!("{}\n{}", CORE_TYPES_FOR_PE, cogen_source))
6103}
6104
6105/// Compile and run LOGOS source, returning stdout.
6106///
6107/// Uses the full compilation pipeline: LOGOS → Rust → binary → execute.
6108/// This is the library-side equivalent of the test infrastructure's `run_logos()`.
6109pub fn run_logos_source(source: &str) -> Result<String, String> {
6110    let compile_output = compile_program_full(source)
6111        .map_err(|e| format!("Compilation failed: {:?}", e))?;
6112
6113    // Create temp directory using std (no tempfile crate needed)
6114    let temp_base = std::env::temp_dir().join("logos_run_source");
6115    std::fs::create_dir_all(&temp_base)
6116        .map_err(|e| format!("mkdir failed: {}", e))?;
6117
6118    let pkg_name = format!(
6119        "logos_run_{}_{}",
6120        std::process::id(),
6121        std::time::SystemTime::now()
6122            .duration_since(std::time::UNIX_EPOCH)
6123            .unwrap()
6124            .as_nanos()
6125    );
6126    let project_dir = temp_base.join(&pkg_name);
6127
6128    // Find workspace root relative to this crate
6129    let manifest_dir = std::path::Path::new(env!("CARGO_MANIFEST_DIR"));
6130    let workspace_root = manifest_dir.parent().unwrap().parent().unwrap();
6131
6132    let cargo_toml = format!(
6133        r#"[package]
6134name = "{}"
6135version = "0.1.0"
6136edition = "2021"
6137
6138[dependencies]
6139logicaffeine-data = {{ path = "{}/crates/logicaffeine_data" }}
6140logicaffeine-system = {{ path = "{}/crates/logicaffeine_system", features = ["full"] }}
6141tokio = {{ version = "1", features = ["rt-multi-thread", "macros"] }}
6142serde = {{ version = "1", features = ["derive"] }}
6143rayon = "1"
6144"#,
6145        pkg_name,
6146        workspace_root.display(),
6147        workspace_root.display(),
6148    );
6149
6150    std::fs::create_dir_all(project_dir.join("src"))
6151        .map_err(|e| format!("mkdir failed: {}", e))?;
6152    std::fs::write(project_dir.join("Cargo.toml"), cargo_toml)
6153        .map_err(|e| format!("Write Cargo.toml failed: {}", e))?;
6154    std::fs::write(project_dir.join("src/main.rs"), &compile_output.rust_code)
6155        .map_err(|e| format!("Write main.rs failed: {}", e))?;
6156    // Pin dependency resolution to the workspace's lockfile: without it each
6157    // generated project re-resolves from the registry and a newly published
6158    // transitive version can break the build.
6159    std::fs::copy(
6160        workspace_root.join("Cargo.lock"),
6161        project_dir.join("Cargo.lock"),
6162    )
6163    .map_err(|e| format!("Seed Cargo.lock failed: {}", e))?;
6164
6165    // Use a shared target dir for caching. Under cargo-nextest, shard by the
6166    // per-test slot (mirrors the test harness's get_shared_target_dir) so
6167    // concurrent generated builds never contend on Cargo's build-dir lock.
6168    let shard = std::env::var("NEXTEST_TEST_GROUP_SLOT")
6169        .ok()
6170        .and_then(|s| s.parse::<u64>().ok())
6171        .or_else(|| {
6172            std::env::var("NEXTEST_TEST_GLOBAL_SLOT")
6173                .ok()
6174                .and_then(|s| s.parse::<u64>().ok())
6175                .map(|n| n % 12)
6176        });
6177    let target_dir = match shard {
6178        Some(s) => std::env::temp_dir().join(format!("logos_e2e_cache_{s}")),
6179        None => std::env::temp_dir().join("logos_e2e_cache"),
6180    };
6181    std::fs::create_dir_all(&target_dir)
6182        .map_err(|e| format!("mkdir target failed: {}", e))?;
6183
6184    let mut run_cmd = std::process::Command::new("cargo");
6185    run_cmd
6186        .args(["run", "--quiet"])
6187        .current_dir(&project_dir)
6188        .env("CARGO_TARGET_DIR", &target_dir)
6189        .env("RUST_MIN_STACK", "268435456");
6190    // Bootstrap-scope: when the compiler is running its own PE / self-interpreter
6191    // (a `ReferenceScope`), the AOT child it spawns must run that compile-time
6192    // Logos under reference semantics — the partial evaluator is self-applicable
6193    // and only specializes when its threaded state is mutated in place.
6194    if crate::semantics::collections::reference_scope_active() {
6195        run_cmd.env("LOGOS_VALUE_SEMANTICS", "0");
6196    }
6197    let output = run_cmd
6198        .output()
6199        .map_err(|e| format!("cargo run failed: {}", e))?;
6200
6201    // Clean up temp project dir AND this project's artifacts in the shared
6202    // cache (unique package names would otherwise accrete one binary +
6203    // deps/incremental/fingerprint entries per run, forever — the shared
6204    // DEPENDENCY builds stay, which is the entire caching win).
6205    let _ = std::fs::remove_dir_all(&project_dir);
6206    {
6207        let stem = pkg_name.replace('-', "_");
6208        let debug = target_dir.join("debug");
6209        let _ = std::fs::remove_file(debug.join(&pkg_name));
6210        let _ = std::fs::remove_file(debug.join(format!("{pkg_name}.d")));
6211        for sub in ["deps", "incremental", ".fingerprint", "build"] {
6212            if let Ok(rd) = std::fs::read_dir(debug.join(sub)) {
6213                for e in rd.flatten() {
6214                    let name = e.file_name();
6215                    let n = name.to_string_lossy();
6216                    if n.starts_with(&format!("{stem}-"))
6217                        || n.starts_with(&format!("lib{stem}-"))
6218                    {
6219                        let path = e.path();
6220                        let _ = if path.is_dir() {
6221                            std::fs::remove_dir_all(&path)
6222                        } else {
6223                            std::fs::remove_file(&path)
6224                        };
6225                    }
6226                }
6227            }
6228        }
6229    }
6230
6231    if !output.status.success() {
6232        return Err(format!(
6233            "Execution failed (status={:?} code={:?}):\nstderr: {}\nstdout: {}",
6234            output.status,
6235            output.status.code(),
6236            String::from_utf8_lossy(&output.stderr),
6237            String::from_utf8_lossy(&output.stdout),
6238        ));
6239    }
6240
6241    Ok(String::from_utf8_lossy(&output.stdout).to_string())
6242}
6243
6244/// Result of a genuine Futamura projection via self-application.
6245/// Contains the LOGOS source of the residual and the discovered entry points.
6246pub struct GenuineProjectionResult {
6247    /// The LOGOS source of the genuine residual — exactly as the PE produced it,
6248    /// including specialized function definitions and a ## Main block.
6249    pub source: String,
6250    /// The name of the block-level entry point (e.g., "peBlockM_d_vPEMiniR_...").
6251    /// This is the specialized function that takes a Seq of CStmt and returns Seq of CStmt.
6252    pub block_entry: String,
6253    /// The name of the expression-level entry point, if discovered.
6254    pub expr_entry: Option<String>,
6255}
6256
6257/// Discover specialized entry points from a PE residual.
6258/// Searches for `## To {prefix}_...` function definitions in the residual source.
6259fn discover_entry_points(residual: &str, block_prefix: &str, expr_prefix: &str)
6260    -> (String, Option<String>)
6261{
6262    let mut block_entry = String::new();
6263    let mut expr_entry = None;
6264    for line in residual.lines() {
6265        let trimmed = line.trim();
6266        if let Some(rest) = trimmed.strip_prefix("## To ") {
6267            // Extract function name: everything before the first ' ('
6268            let name = rest.split(" (").next().unwrap_or("").trim();
6269            if name.starts_with(block_prefix) && block_entry.is_empty() {
6270                block_entry = name.to_string();
6271            } else if name.starts_with(expr_prefix) && expr_entry.is_none() {
6272                expr_entry = Some(name.to_string());
6273            }
6274        }
6275    }
6276    (block_entry, expr_entry)
6277}
6278
6279/// Real Futamura Projection 1: pe(program) = compiled_program
6280///
6281/// Encodes the program as CProgram data, runs the LOGOS PE on it,
6282/// decompiles the residual back to LOGOS source.
6283pub fn projection1_source_real(core_types: &str, _interpreter: &str, program: &str) -> Result<String, String> {
6284    let full_source = if program.contains("## Main") || program.contains("## To ") {
6285        program.to_string()
6286    } else {
6287        format!("## Main\n{}", program)
6288    };
6289
6290    // Step 1: Encode the program as CProgram construction source
6291    let encoded = encode_program_source(&full_source)
6292        .map_err(|e| format!("Failed to encode program: {:?}", e))?;
6293
6294    // Step 2: Get PE source and decompile source
6295    let pe_source = pe_source_text();
6296    let decompile_source = decompile_source_text();
6297
6298    // Step 3: Build the combined source
6299    let actual_core_types = if core_types.is_empty() { CORE_TYPES_FOR_PE } else { core_types };
6300
6301    let driver = r#"
6302    Let state be makePeState(a new Map of Text to CVal, encodedFuncMap, 200).
6303    Let residual be peBlock(encodedMain, state).
6304    Let source be decompileBlock(residual, 0).
6305    Show source.
6306"#;
6307
6308    let combined = format!(
6309        "{}\n{}\n{}\n## Main\n{}\n{}",
6310        actual_core_types,
6311        pe_source,
6312        decompile_source,
6313        encoded,
6314        driver,
6315    );
6316
6317    // Step 4: Compile and run to get the decompiled residual
6318    let raw_residual = run_logos_source(&combined)?;
6319    let trimmed = raw_residual.trim();
6320
6321    // Step 5: Wrap in ## Main if needed
6322    if trimmed.is_empty() {
6323        return Ok("## Main\n".to_string());
6324    }
6325
6326    // Check if the residual already has function definitions
6327    if trimmed.contains("## To ") || trimmed.starts_with("## Main") {
6328        Ok(trimmed.to_string())
6329    } else {
6330        Ok(format!("## Main\n{}", trimmed))
6331    }
6332}
6333
6334/// In-process variant of [`projection1_source_real`].
6335///
6336/// Identical in every respect except that the combined PE source is executed by the
6337/// tree-walking interpreter via [`interpret_program`] rather than compiled to Rust and
6338/// run through `cargo` ([`run_logos_source`]). This makes the genuine LOGOS partial
6339/// evaluator cheap enough to drive a generative/differential test corpus.
6340pub fn projection1_source_real_fast(core_types: &str, _interpreter: &str, program: &str) -> Result<String, String> {
6341    let combined = pe_combined_source(core_types, program)?;
6342
6343    let raw_residual = interpret_program(&combined)
6344        .map_err(|e| format!("PE execution failed: {:?}", e))?;
6345    let residual = finish_projection1_residual(raw_residual)?;
6346    // The residual is statements-only (`## To` specialized funcs + `## Main`); re-attach the
6347    // program's TYPE DEFINITIONS so a `new <Struct>` in the residual can default-fill its
6348    // fields — e.g. a `Shared` struct's CRDT `SharedSet`/`SharedSequence` field, which has no
6349    // explicit constructor argument and is materialized solely from the definition.
6350    Ok(prepend_type_definitions(program, residual))
6351}
6352
6353pub use crate::concurrency::marshal::{decode_value_raw, encode_value_raw};
6354
6355/// The self-interpreter (`coreEval` / `coreExecBlock`) as a first-class committed artifact — the
6356/// Logos program the genuine P2/P3 projections specialize (Futamura: `pe(pe, coreEval)` is a
6357/// compiler). Promoted out of the `phase_futamura` test fixture so it is a real source file, not a
6358/// string constant; the fixture now `include_str!`s this exact file (parity is structural).
6359pub fn core_interp_source() -> &'static str {
6360    include_str!("optimize/core_interp.logos")
6361}
6362
6363/// The compile-once native partial evaluator's SOURCE: the PE engine + decompiler + a driver
6364/// that reads its input `CProgram` from stdin (over the fast wire codec) instead of a baked-in
6365/// constructor. Compiled ONCE (content-addressed on this text) with `WireEncode`/`WireDecode`
6366/// emitted, it specializes any program handed to it as `program_to_core_wire_bytes` — the true
6367/// Futamura "compiler as an artifact", at native speed. The driver body is identical to
6368/// [`pe_combined_source`]'s (same transitive-decompile), only `encodedMain`/`encodedFuncMap`
6369/// come from `readWireProgram()` rather than the encoded program text.
6370/// The transitive-decompile driver logic shared by the resident-server PE (`pe_native_source`,
6371/// terminal statement writes the residual to stdout) and the in-process cdylib PE
6372/// (`pe_cdylib_source`, terminal statement `Return`s it). Everything up to that last statement is
6373/// identical — computing `output`, the Jones-optimal residual source, from `encodedMain` +
6374/// `encodedFuncMap`. Written at 0-base indent; the caller re-indents it under its enclosing arm.
6375const PE_DRIVER_CORE: &str = r#"Let state be makePeState(a new Map of Text to CVal, encodedFuncMap, 200).
6376Let residual be peBlock(encodedMain, state).
6377Let nl be chr(10).
6378Let mutable output be "".
6379Let specFuncs be peFuncs(state).
6380Let mutable allNames be collectCallNames(residual).
6381Let mutable emitted be a new Map of Text to Bool.
6382Let mutable changed be true.
6383While changed:
6384    Set changed to false.
6385    Let mutable toAdd be a new Seq of Text.
6386    Repeat for fnKey in allNames:
6387        Let fkStr be "{fnKey}".
6388        If emitted contains fkStr:
6389            Let skipE be true.
6390        Otherwise:
6391            Set item fkStr of emitted to true.
6392            Let fkStr2 be "{fnKey}".
6393            If specFuncs contains fkStr2:
6394                Let fdef be item fkStr2 of specFuncs.
6395                Inspect fdef:
6396                    When CFuncDef (fn0, ps0, pt0, rt0, body0):
6397                        Let children be collectCallNames(body0).
6398                        Repeat for child in children:
6399                            Let childStr be "{child}".
6400                            If not emitted contains childStr:
6401                                Push child to toAdd.
6402                                Set changed to true.
6403                    Otherwise:
6404                        Let skipF be true.
6405    Repeat for ta in toAdd:
6406        Push ta to allNames.
6407Repeat for fnKey in allNames:
6408    Let fkStr be "{fnKey}".
6409    If specFuncs contains fkStr:
6410        Let fdef be item fkStr of specFuncs.
6411        Let funcSrc be decompileFunc(fdef).
6412        If the length of funcSrc is greater than 0:
6413            Set output to "{output}{funcSrc}{nl}".
6414Let mainSrc be decompileBlock(residual, 0).
6415Set output to "{output}## Main{nl}{mainSrc}"."#;
6416
6417/// The PE driver body with `last` appended as its terminal statement — the only line that differs
6418/// between the stdout-writing server and the value-returning cdylib entry.
6419fn pe_driver_body(last: &str) -> String {
6420    format!("{PE_DRIVER_CORE}\n{last}")
6421}
6422
6423pub fn pe_native_source() -> String {
6424    let core = CORE_TYPES_FOR_PE;
6425    let pe = pe_source_text();
6426    let decompile = decompile_source_text();
6427    // The transitive-decompile driver body, written at 0-base indent — identical logic to
6428    // `pe_combined_source`. It is re-indented under the `When CProg` arm below so the CProg fields
6429    // (`LogosSeq`-typed, not the `Vec` a declared `Seq` return would be) stay in scope with the
6430    // right runtime type — sidestepping the return-type/field-type codegen mismatch.
6431    let driver_body = pe_driver_body("Let __wrn be writeWireResidual(output).");
6432    // Resident-server loop: `While true` reads one length-framed program per iteration
6433    // (`readWireProgram` exits the process on stdin EOF), specializes it, and writes the residual
6434    // back length-framed (`writeWireResidual`). Spawned ONCE by the host and reused — no per-call
6435    // process spawn. Indent the driver body to 16 spaces (inside the `When CProg` arm inside `While`).
6436    let indented: String = driver_body
6437        .lines()
6438        .map(|l| if l.is_empty() { String::new() } else { format!("                {l}") })
6439        .collect::<Vec<_>>()
6440        .join("\n");
6441    let main = format!(
6442        "    While true:\n        Let __prog be readWireProgram().\n        Inspect __prog:\n            When CProg (encFuncs, encMain):\n                Let encodedMain be encMain.\n                Let mutable encodedFuncMap be a new Map of Text to CFunc.\n                Repeat for __f in encFuncs:\n                    Inspect __f:\n                        When CFuncDef (__nm, __ps, __pt, __rt, __bd):\n                            Set item __nm of encodedFuncMap to __f.\n                        Otherwise:\n                            Let __skip be true.\n{indented}\n            Otherwise:\n                Let __wrn be writeWireResidual(\"\").\n"
6443    );
6444    format!("{}\n{}\n{}\n## Main\n{}", core, pe, decompile, main)
6445}
6446
6447/// The PE as an IN-PROCESS cdylib entry: the identical engine to [`pe_native_source`], but the
6448/// driver is a single-shot `## To peSpecializeOnce (__prog: CProgram) -> Text` that RETURNS the
6449/// residual instead of writing it to stdout — no `readWireProgram`/`writeWireResidual`, no stdin
6450/// loop. The exported cdylib ([`build_native_pe_cdylib`]) decodes the wire bytes to a `CProgram` in
6451/// its `extern "C"` shim and calls this function directly, so the resident server's process/pipe
6452/// round-trip is gone. `## Main` is a trivial stub (a cdylib never runs it).
6453pub fn pe_cdylib_source() -> String {
6454    let core = CORE_TYPES_FOR_PE;
6455    let pe = pe_source_text();
6456    let decompile = decompile_source_text();
6457    // Same driver as the server, terminated by `Return output.`; re-indented to 12 spaces to sit
6458    // inside the `When CProg` arm of the function body (one level shallower than the server's
6459    // `While`-nested main).
6460    let driver_body = pe_driver_body("Return output.");
6461    let indented: String = driver_body
6462        .lines()
6463        .map(|l| if l.is_empty() { String::new() } else { format!("            {l}") })
6464        .collect::<Vec<_>>()
6465        .join("\n");
6466    let func = format!(
6467        "## To peSpecializeOnce (__prog: CProgram) -> Text:\n    Inspect __prog:\n        When CProg (encFuncs, encMain):\n            Let encodedMain be encMain.\n            Let mutable encodedFuncMap be a new Map of Text to CFunc.\n            Repeat for __f in encFuncs:\n                Inspect __f:\n                    When CFuncDef (__nm, __ps, __pt, __rt, __bd):\n                        Set item __nm of encodedFuncMap to __f.\n                    Otherwise:\n                        Let __skip be true.\n{indented}\n        Otherwise:\n            Return \"\".\n"
6468    );
6469    format!("{}\n{}\n{}\n{}\n## Main\n    Let __x be 0.\n", core, pe, decompile, func)
6470}
6471
6472use crate::interpreter::RuntimeValue;
6473
6474// ── Native Core-IR value builder ───────────────────────────────────────────────────────────
6475// Builds the `CProgram` VALUE directly in Rust — no interpreter, no re-parse of the 140-line
6476// CORE_TYPES type context (the marshal's dominant cost). Mirrors `encode_expr_src`/`encode_stmt_src`
6477// but emits `RuntimeValue::Inductive` (constructor + fields IN DECLARATION ORDER). The `inductive_type`
6478// is a placeholder: the native `CProgram::wire_decode` dispatches on the constructor and ignores the
6479// type name. Any construct not covered here returns `None`, and the caller falls back to the proven
6480// interpreter path — so a gap is slower, never wrong. Residual-equivalence with the interpreter path
6481// is locked by `native_pe_wire` + the `pe_jones_fuzz` corpus.
6482
6483/// The Core-IR ENUM a constructor belongs to — so the built value's `inductive_type` matches what
6484/// the interpreter records (`a new CInt` → `Inductive{ inductive_type: "CExpr", constructor: "CInt" }`),
6485/// making the native builder's wire bytes byte-identical to the interpreter path. The native decoder
6486/// ignores this field, but matching it keeps every byte-level lock exact.
6487fn cv_enum(ctor: &str) -> &'static str {
6488    match ctor {
6489        "CProg" => "CProgram",
6490        "CFuncDef" => "CFunc",
6491        "CLiteralPart" | "CExprPart" => "CStringPart",
6492        "CInt" | "CBool" | "CText" | "CFloat" | "CVar" | "CBinOp" | "CNot" | "CCall" | "CIndex"
6493        | "CLen" | "CMapGet" | "CList" | "CTuple" | "CRange" | "CSlice" | "CCopy" | "CContains"
6494        | "CUnion" | "CIntersection" | "COptionSome" | "COptionNone" | "CNewSeq" | "CNewSet"
6495        | "CNew" | "CNewVariant" | "CInterpolatedString" | "CClosure" | "CDuration"
6496        | "CCallExpr" | "CChunkAt" | "CEscExpr" | "CManifestOf" => "CExpr",
6497        "CSelectRecv" | "CSelectTimeout" => "CSelectBranch",
6498        _ => "CStmt",
6499    }
6500}
6501
6502fn cv_ind(ctor: &str, args: Vec<RuntimeValue>) -> RuntimeValue {
6503    RuntimeValue::Inductive(Box::new(crate::interpreter::InductiveValue {
6504        inductive_type: cv_enum(ctor).to_string(),
6505        constructor: ctor.to_string(),
6506        args,
6507    }))
6508}
6509fn cv_text(s: &str) -> RuntimeValue {
6510    RuntimeValue::Text(std::rc::Rc::new(s.to_string()))
6511}
6512fn cv_list(items: Vec<RuntimeValue>) -> RuntimeValue {
6513    RuntimeValue::List(std::rc::Rc::new(std::cell::RefCell::new(
6514        crate::interpreter::ListRepr::from_values(items),
6515    )))
6516}
6517
6518/// The binary-operator spelling — IDENTICAL to `encode_expr_src`'s `op_str` (the residual re-parses it).
6519fn cv_binop_str(op: BinaryOpKind) -> &'static str {
6520    match op {
6521        BinaryOpKind::Add => "+",
6522        BinaryOpKind::Subtract => "-",
6523        BinaryOpKind::Multiply => "*",
6524        BinaryOpKind::Divide | BinaryOpKind::ExactDivide => "/",
6525        BinaryOpKind::FloorDivide => "//",
6526        BinaryOpKind::Modulo => "%",
6527        BinaryOpKind::Eq => "==",
6528        BinaryOpKind::NotEq => "!=",
6529        BinaryOpKind::Lt => "<",
6530        BinaryOpKind::Gt => ">",
6531        BinaryOpKind::LtEq => "<=",
6532        BinaryOpKind::GtEq => ">=",
6533        BinaryOpKind::And => "&&",
6534        BinaryOpKind::Or => "||",
6535        BinaryOpKind::Concat => "+",
6536        BinaryOpKind::SeqConcat => "followed by",
6537        BinaryOpKind::ApproxEq => "is approximately",
6538        BinaryOpKind::Pow => "**",
6539        BinaryOpKind::BitXor => "^",
6540        BinaryOpKind::BitAnd => "&",
6541        BinaryOpKind::BitOr => "|",
6542        BinaryOpKind::Shl => "<<",
6543        BinaryOpKind::Shr => ">>",
6544    }
6545}
6546
6547type VCtors = std::collections::HashMap<String, Vec<String>>;
6548
6549/// A backend for the Core-IR builder. ONE generic AST traversal (`expr_to_core` / `stmt_to_core` /
6550/// `build_core`) drives EITHER the reference tree builder ([`TreeSink`] → a `RuntimeValue`, the form
6551/// `encode_value_raw` serializes) OR the single-pass wire emitter ([`WireSink`] → exactly those
6552/// `encode_value_raw` bytes with NO intermediate `RuntimeValue` tree and NO second encode walk).
6553/// Byte-identity of the two backends is locked by `native_builder_is_byte_identical_to_the_interpreter`
6554/// and the single-pass tests — so there is no way for them to silently diverge.
6555trait CoreSink {
6556    type Val;
6557    fn int(n: i64) -> Self::Val;
6558    fn boolean(b: bool) -> Self::Val;
6559    fn float(f: f64) -> Self::Val;
6560    fn text(s: &str) -> Self::Val;
6561    fn list(items: Vec<Self::Val>) -> Self::Val;
6562    fn inductive(ctor: &str, args: Vec<Self::Val>) -> Self::Val;
6563}
6564
6565/// Builds the reference `RuntimeValue` Core-IR tree (what `encode_value_raw` serializes).
6566struct TreeSink;
6567impl CoreSink for TreeSink {
6568    type Val = RuntimeValue;
6569    fn int(n: i64) -> RuntimeValue {
6570        RuntimeValue::Int(n)
6571    }
6572    fn boolean(b: bool) -> RuntimeValue {
6573        RuntimeValue::Bool(b)
6574    }
6575    fn float(f: f64) -> RuntimeValue {
6576        RuntimeValue::Float(f)
6577    }
6578    fn text(s: &str) -> RuntimeValue {
6579        cv_text(s)
6580    }
6581    fn list(items: Vec<RuntimeValue>) -> RuntimeValue {
6582        cv_list(items)
6583    }
6584    fn inductive(ctor: &str, args: Vec<RuntimeValue>) -> RuntimeValue {
6585        cv_ind(ctor, args)
6586    }
6587}
6588
6589/// Emits the wire bytes DIRECTLY — byte-for-byte what `encode_value_raw` (flat lists, structure off,
6590/// dedup off) produces for the equivalent [`TreeSink`] value, but with no `RuntimeValue` allocation
6591/// and no second walk. Each node returns its own encoded bytes; a parent writes its header then
6592/// concatenates its children. The `logicaffeine_data::wire` primitives are proven byte-identical to
6593/// the peer `native_encode` by `peer_and_wire_core_produce_identical_bytes`.
6594struct WireSink;
6595impl CoreSink for WireSink {
6596    type Val = Vec<u8>;
6597    fn int(n: i64) -> Vec<u8> {
6598        let mut v = Vec::with_capacity(4);
6599        v.push(logicaffeine_data::wire::T_INT);
6600        logicaffeine_data::wire::write_uvarint(logicaffeine_data::wire::zigzag(n), &mut v);
6601        v
6602    }
6603    fn boolean(b: bool) -> Vec<u8> {
6604        vec![if b { logicaffeine_data::wire::T_TRUE } else { logicaffeine_data::wire::T_FALSE }]
6605    }
6606    fn float(f: f64) -> Vec<u8> {
6607        let mut v = Vec::with_capacity(9);
6608        v.push(logicaffeine_data::wire::T_FLOAT);
6609        v.extend_from_slice(&f.to_le_bytes());
6610        v
6611    }
6612    fn text(s: &str) -> Vec<u8> {
6613        let mut v = Vec::with_capacity(2 + s.len());
6614        v.push(logicaffeine_data::wire::T_TEXT);
6615        logicaffeine_data::wire::write_str(s, &mut v);
6616        v
6617    }
6618    fn list(items: Vec<Vec<u8>>) -> Vec<u8> {
6619        let mut v = Vec::new();
6620        v.push(logicaffeine_data::wire::T_LIST);
6621        logicaffeine_data::wire::write_uvarint(items.len() as u64, &mut v);
6622        for it in &items {
6623            v.extend_from_slice(it);
6624        }
6625        v
6626    }
6627    fn inductive(ctor: &str, args: Vec<Vec<u8>>) -> Vec<u8> {
6628        let mut v = Vec::new();
6629        logicaffeine_data::wire::write_inductive_header(&mut v, cv_enum(ctor), ctor, args.len() as u64);
6630        for a in &args {
6631            v.extend_from_slice(a);
6632        }
6633        v
6634    }
6635}
6636
6637fn exprs_to_core<S: CoreSink>(args: &[&Expr], interner: &Interner, vctors: &VCtors) -> Option<Vec<S::Val>> {
6638    let mut out = Vec::with_capacity(args.len());
6639    for a in args {
6640        out.push(expr_to_core::<S>(a, interner, vctors)?);
6641    }
6642    Some(out)
6643}
6644
6645fn expr_to_core<S: CoreSink>(expr: &Expr, interner: &Interner, vctors: &VCtors) -> Option<S::Val> {
6646    Some(match expr {
6647        Expr::Literal(Literal::Number(n)) => S::inductive("CInt", vec![S::int(*n)]),
6648        Expr::Literal(Literal::Boolean(b)) => S::inductive("CBool", vec![S::boolean(*b)]),
6649        Expr::Literal(Literal::Text(s)) => S::inductive("CText", vec![S::text(interner.resolve(*s))]),
6650        Expr::Literal(Literal::Float(f)) => S::inductive("CFloat", vec![S::float(*f)]),
6651        // A duration literal (`1 seconds`, `500 milliseconds`) → `CDuration{amount = CInt(ms), unit}`,
6652        // normalized to milliseconds exactly as the reference does (used by `Sleep` and `Select`'s
6653        // `After …` timeout).
6654        Expr::Literal(Literal::Duration(nanos)) => S::inductive(
6655            "CDuration",
6656            vec![S::inductive("CInt", vec![S::int(nanos / 1_000_000)]), S::text("milliseconds")],
6657        ),
6658        Expr::Literal(Literal::Nothing) => S::inductive("CText", vec![S::text("nothing")]),
6659        Expr::Identifier(sym) => S::inductive("CVar", vec![S::text(interner.resolve(*sym))]),
6660        Expr::BinaryOp { op, left, right } => S::inductive(
6661            "CBinOp",
6662            vec![S::text(cv_binop_str(*op)), expr_to_core::<S>(left, interner, vctors)?, expr_to_core::<S>(right, interner, vctors)?],
6663        ),
6664        Expr::Not { operand } => S::inductive("CNot", vec![expr_to_core::<S>(operand, interner, vctors)?]),
6665        Expr::Call { function, args } => {
6666            let fn_name = interner.resolve(*function);
6667            if let Some(field_names) = vctors.get(fn_name) {
6668                // Variant constructor call → CNewVariant (field names from the type registry).
6669                let mut fnames: Vec<S::Val> = Vec::with_capacity(args.len());
6670                let mut fvals: Vec<S::Val> = Vec::with_capacity(args.len());
6671                for (i, a) in args.iter().enumerate() {
6672                    fnames.push(S::text(field_names.get(i).map(|s| s.as_str()).unwrap_or("value")));
6673                    fvals.push(expr_to_core::<S>(a, interner, vctors)?);
6674                }
6675                S::inductive("CNewVariant", vec![S::text(fn_name), S::list(fnames), S::list(fvals)])
6676            } else {
6677                let avals = exprs_to_core::<S>(args, interner, vctors)?;
6678                S::inductive("CCall", vec![S::text(fn_name), S::list(avals)])
6679            }
6680        }
6681        Expr::Index { collection, index } => S::inductive(
6682            "CIndex",
6683            vec![expr_to_core::<S>(collection, interner, vctors)?, expr_to_core::<S>(index, interner, vctors)?],
6684        ),
6685        Expr::Length { collection } => S::inductive("CLen", vec![expr_to_core::<S>(collection, interner, vctors)?]),
6686        Expr::FieldAccess { object, field } => S::inductive(
6687            "CMapGet",
6688            vec![expr_to_core::<S>(object, interner, vctors)?, S::inductive("CText", vec![S::text(interner.resolve(*field))])],
6689        ),
6690        Expr::List(elems) => {
6691            let mut items: Vec<S::Val> = Vec::with_capacity(elems.len());
6692            for e in elems.iter() {
6693                items.push(expr_to_core::<S>(e, interner, vctors)?);
6694            }
6695            S::inductive("CList", vec![S::list(items)])
6696        }
6697        Expr::Tuple(elems) => {
6698            let mut items: Vec<S::Val> = Vec::with_capacity(elems.len());
6699            for e in elems.iter() {
6700                items.push(expr_to_core::<S>(e, interner, vctors)?);
6701            }
6702            S::inductive("CTuple", vec![S::list(items)])
6703        }
6704        Expr::Range { start, end } => S::inductive("CRange", vec![expr_to_core::<S>(start, interner, vctors)?, expr_to_core::<S>(end, interner, vctors)?]),
6705        Expr::Slice { collection, start, end } => S::inductive(
6706            "CSlice",
6707            vec![expr_to_core::<S>(collection, interner, vctors)?, expr_to_core::<S>(start, interner, vctors)?, expr_to_core::<S>(end, interner, vctors)?],
6708        ),
6709        Expr::Copy { expr } => S::inductive("CCopy", vec![expr_to_core::<S>(expr, interner, vctors)?]),
6710        Expr::Contains { collection, value } => S::inductive("CContains", vec![expr_to_core::<S>(collection, interner, vctors)?, expr_to_core::<S>(value, interner, vctors)?]),
6711        Expr::Union { left, right } => S::inductive("CUnion", vec![expr_to_core::<S>(left, interner, vctors)?, expr_to_core::<S>(right, interner, vctors)?]),
6712        Expr::Intersection { left, right } => S::inductive("CIntersection", vec![expr_to_core::<S>(left, interner, vctors)?, expr_to_core::<S>(right, interner, vctors)?]),
6713        Expr::OptionSome { value } => S::inductive("COptionSome", vec![expr_to_core::<S>(value, interner, vctors)?]),
6714        Expr::OptionNone => S::inductive("COptionNone", vec![]),
6715        Expr::InterpolatedString(parts) => {
6716            if parts.is_empty() {
6717                S::inductive("CText", vec![S::text("")])
6718            } else {
6719                let mut pvals: Vec<S::Val> = Vec::with_capacity(parts.len());
6720                for part in parts {
6721                    pvals.push(match part {
6722                        StringPart::Literal(sym) => S::inductive("CLiteralPart", vec![S::text(interner.resolve(*sym))]),
6723                        StringPart::Expr { value, .. } => S::inductive("CExprPart", vec![expr_to_core::<S>(value, interner, vctors)?]),
6724                    });
6725                }
6726                S::inductive("CInterpolatedString", vec![S::list(pvals)])
6727            }
6728        }
6729        Expr::NewVariant { variant, fields, .. } => {
6730            let mut fnames: Vec<S::Val> = Vec::with_capacity(fields.len());
6731            let mut fvals: Vec<S::Val> = Vec::with_capacity(fields.len());
6732            for (fname, fexpr) in fields {
6733                fnames.push(S::text(interner.resolve(*fname)));
6734                fvals.push(expr_to_core::<S>(fexpr, interner, vctors)?);
6735            }
6736            S::inductive("CNewVariant", vec![S::text(interner.resolve(*variant)), S::list(fnames), S::list(fvals)])
6737        }
6738        Expr::New { type_name, init_fields, .. } => {
6739            let tn = interner.resolve(*type_name);
6740            if tn == "Seq" || tn == "List" {
6741                S::inductive("CNewSeq", vec![])
6742            } else if tn == "Set" {
6743                S::inductive("CNewSet", vec![])
6744            } else if tn == "Map" || tn.starts_with("Map ") {
6745                S::inductive("CNew", vec![S::text("Map"), S::list(vec![]), S::list(vec![])])
6746            } else if init_fields.is_empty() {
6747                S::inductive("CNewVariant", vec![S::text(tn), S::list(vec![]), S::list(vec![])])
6748            } else {
6749                let mut fnames: Vec<S::Val> = Vec::with_capacity(init_fields.len());
6750                let mut fvals: Vec<S::Val> = Vec::with_capacity(init_fields.len());
6751                for (fname, fexpr) in init_fields {
6752                    fnames.push(S::text(interner.resolve(*fname)));
6753                    fvals.push(expr_to_core::<S>(fexpr, interner, vctors)?);
6754                }
6755                S::inductive("CNew", vec![S::text(tn), S::list(fnames), S::list(fvals)])
6756            }
6757        }
6758        // Closure: params (Seq Text), body (Seq CStmt), captured free vars (Seq Text, SORTED to
6759        // match the now-deterministic reference encoder). An expression body wraps as `CReturn e`.
6760        Expr::Closure { params, body, .. } => {
6761            let mut bound: std::collections::HashSet<String> = std::collections::HashSet::new();
6762            let mut param_vals: Vec<S::Val> = Vec::with_capacity(params.len());
6763            for (sym, _) in params {
6764                let name = interner.resolve(*sym);
6765                bound.insert(name.to_string());
6766                param_vals.push(S::text(name));
6767            }
6768            let body_vals: Vec<S::Val> = match body {
6769                ClosureBody::Expression(e) => {
6770                    vec![S::inductive("CReturn", vec![expr_to_core::<S>(e, interner, vctors)?])]
6771                }
6772                ClosureBody::Block(stmts) => {
6773                    let mut bv: Vec<S::Val> = Vec::with_capacity(stmts.len());
6774                    for s in stmts.iter() {
6775                        // The reference encodes body stmts with the single-stmt encoder, which
6776                        // defers Inspect/Repeat to empty; fall back rather than replicate that quirk.
6777                        if matches!(s, Stmt::Inspect { .. } | Stmt::Repeat { .. }) {
6778                            return None;
6779                        }
6780                        bv.push(stmt_to_core::<S>(s, interner, vctors)?);
6781                    }
6782                    bv
6783                }
6784            };
6785            let mut free: Vec<String> =
6786                collect_free_vars_expr(expr, interner, &bound).into_iter().collect();
6787            free.sort();
6788            let cap_vals: Vec<S::Val> = free.iter().map(|fv| S::text(fv)).collect();
6789            S::inductive("CClosure", vec![S::list(param_vals), S::list(body_vals), S::list(cap_vals)])
6790        }
6791        // A callee-expression call (`(f)(x)`) → `CCallExpr{target, args}`.
6792        Expr::CallExpr { callee, args } => {
6793            let mut avals: Vec<S::Val> = Vec::with_capacity(args.len());
6794            for a in args.iter() {
6795                avals.push(expr_to_core::<S>(a, interner, vctors)?);
6796            }
6797            S::inductive("CCallExpr", vec![expr_to_core::<S>(callee, interner, vctors)?, S::list(avals)])
6798        }
6799        Expr::ChunkAt { index, zone } => S::inductive(
6800            "CChunkAt",
6801            vec![expr_to_core::<S>(index, interner, vctors)?, expr_to_core::<S>(zone, interner, vctors)?],
6802        ),
6803        Expr::ManifestOf { zone } => S::inductive("CManifestOf", vec![expr_to_core::<S>(zone, interner, vctors)?]),
6804        Expr::Escape { code, .. } => S::inductive("CEscExpr", vec![S::text(interner.resolve(*code))]),
6805        // `Give`/`WithCapacity` are transparent: the reference encodes the inner value with no node
6806        // (an ownership move / an allocation hint the interpreter erases).
6807        Expr::Give { value } => expr_to_core::<S>(value, interner, vctors)?,
6808        Expr::WithCapacity { value, .. } => expr_to_core::<S>(value, interner, vctors)?,
6809        _ => return None,
6810    })
6811}
6812
6813fn stmt_to_core<S: CoreSink>(stmt: &Stmt, interner: &Interner, vctors: &VCtors) -> Option<S::Val> {
6814    Some(match stmt {
6815        Stmt::Let { var, value, .. } => S::inductive("CLet", vec![S::text(interner.resolve(*var)), expr_to_core::<S>(value, interner, vctors)?]),
6816        Stmt::Set { target, value } => S::inductive("CSet", vec![S::text(interner.resolve(*target)), expr_to_core::<S>(value, interner, vctors)?]),
6817        Stmt::Show { object, .. } => S::inductive("CShow", vec![expr_to_core::<S>(object, interner, vctors)?]),
6818        Stmt::SetIndex { collection, index, value } => S::inductive(
6819            "CSetIdx",
6820            vec![S::text(&extract_ident_name(collection, interner)), expr_to_core::<S>(index, interner, vctors)?, expr_to_core::<S>(value, interner, vctors)?],
6821        ),
6822        Stmt::SetField { object, field, value } => S::inductive(
6823            "CMapSet",
6824            vec![
6825                S::text(&extract_ident_name(object, interner)),
6826                S::inductive("CText", vec![S::text(interner.resolve(*field))]),
6827                expr_to_core::<S>(value, interner, vctors)?,
6828            ],
6829        ),
6830        Stmt::Pop { collection, .. } => S::inductive("CPop", vec![S::text(&extract_ident_name(collection, interner))]),
6831        Stmt::Give { object, recipient } => S::inductive(
6832            "CGive",
6833            vec![expr_to_core::<S>(object, interner, vctors)?, S::text(&extract_ident_name(recipient, interner))],
6834        ),
6835        Stmt::Sleep { milliseconds } => S::inductive("CSleep", vec![expr_to_core::<S>(milliseconds, interner, vctors)?]),
6836        Stmt::RuntimeAssert { condition, hard } => S::inductive(
6837            if *hard { "CHardAssert" } else { "CRuntimeAssert" },
6838            vec![expr_to_core::<S>(condition, interner, vctors)?, S::inductive("CText", vec![S::text("assertion failed")])],
6839        ),
6840        Stmt::Return { value } => {
6841            let e = match value {
6842                Some(v) => expr_to_core::<S>(v, interner, vctors)?,
6843                None => S::inductive("CInt", vec![S::int(0)]),
6844            };
6845            S::inductive("CReturn", vec![e])
6846        }
6847        Stmt::Break => S::inductive("CBreak", vec![]),
6848        Stmt::If { cond, then_block, else_block } => {
6849            let cond_v = expr_to_core::<S>(cond, interner, vctors)?;
6850            let then_s: Vec<&Stmt> = then_block.iter().collect();
6851            let then_v = stmt_list_to_core::<S>(&then_s, interner, vctors)?;
6852            let else_v = match else_block {
6853                Some(els) => {
6854                    let else_s: Vec<&Stmt> = els.iter().collect();
6855                    stmt_list_to_core::<S>(&else_s, interner, vctors)?
6856                }
6857                None => S::list(vec![]),
6858            };
6859            S::inductive("CIf", vec![cond_v, then_v, else_v])
6860        }
6861        Stmt::While { cond, body, .. } => {
6862            let cond_v = expr_to_core::<S>(cond, interner, vctors)?;
6863            let body_s: Vec<&Stmt> = body.iter().collect();
6864            S::inductive("CWhile", vec![cond_v, stmt_list_to_core::<S>(&body_s, interner, vctors)?])
6865        }
6866        Stmt::Call { function, args } => {
6867            let mut avals: Vec<S::Val> = Vec::with_capacity(args.len());
6868            for a in args {
6869                avals.push(expr_to_core::<S>(a, interner, vctors)?);
6870            }
6871            S::inductive("CCallS", vec![S::text(interner.resolve(*function)), S::list(avals)])
6872        }
6873        Stmt::Push { value, collection } => S::inductive(
6874            "CPush",
6875            vec![expr_to_core::<S>(value, interner, vctors)?, S::text(&extract_ident_name(collection, interner))],
6876        ),
6877        Stmt::Repeat { pattern, iterable, body, .. } => {
6878            let loop_var = match pattern {
6879                Pattern::Identifier(sym) => interner.resolve(*sym).to_string(),
6880                Pattern::Tuple(syms) => syms.first().map(|s| interner.resolve(*s).to_string()).unwrap_or_else(|| "item".to_string()),
6881            };
6882            let body_s: Vec<&Stmt> = body.iter().collect();
6883            let body_v = stmt_list_to_core::<S>(&body_s, interner, vctors)?;
6884            if let Expr::Range { start, end } = iterable {
6885                S::inductive(
6886                    "CRepeatRange",
6887                    vec![S::text(&loop_var), expr_to_core::<S>(start, interner, vctors)?, expr_to_core::<S>(end, interner, vctors)?, body_v],
6888                )
6889            } else {
6890                S::inductive("CRepeat", vec![S::text(&loop_var), expr_to_core::<S>(iterable, interner, vctors)?, body_v])
6891            }
6892        }
6893        // CRDT ops (mirror `encode_stmt_src`; declaration order from CORE_TYPES_FOR_PE).
6894        Stmt::Add { value, collection } => S::inductive(
6895            "CAdd",
6896            vec![expr_to_core::<S>(value, interner, vctors)?, S::text(&extract_ident_name(collection, interner))],
6897        ),
6898        Stmt::Remove { value, collection } => S::inductive(
6899            "CRemove",
6900            vec![expr_to_core::<S>(value, interner, vctors)?, S::text(&extract_ident_name(collection, interner))],
6901        ),
6902        Stmt::MergeCrdt { source, target } => S::inductive(
6903            "CMerge",
6904            vec![S::text(&extract_ident_name(target, interner)), expr_to_core::<S>(source, interner, vctors)?],
6905        ),
6906        Stmt::IncreaseCrdt { object, field, amount } => S::inductive(
6907            "CIncrease",
6908            vec![
6909                S::text(&format!("{}'s {}", extract_ident_name(object, interner), interner.resolve(*field))),
6910                expr_to_core::<S>(amount, interner, vctors)?,
6911            ],
6912        ),
6913        Stmt::DecreaseCrdt { object, field, amount } => S::inductive(
6914            "CDecrease",
6915            vec![
6916                S::text(&format!("{}'s {}", extract_ident_name(object, interner), interner.resolve(*field))),
6917                expr_to_core::<S>(amount, interner, vctors)?,
6918            ],
6919        ),
6920        // Task launch (both handled forms; body is a single `CCallS`, handle defaults to `_task`).
6921        Stmt::LaunchTask { function, args } | Stmt::LaunchTaskWithHandle { function, args, .. } => {
6922            let func_name = interner.resolve(*function);
6923            let mut avals: Vec<S::Val> = Vec::with_capacity(args.len());
6924            for a in args {
6925                avals.push(expr_to_core::<S>(a, interner, vctors)?);
6926            }
6927            let call = S::inductive("CCallS", vec![S::text(func_name), S::list(avals)]);
6928            let body = S::list(vec![call]);
6929            let handle_name = if let Stmt::LaunchTaskWithHandle { handle, .. } = stmt {
6930                interner.resolve(*handle).to_string()
6931            } else {
6932                "_task".to_string()
6933            };
6934            S::inductive("CLaunchTask", vec![body, S::text(&handle_name)])
6935        }
6936        // Concurrency: one branch (Seq of CStmt) per task. The reference encodes each task with the
6937        // single-stmt encoder, which defers Inspect/Repeat to an EMPTY branch; rather than replicate
6938        // that quirk, fall back the whole program if a task is one of those.
6939        Stmt::Concurrent { tasks } => {
6940            let mut branches: Vec<S::Val> = Vec::with_capacity(tasks.len());
6941            for task in tasks.iter() {
6942                if matches!(task, Stmt::Inspect { .. } | Stmt::Repeat { .. }) {
6943                    return None;
6944                }
6945                branches.push(S::list(vec![stmt_to_core::<S>(task, interner, vctors)?]));
6946            }
6947            S::inductive("CConcurrent", vec![S::list(branches)])
6948        }
6949        Stmt::Parallel { tasks } => {
6950            let mut branches: Vec<S::Val> = Vec::with_capacity(tasks.len());
6951            for task in tasks.iter() {
6952                if matches!(task, Stmt::Inspect { .. } | Stmt::Repeat { .. }) {
6953                    return None;
6954                }
6955                branches.push(S::list(vec![stmt_to_core::<S>(task, interner, vctors)?]));
6956            }
6957            S::inductive("CParallel", vec![S::list(branches)])
6958        }
6959        // Sequence append (`Append x to d's lines`) — the base struct is forced dynamic at the list
6960        // level (see `stmt_list_to_vec`); here just the op. `target` round-trips as surface syntax.
6961        Stmt::AppendToSequence { sequence, value } => S::inductive(
6962            "CAppendToSeq",
6963            vec![S::text(&extract_ident_name(sequence, interner)), expr_to_core::<S>(value, interner, vctors)?],
6964        ),
6965        // Pipes / channels. Capacity is a `CInt` expression node (default 32, matching the reference).
6966        Stmt::CreatePipe { var: pipe_var, capacity, .. } => {
6967            let cap = capacity.unwrap_or(32);
6968            S::inductive(
6969                "CCreatePipe",
6970                vec![S::text(interner.resolve(*pipe_var)), S::inductive("CInt", vec![S::int(cap as i64)])],
6971            )
6972        }
6973        Stmt::SendPipe { value, pipe } => S::inductive(
6974            "CSendPipe",
6975            vec![S::text(&pipe_ident_name(pipe, interner)), expr_to_core::<S>(value, interner, vctors)?],
6976        ),
6977        Stmt::ReceivePipe { var: recv_var, pipe } => S::inductive(
6978            "CReceivePipe",
6979            vec![S::text(&pipe_ident_name(pipe, interner)), S::text(interner.resolve(*recv_var))],
6980        ),
6981        Stmt::TrySendPipe { value, pipe, .. } => S::inductive(
6982            "CTrySendPipe",
6983            vec![S::text(&pipe_ident_name(pipe, interner)), expr_to_core::<S>(value, interner, vctors)?],
6984        ),
6985        Stmt::TryReceivePipe { var: recv_var, pipe } => S::inductive(
6986            "CTryReceivePipe",
6987            vec![S::text(&pipe_ident_name(pipe, interner)), S::text(interner.resolve(*recv_var))],
6988        ),
6989        // Select over channels: one `CSelectRecv{chan,var,body}` or `CSelectTimeout{duration,body}`
6990        // per branch. Branch bodies use the single-stmt encoder (fall back on Inspect/Repeat).
6991        Stmt::Select { branches } => {
6992            let mut bvals: Vec<S::Val> = Vec::with_capacity(branches.len());
6993            for branch in branches.iter() {
6994                match branch {
6995                    SelectBranch::Receive { var: recv_var, pipe, body } => bvals.push(S::inductive(
6996                        "CSelectRecv",
6997                        vec![
6998                            S::text(&pipe_ident_name(pipe, interner)),
6999                            S::text(interner.resolve(*recv_var)),
7000                            select_body_to_core::<S>(body, interner, vctors)?,
7001                        ],
7002                    )),
7003                    SelectBranch::Timeout { milliseconds, body } => bvals.push(S::inductive(
7004                        "CSelectTimeout",
7005                        vec![
7006                            expr_to_core::<S>(milliseconds, interner, vctors)?,
7007                            select_body_to_core::<S>(body, interner, vctors)?,
7008                        ],
7009                    )),
7010                }
7011            }
7012            S::inductive("CSelect", vec![S::list(bvals)])
7013        }
7014        // Proof / verification directives — the proposition/predicate is erased to `CBool(true)`
7015        // (it has no runtime effect to specialize; only its shape survives into the residual).
7016        Stmt::Assert { .. } => S::inductive("CAssert", vec![S::inductive("CBool", vec![S::boolean(true)])]),
7017        Stmt::Check { source_text, .. } => S::inductive(
7018            "CCheck",
7019            vec![S::inductive("CBool", vec![S::boolean(true)]), S::inductive("CText", vec![S::text(source_text)])],
7020        ),
7021        Stmt::Trust { justification, .. } => S::inductive(
7022            "CTrust",
7023            vec![S::inductive("CBool", vec![S::boolean(true)]), S::text(interner.resolve(*justification))],
7024        ),
7025        Stmt::Require { crate_name, .. } => S::inductive("CRequire", vec![S::text(interner.resolve(*crate_name))]),
7026        // Inline-native escape hatch.
7027        Stmt::Escape { code, .. } => S::inductive("CEscStmt", vec![S::text(interner.resolve(*code))]),
7028        // `Splice` (unconditional inlined block) desugars to `CIf(true, body, [])`.
7029        Stmt::Splice { body } => {
7030            let body_refs: Vec<&Stmt> = body.iter().collect();
7031            S::inductive(
7032                "CIf",
7033                vec![
7034                    S::inductive("CBool", vec![S::boolean(true)]),
7035                    stmt_list_to_core::<S>(&body_refs, interner, vctors)?,
7036                    S::list(vec![]),
7037                ],
7038            )
7039        }
7040        // Task / actor control.
7041        Stmt::StopTask { handle } => S::inductive("CStopTask", vec![expr_to_core::<S>(handle, interner, vctors)?]),
7042        Stmt::Spawn { agent_type, name } => S::inductive(
7043            "CSpawn",
7044            vec![S::text(interner.resolve(*agent_type)), S::text(interner.resolve(*name))],
7045        ),
7046        // Structured-concurrency zone (kind fixed to "heap"); body via the single-stmt encoder.
7047        Stmt::Zone { name, body, .. } => S::inductive(
7048            "CZone",
7049            vec![S::text(interner.resolve(*name)), S::text("heap"), select_body_to_core::<S>(body, interner, vctors)?],
7050        ),
7051        // CRDT conflict resolution (base struct forced dynamic at the list level, like Increase/Merge).
7052        Stmt::ResolveConflict { object, field, .. } => S::inductive(
7053            "CResolve",
7054            vec![S::text(&format!("{}'s {}", extract_ident_name(object, interner), interner.resolve(*field)))],
7055        ),
7056        // Sync / mount.
7057        Stmt::Sync { var: sync_var, topic } => S::inductive(
7058            "CSync",
7059            vec![S::text(interner.resolve(*sync_var)), expr_to_core::<S>(topic, interner, vctors)?],
7060        ),
7061        Stmt::Mount { var: mount_var, path } => S::inductive(
7062            "CMount",
7063            vec![S::text(interner.resolve(*mount_var)), expr_to_core::<S>(path, interner, vctors)?],
7064        ),
7065        // IO.
7066        Stmt::ReadFrom { var: read_var, source } => {
7067            let var_name = interner.resolve(*read_var);
7068            match source {
7069                ReadSource::Console => S::inductive("CReadConsole", vec![S::text(var_name)]),
7070                ReadSource::File(path_expr) => S::inductive(
7071                    "CReadFile",
7072                    vec![expr_to_core::<S>(path_expr, interner, vctors)?, S::text(var_name)],
7073                ),
7074            }
7075        }
7076        Stmt::WriteFile { content, path } => S::inductive(
7077            "CWriteFile",
7078            vec![expr_to_core::<S>(path, interner, vctors)?, expr_to_core::<S>(content, interner, vctors)?],
7079        ),
7080        // Networking / messaging. (`LetPeerAgent` encodes as `CConnectTo` with the agent as target.)
7081        Stmt::ConnectTo { address, .. } => S::inductive(
7082            "CConnectTo",
7083            vec![expr_to_core::<S>(address, interner, vctors)?, S::text("conn")],
7084        ),
7085        Stmt::Listen { address, .. } => S::inductive(
7086            "CListen",
7087            vec![expr_to_core::<S>(address, interner, vctors)?, S::text("default")],
7088        ),
7089        Stmt::SendMessage { message, destination, .. } => S::inductive(
7090            "CSendMessage",
7091            vec![expr_to_core::<S>(destination, interner, vctors)?, expr_to_core::<S>(message, interner, vctors)?],
7092        ),
7093        Stmt::AwaitMessage { into, .. } => S::inductive("CAwaitMessage", vec![S::text(interner.resolve(*into))]),
7094        Stmt::StreamMessage { values, destination } => S::inductive(
7095            "CStreamMessage",
7096            vec![expr_to_core::<S>(destination, interner, vctors)?, expr_to_core::<S>(values, interner, vctors)?],
7097        ),
7098        Stmt::LetPeerAgent { var: pa_var, address } => S::inductive(
7099            "CConnectTo",
7100            vec![expr_to_core::<S>(address, interner, vctors)?, S::text(interner.resolve(*pa_var))],
7101        ),
7102        _ => return None,
7103    })
7104}
7105
7106/// A channel name: a plain identifier, else the literal `"pipe"` (mirrors `encode_stmt_src`).
7107fn pipe_ident_name(pipe: &Expr, interner: &Interner) -> String {
7108    match pipe {
7109        Expr::Identifier(sym) => interner.resolve(*sym).to_string(),
7110        _ => "pipe".to_string(),
7111    }
7112}
7113
7114/// A `Select` branch body: each statement via the SINGLE-statement encoder — the reference defers
7115/// Inspect/Repeat to empty here, so fall back rather than replicate that quirk.
7116fn select_body_to_core<S: CoreSink>(body: &[Stmt], interner: &Interner, vctors: &VCtors) -> Option<S::Val> {
7117    let mut items: Vec<S::Val> = Vec::with_capacity(body.len());
7118    for s in body.iter() {
7119        if matches!(s, Stmt::Inspect { .. } | Stmt::Repeat { .. }) {
7120            return None;
7121        }
7122        items.push(stmt_to_core::<S>(s, interner, vctors)?);
7123    }
7124    Some(S::list(items))
7125}
7126
7127fn stmt_list_to_core<S: CoreSink>(stmts: &[&Stmt], interner: &Interner, vctors: &VCtors) -> Option<S::Val> {
7128    Some(S::list(stmt_list_to_vec::<S>(stmts, interner, vctors)?))
7129}
7130
7131/// Flatten a statement list into Core-IR nodes. Most statements map 1:1 via `stmt_to_core`; an
7132/// `Inspect` DESUGARS in place ([`inspect_to_cif`]) into the exact flat `CIf` chain `encode_stmts_src`
7133/// emits — so the PE folds a statically-known match to its single arm, Jones-optimally.
7134fn stmt_list_to_vec<S: CoreSink>(stmts: &[&Stmt], interner: &Interner, vctors: &VCtors) -> Option<Vec<S::Val>> {
7135    let mut out: Vec<S::Val> = Vec::with_capacity(stmts.len());
7136    for s in stmts {
7137        match s {
7138            Stmt::Inspect { .. } => inspect_to_cif::<S>(s, &mut out, interner, vctors)?,
7139            // A CRDT op forces its base struct DYNAMIC first (so the PE cannot statically fold a
7140            // struct it then mutates), THEN emits the op — a list-level transform mirroring
7141            // `encode_stmts_src`. `crdt_base_var` is `None` for a plain-variable collection (a bare
7142            // `Add x to s`), so those emit no force, exactly like the reference.
7143            Stmt::Add { collection, .. } | Stmt::Remove { collection, .. } => {
7144                if let Some(base) = crdt_base_var(collection, interner) {
7145                    out.push(S::inductive("CForceDynamic", vec![S::text(&base)]));
7146                }
7147                out.push(stmt_to_core::<S>(s, interner, vctors)?);
7148            }
7149            Stmt::AppendToSequence { sequence, .. } => {
7150                if let Some(base) = crdt_base_var(sequence, interner) {
7151                    out.push(S::inductive("CForceDynamic", vec![S::text(&base)]));
7152                }
7153                out.push(stmt_to_core::<S>(s, interner, vctors)?);
7154            }
7155            Stmt::IncreaseCrdt { object, .. } | Stmt::DecreaseCrdt { object, .. } | Stmt::ResolveConflict { object, .. } => {
7156                if matches!(object, Expr::Identifier(_) | Expr::FieldAccess { .. }) {
7157                    out.push(S::inductive("CForceDynamic", vec![S::text(&crdt_base_var_root(object, interner))]));
7158                }
7159                out.push(stmt_to_core::<S>(s, interner, vctors)?);
7160            }
7161            Stmt::MergeCrdt { target, .. } => {
7162                if matches!(target, Expr::Identifier(_) | Expr::FieldAccess { .. }) {
7163                    out.push(S::inductive("CForceDynamic", vec![S::text(&crdt_base_var_root(target, interner))]));
7164                }
7165                out.push(stmt_to_core::<S>(s, interner, vctors)?);
7166            }
7167            _ => out.push(stmt_to_core::<S>(s, interner, vctors)?),
7168        }
7169    }
7170    Some(out)
7171}
7172
7173thread_local! {
7174    /// Per-program index for the `Inspect`-with-`Otherwise` "did any arm match" flag. Reset at the
7175    /// start of every full-program encode ([`program_to_core`] AND `encode_program_source`); both the
7176    /// native builder and the interpreter reference increment it in the SAME traversal (functions in
7177    /// source order, then main; DFS into bodies), so the Nth such Inspect is named `__inspectMatched_N`
7178    /// identically in both — DETERMINISTIC and collision-free. (The reference formerly named it from
7179    /// its global intermediate counter, whose value the value-based builder cannot reproduce; this
7180    /// dedicated index is the reproducible replacement.)
7181    static INSPECT_OTHERWISE_IDX: std::cell::Cell<usize> = const { std::cell::Cell::new(0) };
7182}
7183
7184fn reset_inspect_otherwise_idx() {
7185    INSPECT_OTHERWISE_IDX.with(|c| c.set(0));
7186}
7187
7188/// The next `Inspect`-with-`Otherwise` flag index (post-increment). Called at the START of each such
7189/// Inspect's desugaring in BOTH encoders, so a nested Inspect gets a strictly higher index than its
7190/// enclosing one — no two live flags ever collide.
7191pub fn next_inspect_otherwise_idx() -> usize {
7192    INSPECT_OTHERWISE_IDX.with(|c| {
7193        let v = c.get();
7194        c.set(v + 1);
7195        v
7196    })
7197}
7198
7199/// Desugar `Inspect target: When Variant(field→bind): body … [Otherwise: rest]` into the flat `CIf`
7200/// chain `encode_stmts_src` produces, byte-for-byte. Each variant arm →
7201/// `CIf(target's __tag == "Variant", [<set match flag if Otherwise>; binds; body], [])`; when the PE
7202/// knows the variant it folds the comparison and keeps only the matching arm (zero dispatch =
7203/// Jones-optimal). WITH an `Otherwise`, a `__inspectMatched_N` flag (N = [`next_inspect_otherwise_idx`],
7204/// deterministic in both encoders) is declared false, set true in each arm, and checked negated to run
7205/// the Otherwise body. An `Otherwise`-only inspect just inlines that body.
7206fn inspect_to_cif<S: CoreSink>(stmt: &Stmt, out: &mut Vec<S::Val>, interner: &Interner, vctors: &VCtors) -> Option<()> {
7207    let Stmt::Inspect { target, arms, .. } = stmt else {
7208        return None;
7209    };
7210    let has_otherwise = arms.iter().any(|a| a.variant.is_none());
7211    let has_variant = arms.iter().any(|a| a.variant.is_some());
7212
7213    // No variant arms (empty or Otherwise-only): the reference inlines the Otherwise body directly.
7214    if !has_variant {
7215        for arm in arms {
7216            if arm.variant.is_none() {
7217                let body_refs: Vec<&Stmt> = arm.body.iter().collect();
7218                out.extend(stmt_list_to_vec::<S>(&body_refs, interner, vctors)?);
7219            }
7220        }
7221        return Some(());
7222    }
7223
7224    // The match flag is created (and its index reserved) BEFORE the arms — so a nested Inspect in an
7225    // arm/otherwise body gets a strictly higher index, exactly as the reference does.
7226    let matched_name = if has_otherwise {
7227        let name = format!("__inspectMatched_{}", next_inspect_otherwise_idx());
7228        out.push(S::inductive(
7229            "CLet",
7230            vec![S::text(&name), S::inductive("CBool", vec![S::boolean(false)])],
7231        ));
7232        Some(name)
7233    } else {
7234        None
7235    };
7236
7237    for arm in arms {
7238        if arm.variant.is_none() {
7239            continue;
7240        }
7241        let variant_name = interner.resolve(arm.variant.unwrap());
7242        let tag_get = S::inductive(
7243            "CMapGet",
7244            vec![expr_to_core::<S>(target, interner, vctors)?, S::inductive("CText", vec![S::text("__tag")])],
7245        );
7246        let cond = S::inductive(
7247            "CBinOp",
7248            vec![S::text("=="), tag_get, S::inductive("CText", vec![S::text(variant_name)])],
7249        );
7250        let mut then_items: Vec<S::Val> = Vec::new();
7251        if let Some(ref mname) = matched_name {
7252            then_items.push(S::inductive(
7253                "CSet",
7254                vec![S::text(mname), S::inductive("CBool", vec![S::boolean(true)])],
7255            ));
7256        }
7257        for (field_name, binding_name) in &arm.bindings {
7258            let field_str = interner.resolve(*field_name);
7259            let bind_str = interner.resolve(*binding_name);
7260            let fget = S::inductive(
7261                "CMapGet",
7262                vec![expr_to_core::<S>(target, interner, vctors)?, S::inductive("CText", vec![S::text(field_str)])],
7263            );
7264            then_items.push(S::inductive("CLet", vec![S::text(bind_str), fget]));
7265        }
7266        let body_refs: Vec<&Stmt> = arm.body.iter().collect();
7267        then_items.extend(stmt_list_to_vec::<S>(&body_refs, interner, vctors)?);
7268        out.push(S::inductive("CIf", vec![cond, S::list(then_items), S::list(vec![])]));
7269    }
7270
7271    // Otherwise: `CIf(not __inspectMatched_N, otherwise_body, [])`.
7272    if let Some(ref mname) = matched_name {
7273        let not_matched = S::inductive("CNot", vec![S::inductive("CVar", vec![S::text(mname)])]);
7274        let mut otherwise_body: Vec<&Stmt> = Vec::new();
7275        for arm in arms {
7276            if arm.variant.is_none() {
7277                otherwise_body = arm.body.iter().collect();
7278            }
7279        }
7280        let ow = stmt_list_to_core::<S>(&otherwise_body, interner, vctors)?;
7281        out.push(S::inductive("CIf", vec![not_matched, ow, S::list(vec![])]));
7282    }
7283    Some(())
7284}
7285
7286/// Whether the native Core-IR value builder covers `program` (the fast marshal path is taken, not
7287/// the interpreter fallback). For test coverage assertions.
7288pub fn program_covered_by_native_builder(program: &str) -> bool {
7289    program_to_core_value(program).is_some()
7290}
7291
7292/// Build the whole `CProgram` value for `program` on the [`TreeSink`] backend, or `None` if it uses
7293/// any construct the native builder does not cover (caller falls back to the interpreter path).
7294fn program_to_core_value(program: &str) -> Option<RuntimeValue> {
7295    program_to_core::<TreeSink>(program)
7296}
7297
7298/// Parse `program` and construct its Core-IR `CProg` on the sink `S` — the shared parse+build spine
7299/// behind both the `RuntimeValue` builder ([`program_to_core_value`], `S = TreeSink`) and the direct
7300/// single-pass wire marshal ([`program_to_core_wire_bytes`], `S = WireSink`). `None` if any construct
7301/// is uncovered. The parse (lex + discovery + `parse_program`) dominates; the sink chooses whether the
7302/// build allocates a `RuntimeValue` tree + a second encode walk or emits bytes in one pass.
7303fn program_to_core<S: CoreSink>(program: &str) -> Option<S::Val> {
7304    reset_inspect_otherwise_idx(); // match the reference's deterministic `__inspectMatched_N` numbering
7305    let full_source = if program.contains("## Main") || program.contains("## To ") {
7306        program.to_string()
7307    } else {
7308        format!("## Main\n{}", program)
7309    };
7310    let mut interner = Interner::new();
7311    let mut lexer = Lexer::new(&full_source, &mut interner);
7312    let tokens = lexer.tokenize();
7313    let type_registry = {
7314        let mut discovery = DiscoveryPass::new(&tokens, &mut interner);
7315        discovery.run_full().types
7316    };
7317    let mut vctors: VCtors = std::collections::HashMap::new();
7318    for (_t, def) in type_registry.iter_types() {
7319        if let crate::analysis::TypeDef::Enum { variants, .. } = def {
7320            for v in variants {
7321                let fnames: Vec<String> = v.fields.iter().map(|f| interner.resolve(f.name).to_string()).collect();
7322                vctors.insert(interner.resolve(v.name).to_string(), fnames);
7323            }
7324        }
7325    }
7326    let mut world_state = WorldState::new();
7327    let expr_arena = Arena::new();
7328    let term_arena = Arena::new();
7329    let np_arena = Arena::new();
7330    let sym_arena = Arena::new();
7331    let role_arena = Arena::new();
7332    let pp_arena = Arena::new();
7333    let stmt_arena: Arena<Stmt> = Arena::new();
7334    let imperative_expr_arena: Arena<Expr> = Arena::new();
7335    let type_expr_arena: Arena<TypeExpr> = Arena::new();
7336    let ast_ctx = AstContext::with_types(
7337        &expr_arena, &term_arena, &np_arena, &sym_arena, &role_arena, &pp_arena,
7338        &stmt_arena, &imperative_expr_arena, &type_expr_arena,
7339    );
7340    let mut parser = crate::parser::Parser::new(tokens, &mut world_state, &mut interner, ast_ctx, type_registry);
7341    let stmts = parser.parse_program().ok()?;
7342    build_core::<S>(&stmts, &interner, &vctors)
7343}
7344
7345/// Construct the Core-IR `CProg` for an ALREADY-PARSED program on the sink `S`: partition top-level
7346/// statements into `## To` functions (`CFuncDef`) and the main block, then emit `CProg [funcs] main`.
7347/// This is the parse-free spine — an integrated caller that already holds the AST + interner + variant
7348/// table skips the ~90%-of-marshal parse entirely by calling [`stmts_to_core_wire_bytes`]. `None` if
7349/// any statement uses a construct the native builder does not cover.
7350fn build_core<S: CoreSink>(stmts: &[Stmt], interner: &Interner, vctors: &VCtors) -> Option<S::Val> {
7351    let mut funcs: Vec<S::Val> = Vec::new();
7352    let mut main_stmts: Vec<&Stmt> = Vec::new();
7353    for stmt in stmts {
7354        if let Stmt::FunctionDef { name, params, body, return_type, is_native, .. } = stmt {
7355            if *is_native {
7356                continue;
7357            }
7358            let fname = interner.resolve(*name).to_string();
7359            let pnames: Vec<S::Val> = params.iter().map(|(n, _)| S::text(interner.resolve(*n))).collect();
7360            let ptypes: Vec<S::Val> = params.iter().map(|(_, ty)| S::text(&decompile_type_expr(ty, interner))).collect();
7361            let ret = return_type.map(|rt| decompile_type_expr(rt, interner)).unwrap_or_else(|| "Nothing".to_string());
7362            let body_s: Vec<&Stmt> = body.iter().collect();
7363            let body_v = stmt_list_to_core::<S>(&body_s, interner, vctors)?;
7364            funcs.push(S::inductive("CFuncDef", vec![S::text(&fname), S::list(pnames), S::list(ptypes), S::text(&ret), body_v]));
7365        } else if matches!(
7366            stmt,
7367            Stmt::StructDef { .. } | Stmt::Theorem(_) | Stmt::Definition(_) | Stmt::Axiom(_) | Stmt::Theory(_)
7368        ) {
7369            // Declarations contribute NOTHING to the runtime statement list — they live in the type
7370            // catalog / proof layer, and the reference encoder drops them. Skipping them here (rather
7371            // than routing to `stmt_to_core` → `None` → fallback) lets programs that carry an enum /
7372            // `Shared` struct (the ones that use Inspect, CRDT counters, typed closures) still hit the
7373            // fast native builder. Byte-identical: the reference likewise emits nothing for these.
7374        } else {
7375            main_stmts.push(stmt);
7376        }
7377    }
7378    let main_v = stmt_list_to_core::<S>(&main_stmts, interner, vctors)?;
7379    Some(S::inductive("CProg", vec![S::list(funcs), main_v]))
7380}
7381
7382/// Serialize a program's Core-IR `CProgram` (a `CProg` with its functions and main block) to the
7383/// plain wire form — the bytes a compile-once native partial evaluator reads on stdin. FAST PATH:
7384/// [`program_to_core`] on [`WireSink`] emits the wire bytes in ONE pass straight from the AST — no
7385/// intermediate `RuntimeValue` tree and no second `encode_value_raw` walk (byte-for-byte identical to
7386/// the old two-pass form; locked by `native_builder_is_byte_identical_to_the_interpreter`). FALLBACK:
7387/// for a construct the native builder does not cover, run the constructor source on the tree-walker
7388/// (`wireBytes`). Both are decoded identically by the native binary's generated `CProgram::wire_decode`.
7389pub fn program_to_core_wire_bytes(program: &str) -> Result<Vec<u8>, String> {
7390    if let Some(bytes) = program_to_core::<WireSink>(program) {
7391        return Ok(bytes);
7392    }
7393    program_to_core_wire_bytes_via_interpreter(program)
7394}
7395
7396/// The PARSE-FREE marshal: emit a program's Core-IR wire bytes directly from an ALREADY-PARSED AST,
7397/// skipping the lex + discovery + `parse_program` that dominates [`program_to_core_wire_bytes`] (~90%
7398/// of a specialization). An integrated caller that parsed the program once for compilation reuses that
7399/// AST here to feed the native PE at a fraction of the cost. `variant_fields` maps each variant
7400/// constructor name to its ordered field names (the caller derives it from its type registry, as
7401/// [`program_to_core`] does). `None` if a construct is uncovered. Byte-identical to
7402/// [`program_to_core_wire_bytes`] on the same program — same [`build_core`]/[`WireSink`] spine.
7403pub fn stmts_to_core_wire_bytes(
7404    stmts: &[Stmt],
7405    interner: &Interner,
7406    variant_fields: &std::collections::HashMap<String, Vec<String>>,
7407) -> Option<Vec<u8>> {
7408    build_core::<WireSink>(stmts, interner, variant_fields)
7409}
7410
7411/// The TWO-PASS marshal that [`program_to_core_wire_bytes`] replaced: build the full `RuntimeValue`
7412/// Core-IR tree ([`TreeSink`]) then `encode_value_raw` it. Kept as the correctness ORACLE and the
7413/// speed BASELINE for the single-pass ([`WireSink`]) form — the two are byte-identical by construction
7414/// (`WireSink` emits exactly what `encode_value_raw` would), and the single pass avoids the tree
7415/// allocation + the second encode walk. `None` if a construct is uncovered.
7416pub fn program_to_core_wire_bytes_two_pass(program: &str) -> Option<Vec<u8>> {
7417    program_to_core::<TreeSink>(program).and_then(|v| encode_value_raw(&v).ok())
7418}
7419
7420/// The REFERENCE marshal: always the interpreter path (construct the `CProgram` via the tree-walker,
7421/// `wireBytes`-serialize it). The native builder's output must be BYTE-IDENTICAL to this — locked by
7422/// `native_pe_wire::native_builder_is_byte_identical_to_the_interpreter`. Kept public so the lock can
7423/// force the reference regardless of native-builder coverage.
7424pub fn program_to_core_wire_bytes_via_interpreter(program: &str) -> Result<Vec<u8>, String> {
7425    let full_source = if program.contains("## Main") || program.contains("## To ") {
7426        program.to_string()
7427    } else {
7428        format!("## Main\n{}", program)
7429    };
7430    let encoded = encode_program_source(&full_source).map_err(|e| format!("Failed to encode program: {:?}", e))?;
7431    let driver = "    Let prog be a new CProg with funcs encodedFuncSeq and main encodedMain.\n    Let wb be wireBytes(prog).\n    Repeat for b in wb:\n        Show \"{b}\".\n";
7432    let combined = format!("{}\n## Main\n{}\n{}", CORE_TYPES_FOR_PE, encoded, driver);
7433    let out = interpret_program(&combined).map_err(|e| format!("wire-encode run failed: {:?}", e))?;
7434    let mut bytes = Vec::new();
7435    for line in out.lines() {
7436        let t = line.trim();
7437        if t.is_empty() {
7438            continue;
7439        }
7440        let n: u16 = t.parse().map_err(|_| format!("wireBytes emitted a non-byte line: {:?}", t))?;
7441        bytes.push(n as u8);
7442    }
7443    Ok(bytes)
7444}
7445
7446/// Build (once, content-addressed) the compile-once native PE binary and return its path. Keyed
7447/// on the generated Rust of [`pe_native_source`] + toolchain, so the one-time rustc compile of the
7448/// whole PE engine happens exactly once per PE version and is reused across every program and
7449/// session. This is the amortization that turns AOT from "recompile per program" into the 210x win.
7450#[cfg(not(target_arch = "wasm32"))]
7451thread_local! {
7452    /// The already-built native PE binary path for this process. Caching it avoids re-running
7453    /// `aot_cache_key` on every specialization — which spawns `rustc --version` (~15 ms) and
7454    /// re-hashes the 167 KB source. The path is stable for the process (the PE source doesn't
7455    /// change at runtime), so one lookup per process suffices.
7456    static NATIVE_PE_BIN: std::cell::RefCell<Option<std::path::PathBuf>> = const { std::cell::RefCell::new(None) };
7457}
7458
7459#[cfg(not(target_arch = "wasm32"))]
7460pub fn build_native_pe_binary() -> Result<std::path::PathBuf, String> {
7461    // Warm path: the binary was already located/built this process — reuse it (no `rustc --version`
7462    // spawn, no re-hash) as long as it still exists on disk.
7463    if let Some(p) = NATIVE_PE_BIN.with(|c| c.borrow().clone()) {
7464        if p.exists() {
7465            return Ok(p);
7466        }
7467    }
7468    let src = pe_native_source();
7469    // Key on the (stable) PE SOURCE text + toolchain — NOT the generated Rust: computing the Rust
7470    // means recompiling the whole 167 KB engine, which would defeat the cache on every warm call.
7471    let key = aot_cache_key(&src);
7472    let base = std::env::temp_dir().join("logos_native_pe");
7473    std::fs::create_dir_all(&base).map_err(|e| e.to_string())?;
7474    let proj = base.join(format!("pe_{key}"));
7475    let bin = proj.join("target").join("release").join("logos_native_pe");
7476    if bin.exists() {
7477        NATIVE_PE_BIN.with(|c| *c.borrow_mut() = Some(bin.clone()));
7478        return Ok(bin);
7479    }
7480    let rust = compile_program_full_with_wire(&src)
7481        .map_err(|e| format!("native PE compile: {:?}", e))?
7482        .rust_code;
7483    std::fs::create_dir_all(proj.join("src")).map_err(|e| e.to_string())?;
7484    let manifest = std::path::Path::new(env!("CARGO_MANIFEST_DIR"));
7485    let root = manifest.parent().unwrap().parent().unwrap();
7486    std::fs::write(
7487        proj.join("Cargo.toml"),
7488        format!(
7489            "[package]\nname=\"logos_native_pe\"\nversion=\"0.1.0\"\nedition=\"2021\"\n[dependencies]\nlogicaffeine-data={{path=\"{r}/crates/logicaffeine_data\"}}\nlogicaffeine-system={{path=\"{r}/crates/logicaffeine_system\",features=[\"full\"]}}\ntokio={{version=\"1\",features=[\"rt-multi-thread\",\"macros\"]}}\nserde={{version=\"1\",features=[\"derive\"]}}\nrayon=\"1\"\n[profile.release]\nopt-level=3\n",
7490            r = root.display()
7491        ),
7492    )
7493    .map_err(|e| e.to_string())?;
7494    std::fs::write(proj.join("src/main.rs"), &rust).map_err(|e| e.to_string())?;
7495    std::fs::copy(root.join("Cargo.lock"), proj.join("Cargo.lock")).map_err(|e| e.to_string())?;
7496    let out = std::process::Command::new("cargo")
7497        .args(["build", "--release", "--quiet"])
7498        .current_dir(&proj)
7499        .env("CARGO_TARGET_DIR", proj.join("target"))
7500        .env("RUST_MIN_STACK", "268435456")
7501        .output()
7502        .map_err(|e| e.to_string())?;
7503    if !out.status.success() {
7504        return Err(format!(
7505            "native PE build failed:\n{}",
7506            String::from_utf8_lossy(&out.stderr)
7507        ));
7508    }
7509    if bin.exists() {
7510        NATIVE_PE_BIN.with(|c| *c.borrow_mut() = Some(bin.clone()));
7511        Ok(bin)
7512    } else {
7513        Err("native PE binary missing after a successful build".into())
7514    }
7515}
7516
7517/// Specialize `program` on the compile-once native PE via the RESIDENT SERVER (the fallback path;
7518/// the default [`run_native_pe`] prefers the faster in-process cdylib): serialize it to wire bytes
7519/// ([`program_to_core_wire_bytes`]), stream them to the server, and return its Jones-optimal
7520/// residual. The server (the compiled PE binary) is spawned ONCE per thread and reused — length-
7521/// framed request/response over its stdin/stdout — so there is no per-call process spawn and no
7522/// per-call rustc: just the host marshal + native `peBlock`, plus one pipe round-trip.
7523///
7524/// The server persists in a thread-local; dropping it (thread end / on I/O error, which triggers a
7525/// respawn) closes its stdin, and `readWireProgram` exits the child cleanly on that EOF.
7526#[cfg(not(target_arch = "wasm32"))]
7527struct PeServer {
7528    child: std::process::Child,
7529    stdin: std::process::ChildStdin,
7530    stdout: std::io::BufReader<std::process::ChildStdout>,
7531}
7532
7533#[cfg(not(target_arch = "wasm32"))]
7534impl Drop for PeServer {
7535    fn drop(&mut self) {
7536        // Reap the child so a long-lived host (or the test harness) never accumulates zombies.
7537        let _ = self.child.kill();
7538        let _ = self.child.wait();
7539    }
7540}
7541
7542#[cfg(not(target_arch = "wasm32"))]
7543thread_local! {
7544    static PE_SERVER: std::cell::RefCell<Option<PeServer>> = const { std::cell::RefCell::new(None) };
7545}
7546
7547#[cfg(not(target_arch = "wasm32"))]
7548pub fn run_native_pe_server(program: &str) -> Result<String, String> {
7549    use std::io::{Read, Write};
7550    let bytes = program_to_core_wire_bytes(program)?;
7551    let raw = PE_SERVER.with(|cell| -> Result<String, String> {
7552        // A single request/response over the resident server; on ANY I/O failure the server is
7553        // dropped (respawned next call) so a crashed child never wedges the pipeline.
7554        let attempt = |slot: &mut Option<PeServer>| -> Result<String, String> {
7555            if slot.is_none() {
7556                let bin = build_native_pe_binary()?;
7557                let mut child = std::process::Command::new(&bin)
7558                    .stdin(std::process::Stdio::piped())
7559                    .stdout(std::process::Stdio::piped())
7560                    .stderr(std::process::Stdio::null())
7561                    .env("RUST_MIN_STACK", "268435456")
7562                    .spawn()
7563                    .map_err(|e| format!("native PE server spawn: {e}"))?;
7564                let stdin = child.stdin.take().unwrap();
7565                let stdout = std::io::BufReader::new(child.stdout.take().unwrap());
7566                *slot = Some(PeServer { child, stdin, stdout });
7567            }
7568            let srv = slot.as_mut().unwrap();
7569            srv.stdin
7570                .write_all(&(bytes.len() as u32).to_le_bytes())
7571                .and_then(|_| srv.stdin.write_all(&bytes))
7572                .and_then(|_| srv.stdin.flush())
7573                .map_err(|e| format!("native PE server write: {e}"))?;
7574            let mut len = [0u8; 4];
7575            srv.stdout
7576                .read_exact(&mut len)
7577                .map_err(|e| format!("native PE server read length: {e}"))?;
7578            let n = u32::from_le_bytes(len) as usize;
7579            let mut buf = vec![0u8; n];
7580            srv.stdout
7581                .read_exact(&mut buf)
7582                .map_err(|e| format!("native PE server read residual: {e}"))?;
7583            String::from_utf8(buf).map_err(|e| format!("native PE residual not UTF-8: {e}"))
7584        };
7585        let mut slot = cell.borrow_mut();
7586        match attempt(&mut slot) {
7587            Ok(v) => Ok(v),
7588            Err(e) => {
7589                *slot = None; // drop the (possibly dead) server so the next call respawns
7590                Err(e)
7591            }
7592        }
7593    })?;
7594    let residual = finish_projection1_residual(raw)?;
7595    Ok(prepend_type_definitions(program, residual))
7596}
7597
7598#[cfg(not(target_arch = "wasm32"))]
7599thread_local! {
7600    /// The already-built PE cdylib path for this process (see [`NATIVE_PE_BIN`]).
7601    static NATIVE_PE_CDYLIB: std::cell::RefCell<Option<std::path::PathBuf>> = const { std::cell::RefCell::new(None) };
7602}
7603
7604/// The in-process FFI shim appended to the generated PE engine to form the cdylib's `lib.rs`. It
7605/// lives in the SAME crate-root module as the generated `pub enum CProgram` and the module-private
7606/// `fn peSpecializeOnce(CProgram) -> String`, so it can call both directly. `into_boxed_slice` gives
7607/// an allocation with capacity EXACTLY == len, so `logos_pe_free` reconstructs the identical
7608/// `Box<[u8]>` (reconstructing a `Vec` with cap==len would be UB if the allocator over-allocated).
7609#[cfg(not(target_arch = "wasm32"))]
7610const PE_CDYLIB_SHIM: &str = r#"
7611
7612// ── in-process FFI entry (appended by build_native_pe_cdylib) ───────────────────────────────
7613#[no_mangle]
7614pub extern "C" fn logos_pe_specialize(ptr: *const u8, len: usize, out_len: *mut usize) -> *mut u8 {
7615    let bytes = unsafe { std::slice::from_raw_parts(ptr, len) };
7616    let prog = <CProgram as logicaffeine_data::wire::WireDecode>::wire_decode(bytes, &mut 0usize)
7617        .expect("logos_pe_specialize: decode CProgram");
7618    let out: String = peSpecializeOnce(prog);
7619    let boxed: Box<[u8]> = out.into_bytes().into_boxed_slice();
7620    let n = boxed.len();
7621    unsafe { *out_len = n; }
7622    Box::into_raw(boxed) as *mut u8
7623}
7624
7625#[no_mangle]
7626pub extern "C" fn logos_pe_free(ptr: *mut u8, len: usize) {
7627    if !ptr.is_null() {
7628        unsafe { drop(Box::from_raw(std::slice::from_raw_parts_mut(ptr, len) as *mut [u8])); }
7629    }
7630}
7631"#;
7632
7633/// Build (and cache) the PE compiled as an IN-PROCESS cdylib exporting `logos_pe_specialize`.
7634/// Mirrors [`build_native_pe_binary`] but emits a `cdylib` whose `lib.rs` is the generated PE engine
7635/// ([`pe_cdylib_source`]) plus a hand-written `extern "C"` shim ([`PE_CDYLIB_SHIM`]) that decodes the
7636/// wire `CProgram` and calls `peSpecializeOnce` directly — so the caller loads it once and
7637/// specializes with a plain function call, no process, no pipe. Content-addressed on the (stable)
7638/// cdylib PE source + shim + toolchain, so warm calls skip rustc.
7639#[cfg(not(target_arch = "wasm32"))]
7640pub fn build_native_pe_cdylib() -> Result<std::path::PathBuf, String> {
7641    if let Some(p) = NATIVE_PE_CDYLIB.with(|c| c.borrow().clone()) {
7642        if p.exists() {
7643            return Ok(p);
7644        }
7645    }
7646    let src = pe_cdylib_source();
7647    // Key on the PE source AND the FFI shim, so editing either invalidates the on-disk cache.
7648    let key = aot_cache_key(&format!("{src}\n{PE_CDYLIB_SHIM}"));
7649    let base = std::env::temp_dir().join("logos_native_pe_cdylib");
7650    std::fs::create_dir_all(&base).map_err(|e| e.to_string())?;
7651    let proj = base.join(format!("pe_{key}"));
7652    let so = proj.join("target").join("release").join(format!(
7653        "{}logos_native_pe_lib{}",
7654        std::env::consts::DLL_PREFIX,
7655        std::env::consts::DLL_SUFFIX
7656    ));
7657    if so.exists() {
7658        NATIVE_PE_CDYLIB.with(|c| *c.borrow_mut() = Some(so.clone()));
7659        return Ok(so);
7660    }
7661    let generated = compile_program_full_with_wire(&src)
7662        .map_err(|e| format!("native PE cdylib compile: {:?}", e))?
7663        .rust_code;
7664    let lib_rs = format!("{generated}\n{PE_CDYLIB_SHIM}");
7665    std::fs::create_dir_all(proj.join("src")).map_err(|e| e.to_string())?;
7666    let manifest = std::path::Path::new(env!("CARGO_MANIFEST_DIR"));
7667    let root = manifest.parent().unwrap().parent().unwrap();
7668    std::fs::write(
7669        proj.join("Cargo.toml"),
7670        format!(
7671            "[package]\nname=\"logos_native_pe_lib\"\nversion=\"0.1.0\"\nedition=\"2021\"\n[lib]\ncrate-type=[\"cdylib\"]\npath=\"src/lib.rs\"\n[dependencies]\nlogicaffeine-data={{path=\"{r}/crates/logicaffeine_data\"}}\nlogicaffeine-system={{path=\"{r}/crates/logicaffeine_system\",features=[\"full\"]}}\ntokio={{version=\"1\",features=[\"rt-multi-thread\",\"macros\"]}}\nserde={{version=\"1\",features=[\"derive\"]}}\nrayon=\"1\"\n[profile.release]\nopt-level=3\n",
7672            r = root.display()
7673        ),
7674    )
7675    .map_err(|e| e.to_string())?;
7676    std::fs::write(proj.join("src/lib.rs"), &lib_rs).map_err(|e| e.to_string())?;
7677    std::fs::copy(root.join("Cargo.lock"), proj.join("Cargo.lock")).map_err(|e| e.to_string())?;
7678    let out = std::process::Command::new("cargo")
7679        .args(["build", "--release", "--quiet"])
7680        .current_dir(&proj)
7681        .env("CARGO_TARGET_DIR", proj.join("target"))
7682        .env("RUST_MIN_STACK", "268435456")
7683        .output()
7684        .map_err(|e| e.to_string())?;
7685    if !out.status.success() {
7686        return Err(format!(
7687            "native PE cdylib build failed:\n{}",
7688            String::from_utf8_lossy(&out.stderr)
7689        ));
7690    }
7691    if so.exists() {
7692        NATIVE_PE_CDYLIB.with(|c| *c.borrow_mut() = Some(so.clone()));
7693        Ok(so)
7694    } else {
7695        Err("native PE cdylib missing after a successful build".into())
7696    }
7697}
7698
7699/// The loaded PE cdylib for this thread: the `Library` kept alive alongside the raw entry-point
7700/// pointers derived from it (valid for as long as the `Library` is not unloaded, which is exactly
7701/// this struct's lifetime).
7702#[cfg(not(target_arch = "wasm32"))]
7703struct PeLib {
7704    _lib: libloading::Library,
7705    specialize: unsafe extern "C" fn(*const u8, usize, *mut usize) -> *mut u8,
7706    free: unsafe extern "C" fn(*mut u8, usize),
7707}
7708
7709#[cfg(not(target_arch = "wasm32"))]
7710thread_local! {
7711    static PE_LIB: std::cell::RefCell<Option<PeLib>> = const { std::cell::RefCell::new(None) };
7712}
7713
7714/// Specialize `program` on the compile-once native PE, IN PROCESS: marshal it to wire bytes, hand
7715/// them to the `dlopen`ed PE cdylib's `logos_pe_specialize` as a plain function call, and reclaim
7716/// the residual bytes. No child process, no pipe — the ~59 µs IPC round-trip of the resident server
7717/// is gone; all that remains is the host marshal + native `peBlock` + one FFI call. The cdylib is
7718/// built + loaded ONCE per thread and cached. Residual-identity with the server / tree-walker is
7719/// locked by `native_pe_wire`.
7720#[cfg(not(target_arch = "wasm32"))]
7721pub fn run_native_pe_inprocess(program: &str) -> Result<String, String> {
7722    let bytes = program_to_core_wire_bytes(program)?;
7723    let raw = PE_LIB.with(|cell| -> Result<String, String> {
7724        let mut slot = cell.borrow_mut();
7725        if slot.is_none() {
7726            let so = build_native_pe_cdylib()?;
7727            // SAFETY: the cdylib was just built from our own generated source; its exported symbols
7728            // have exactly the signatures declared here, and the raw pointers are stored alongside
7729            // the owning `Library` so they never outlive it.
7730            unsafe {
7731                let lib = libloading::Library::new(&so)
7732                    .map_err(|e| format!("native PE cdylib load: {e}"))?;
7733                let specialize: libloading::Symbol<
7734                    unsafe extern "C" fn(*const u8, usize, *mut usize) -> *mut u8,
7735                > = lib
7736                    .get(b"logos_pe_specialize")
7737                    .map_err(|e| format!("dlsym logos_pe_specialize: {e}"))?;
7738                let free: libloading::Symbol<unsafe extern "C" fn(*mut u8, usize)> = lib
7739                    .get(b"logos_pe_free")
7740                    .map_err(|e| format!("dlsym logos_pe_free: {e}"))?;
7741                let specialize = *specialize;
7742                let free = *free;
7743                *slot = Some(PeLib { _lib: lib, specialize, free });
7744            }
7745        }
7746        let pl = slot.as_ref().unwrap();
7747        let mut out_len: usize = 0;
7748        // SAFETY: `bytes` is a live slice for the duration of the call; the callee writes `out_len`
7749        // and returns a buffer of that length we own until `free`.
7750        let p = unsafe { (pl.specialize)(bytes.as_ptr(), bytes.len(), &mut out_len as *mut usize) };
7751        if p.is_null() {
7752            return Err("native PE cdylib returned a null residual".into());
7753        }
7754        let residual_bytes = unsafe { std::slice::from_raw_parts(p, out_len).to_vec() };
7755        unsafe { (pl.free)(p, out_len) };
7756        String::from_utf8(residual_bytes).map_err(|e| format!("native PE residual not UTF-8: {e}"))
7757    })?;
7758    let residual = finish_projection1_residual(raw)?;
7759    Ok(prepend_type_definitions(program, residual))
7760}
7761
7762/// Specialize `program` on the compile-once native PE — the DEFAULT entry and the pit of success.
7763/// Prefers the IN-PROCESS cdylib path ([`run_native_pe_inprocess`]; no process/pipe boundary) and
7764/// falls back to the resident server ([`run_native_pe_server`]) only if the in-process build/load
7765/// fails (e.g. no toolchain), so the fast+correct route is what every caller gets by default. The
7766/// two paths are proven byte-identical by `native_pe_wire`.
7767#[cfg(not(target_arch = "wasm32"))]
7768pub fn run_native_pe(program: &str) -> Result<String, String> {
7769    match run_native_pe_inprocess(program) {
7770        Ok(v) => Ok(v),
7771        Err(_) => run_native_pe_server(program),
7772    }
7773}
7774
7775/// VM-driven twin of [`projection1_source_real_fast`].
7776///
7777/// Executes the identical assembled PE program on the register bytecode VM
7778/// ([`vm_run_source`]) instead of the tree-walker. The PE engine is a fixed program —
7779/// only its `encodedMain` input varies per call — so the VM either supports the whole
7780/// engine or none of it; measured ~2× faster than the tree-walker with a byte-identical
7781/// residual. The corpus-wide equivalence is locked by `futamura_tier_lock`.
7782pub fn projection1_source_real_fast_on_vm(
7783    core_types: &str,
7784    _interpreter: &str,
7785    program: &str,
7786) -> Result<String, String> {
7787    let combined = pe_combined_source(core_types, program)?;
7788
7789    let raw_residual = vm_run_source(&combined).map_err(|e| format!("PE VM execution failed: {e}"))?;
7790    let residual = finish_projection1_residual(raw_residual)?;
7791    Ok(prepend_type_definitions(program, residual))
7792}
7793
7794/// Re-attach every original `## …` section that is NOT `## Main` and NOT a `## To` function
7795/// (the residual supplies its own Main + specialized functions). These are the type / struct
7796/// / policy definitions that make the residual a self-contained, re-runnable program.
7797fn prepend_type_definitions(program: &str, residual: String) -> String {
7798    let mut defs = String::new();
7799    let mut keep = false;
7800    for line in program.lines() {
7801        if let Some(header) = line.strip_prefix("## ") {
7802            keep = header != "Main" && !header.starts_with("To ");
7803        }
7804        if keep {
7805            defs.push_str(line);
7806            defs.push('\n');
7807        }
7808    }
7809    if defs.trim().is_empty() {
7810        residual
7811    } else {
7812        format!("{}{}", defs, residual)
7813    }
7814}
7815
7816/// Assemble the full PE-run source (core types + PE + decompiler + encoded
7817/// input + driver) — the exact program [`projection1_source_real_fast`]
7818/// executes. Public so differential harnesses can run it on a specific engine.
7819pub fn pe_combined_source(core_types: &str, program: &str) -> Result<String, String> {
7820    let full_source = if program.contains("## Main") || program.contains("## To ") {
7821        program.to_string()
7822    } else {
7823        format!("## Main\n{}", program)
7824    };
7825
7826    let encoded = encode_program_source(&full_source)
7827        .map_err(|e| format!("Failed to encode program: {:?}", e))?;
7828
7829    let pe_source = pe_source_text();
7830    let decompile_source = decompile_source_text();
7831
7832    let actual_core_types = if core_types.is_empty() { CORE_TYPES_FOR_PE } else { core_types };
7833
7834    // Driver: run PE, then transitively decompile every referenced function (specialized
7835    // or original) so residual calls — e.g. an MSG-generalized recursive loop — resolve.
7836    // For fully-inlined residuals (no calls) this emits nothing and matches the bare form.
7837    let driver = r#"
7838    Let state be makePeState(a new Map of Text to CVal, encodedFuncMap, 200).
7839    Let residual be peBlock(encodedMain, state).
7840    Let nl be chr(10).
7841    Let mutable output be "".
7842    Let specFuncs be peFuncs(state).
7843    Let mutable allNames be collectCallNames(residual).
7844    Let mutable emitted be a new Map of Text to Bool.
7845    Let mutable changed be true.
7846    While changed:
7847        Set changed to false.
7848        Let mutable toAdd be a new Seq of Text.
7849        Repeat for fnKey in allNames:
7850            Let fkStr be "{fnKey}".
7851            If emitted contains fkStr:
7852                Let skipE be true.
7853            Otherwise:
7854                Set item fkStr of emitted to true.
7855                Let fkStr2 be "{fnKey}".
7856                If specFuncs contains fkStr2:
7857                    Let fdef be item fkStr2 of specFuncs.
7858                    Inspect fdef:
7859                        When CFuncDef (fn0, ps0, pt0, rt0, body0):
7860                            Let children be collectCallNames(body0).
7861                            Repeat for child in children:
7862                                Let childStr be "{child}".
7863                                If not emitted contains childStr:
7864                                    Push child to toAdd.
7865                                    Set changed to true.
7866                        Otherwise:
7867                            Let skipF be true.
7868        Repeat for ta in toAdd:
7869            Push ta to allNames.
7870    Repeat for fnKey in allNames:
7871        Let fkStr be "{fnKey}".
7872        If specFuncs contains fkStr:
7873            Let fdef be item fkStr of specFuncs.
7874            Let funcSrc be decompileFunc(fdef).
7875            If the length of funcSrc is greater than 0:
7876                Set output to "{output}{funcSrc}{nl}".
7877    Let mainSrc be decompileBlock(residual, 0).
7878    Set output to "{output}## Main{nl}{mainSrc}".
7879    Show output.
7880"#;
7881
7882    let combined = format!(
7883        "{}\n{}\n{}\n## Main\n{}\n{}",
7884        actual_core_types,
7885        pe_source,
7886        decompile_source,
7887        encoded,
7888        driver,
7889    );
7890
7891    Ok(combined)
7892}
7893
7894/// The post-processing applied to a raw PE residual (the `Any`-type safety
7895/// net + `## Main` normalization).
7896pub fn finish_projection1_residual(raw_residual: String) -> Result<String, String> {
7897    // Safety net (mirrors `fix_decompiled_types` on the P2/P3 path): specialized functions
7898    // whose param/return types the PE could not propagate are emitted as `Any`, which the
7899    // parser rejects. The tree-walker is dynamically typed, so a concrete placeholder type
7900    // is cosmetic at runtime — rewrite `Any` annotations so the residual re-parses.
7901    let fixed = raw_residual
7902        .replace(": Any)", ": Int)")
7903        .replace("-> Any:", "-> Int:")
7904        .replace(" of Any", " of Int")
7905        .replace(" to Any", " to Int");
7906    let trimmed = fixed.trim();
7907
7908    if trimmed.is_empty() {
7909        return Ok("## Main\n".to_string());
7910    }
7911
7912    if trimmed.contains("## To ") || trimmed.starts_with("## Main") {
7913        Ok(trimmed.to_string())
7914    } else {
7915        Ok(format!("## Main\n{}", trimmed))
7916    }
7917}
7918
7919/// Run genuine P2 on a specific target: PE(pe_source, pe_mini(target))
7920///
7921/// This is the real Futamura Projection 2 applied end-to-end:
7922/// 1. Build pe_mini applied to the target (pe_mini compiles the target)
7923/// 2. Encode the combined pe_mini+target as CProgram data
7924/// 3. Run pe_source on the encoded data (PE specializes pe_mini for this target)
7925/// 4. Decompile the residual to LOGOS source
7926/// 5. Execute the decompiled residual to get the target's output
7927///
7928/// No decompilation of specialized functions — the PE directly produces the compiled
7929/// target as CStmt data, which is decompiled to a simple LOGOS program and run.
7930pub fn run_genuine_p2_on_target(program: &str, core_types: &str, interpreter: &str) -> Result<String, String> {
7931    let pe_mini = pe_mini_source_text();
7932    let pe = pe_source_text();
7933
7934    let full_source = if program.contains("## Main") || program.contains("## To ") {
7935        program.to_string()
7936    } else {
7937        format!("## Main\n{}", program)
7938    };
7939
7940    // Build pe_mini applied to the specific target.
7941    // pe_mini ONLY COMPILES the target — produces CStmt data as output.
7942    // Build pe_mini + interpreter applied to the target.
7943    // pe_mini compiles the target, coreExecBlock runs the compiled result.
7944    let target_encoded = encode_program_source(&full_source)
7945        .map_err(|e| format!("Failed to encode target: {:?}", e))?;
7946    let pe_mini_prog = format!(
7947        "{}\n{}\n{}\n## Main\n{}\n\
7948         Let compileEnv be a new Map of Text to CVal.\n\
7949         Let compileState be makePeState(compileEnv, encodedFuncMap, 200).\n\
7950         Let compiled be peBlockM(encodedMain, compileState).\n\
7951         Let runEnv be a new Map of Text to CVal.\n\
7952         coreExecBlock(compiled, runEnv, encodedFuncMap).\n",
7953        core_types, pe_mini, interpreter, target_encoded
7954    );
7955
7956    // Encode pe_mini+target as CProgram data for the outer PE
7957    let encoded = encode_program_source_compact(&pe_mini_prog)
7958        .map_err(|e| format!("Failed to encode pe_mini+target for P2: {:?}", e))?;
7959
7960    // The driver runs PE on the encoded pe_mini+target, then executes the residual.
7961    // peFuncs(state) contains BOTH the PE-specialized functions AND the original
7962    // functions from the encoded program (including pe_mini, interpreter, and
7963    // target functions like factorial).
7964    let driver = r#"    Let state be makePeState(a new Map of Text to CVal, encodedFuncMap, 500).
7965    Let residual be peBlock(encodedMain, state).
7966    Let allFuncs be peFuncs(state).
7967    Let runEnv be a new Map of Text to CVal.
7968    coreExecBlock(residual, runEnv, allFuncs).
7969"#;
7970    let combined = format!(
7971        "{}\n{}\n{}\n## Main\n{}\n{}",
7972        CORE_TYPES_FOR_PE, pe, interpreter, encoded, driver
7973    );
7974
7975    run_logos_source(&combined)
7976}
7977
7978/// Run genuine P3 on a specific target: PE(pe_source, pe_bti(target))
7979pub fn run_genuine_p3_on_target(program: &str, core_types: &str, interpreter: &str) -> Result<String, String> {
7980    let pe_bti = pe_bti_source_text();
7981    let pe = pe_source_text();
7982
7983    let full_source = if program.contains("## Main") || program.contains("## To ") {
7984        program.to_string()
7985    } else {
7986        format!("## Main\n{}", program)
7987    };
7988
7989    let bti_types = CORE_TYPES_FOR_PE
7990        .replace("specResults", "memoCache")
7991        .replace("onStack", "callGuard");
7992
7993    let target_encoded = encode_program_source(&full_source)
7994        .map_err(|e| format!("Failed to encode target: {:?}", e))?;
7995    let pe_bti_prog = format!(
7996        "{}\n{}\n{}\n## Main\n{}\n\
7997         Let compileEnv be a new Map of Text to CVal.\n\
7998         Let compileState be makePeState(compileEnv, encodedFuncMap, 200).\n\
7999         Let compiled be peBlockB(encodedMain, compileState).\n\
8000         Let runEnv be a new Map of Text to CVal.\n\
8001         coreExecBlock(compiled, runEnv, encodedFuncMap).\n",
8002        bti_types, pe_bti, interpreter, target_encoded
8003    );
8004
8005    let encoded = encode_program_source_compact(&pe_bti_prog)
8006        .map_err(|e| format!("Failed to encode pe_bti+target for P3: {:?}", e))?;
8007
8008    // Execute residual directly — no decompilation needed
8009    let driver = r#"    Let state be makePeState(a new Map of Text to CVal, encodedFuncMap, 200).
8010    Let residual be peBlock(encodedMain, state).
8011    Let runEnv be a new Map of Text to CVal.
8012    coreExecBlock(residual, runEnv, encodedFuncMap).
8013"#;
8014    let combined = format!(
8015        "{}\n{}\n{}\n## Main\n{}\n{}",
8016        CORE_TYPES_FOR_PE, pe, interpreter, encoded, driver
8017    );
8018
8019    run_logos_source(&combined)
8020}
8021
8022/// Genuine Futamura Projection 2 via self-application: PE(pe_source, pe_mini(targetStmts))
8023///
8024/// The outer PE (pe_source) specializes pe_mini's FULL BLOCK evaluator with known
8025/// state (empty env, empty funcs, depth 200). targetStmts is a free/dynamic variable;
8026/// state is fully static. The PE produces a specialized peBlockM_ function that IS
8027/// the compiler — handling all CStmt variants with specialized expression processing.
8028///
8029/// This eliminates the need for a Rust-generated block wrapper: the PE naturally
8030/// produces block-level dispatch through specialization of peBlockM.
8031pub fn projection2_source_real(_core_types: &str, _interpreter: &str) -> Result<GenuineProjectionResult, String> {
8032    let pe_mini = pe_mini_source_text();
8033    let pe = pe_source_text();
8034    let decompile = decompile_source_text();
8035
8036    // Build pe_mini program with peBlockM as entry — FULL block-level specialization.
8037    // targetStmts = free/dynamic variable (program's main block).
8038    // targetFuncs = free/dynamic variable (program's function definitions).
8039    // env = static (empty — programs start with no bindings).
8040    // depth = static (200 — PE eliminates depth checks).
8041    // targetStmts is a free/dynamic variable. State is fully static (empty env,
8042    // empty funcs, depth 200). The PE specializes pe_mini's dispatch code completely.
8043    // The resulting compiler handles function definitions at USE time via the state
8044    // parameter provided by the test harness.
8045    let program = format!(
8046        "{}\n{}\n## Main\n    Let env be a new Map of Text to CVal.\n    Let funcs be a new Map of Text to CFunc.\n    Let state be makePeState(env, funcs, 200).\n    Let result be peBlockM(targetStmts, state).\n    Show \"done\".\n",
8047        CORE_TYPES_FOR_PE, pe_mini
8048    );
8049
8050    // Encode pe_mini + driver as CProgram data (compact encoding)
8051    let encoded = encode_program_source_compact(&program)
8052        .map_err(|e| format!("Failed to encode pe_mini for P2: {:?}", e))?;
8053
8054    // Driver: run PE, then decompile specialized functions transitively.
8055    // Fixpoint collection: discover all transitively-referenced specialized functions
8056    // at arbitrary depth, not limited to any fixed number of levels.
8057    let driver = r#"    Let state be makePeState(a new Map of Text to CVal, encodedFuncMap, 200).
8058    Let residual be peBlock(encodedMain, state).
8059    Let nl be chr(10).
8060    Let mutable output be "".
8061    Let specFuncs be peFuncs(state).
8062    Let mutable allNames be collectCallNames(residual).
8063    Let mutable emitted be a new Map of Text to Bool.
8064    Let mutable changed be true.
8065    While changed:
8066        Set changed to false.
8067        Let mutable toAdd be a new Seq of Text.
8068        Repeat for fnKey in allNames:
8069            Let fkStr be "{fnKey}".
8070            If emitted contains fkStr:
8071                Let skipE be true.
8072            Otherwise:
8073                Set item fkStr of emitted to true.
8074                Let fkStr2 be "{fnKey}".
8075                If specFuncs contains fkStr2:
8076                    Let fdef be item fkStr2 of specFuncs.
8077                    Inspect fdef:
8078                        When CFuncDef (fn0, ps0, pt0, rt0, body0):
8079                            Let children be collectCallNames(body0).
8080                            Repeat for child in children:
8081                                Let childStr be "{child}".
8082                                If not emitted contains childStr:
8083                                    Push child to toAdd.
8084                                    Set changed to true.
8085                        Otherwise:
8086                            Let skipF be true.
8087        Repeat for ta in toAdd:
8088            Push ta to allNames.
8089    Repeat for fnKey in allNames:
8090        Let fkStr be "{fnKey}".
8091        If specFuncs contains fkStr:
8092            Let fdef be item fkStr of specFuncs.
8093            Let funcSrc be decompileFunc(fdef).
8094            If the length of funcSrc is greater than 0:
8095                Set output to "{output}{funcSrc}{nl}".
8096    Let mainSrc be decompileBlock(residual, 0).
8097    Set output to "{output}## Main{nl}{mainSrc}".
8098    Show output.
8099"#;
8100    let combined = format!("{}\n{}\n{}\n## Main\n{}\n{}", CORE_TYPES_FOR_PE, pe, decompile, encoded, driver);
8101
8102    let result = run_logos_source(&combined)?;
8103
8104    // The decompiler now emits types from CFunc's paramTypes/returnType fields for
8105    // functions that carry type info. PE-generated specializations may still use "Any"
8106    // for types that couldn't be propagated through specialization. Apply type fixup
8107    // as a safety net for any remaining "Any" types in specialized function signatures.
8108    let result = fix_decompiled_types(&result, &[
8109        ("peExprM_", "(e: CExpr) -> CExpr:"),
8110        ("peBlockM_", "(stmts: Seq of CStmt) -> Seq of CStmt:"),
8111        ("checkLiteralM_", "(e: CExpr) -> Bool:"),
8112        ("exprToValM_", "(e: CExpr) -> CVal:"),
8113        ("valToExprM_", "(v: CVal) -> CExpr:"),
8114        ("evalBinOpM_", "(binOp: Text) and (lv: CVal) and (rv: CVal) -> CVal:"),
8115        ("isCopyPropSafeM_", "(e: CExpr) -> Bool:"),
8116        ("checkVNothingM_", "(v: CVal) -> Bool:"),
8117        ("hasReturnM_", "(stmts: Seq of CStmt) -> Bool:"),
8118        ("extractReturnM_", "(stmts: Seq of CStmt) -> CExpr:"),
8119        ("validateExtractReturnM_", "(result: CExpr) and (bodyStmts: Seq of CStmt) -> CExpr:"),
8120        ("makeKeyM_", "(fnName: Text) and (args: Seq of CExpr) -> Text:"),
8121        ("exprToKeyPartM_", "(e: CExpr) -> Text:"),
8122        ("collectSetVarsM_", "(stmts: Seq of CStmt) -> Seq of Text:"),
8123        ("peEnvM_", "(st: PEMiniState) -> Map of Text to CVal:"),
8124        ("peFuncsM_", "(st: PEMiniState) -> Map of Text to CFunc:"),
8125        ("peDepthM_", "(st: PEMiniState) -> Int:"),
8126        ("peStaticEnvM_", "(st: PEMiniState) -> Map of Text to CExpr:"),
8127        ("peMemoCacheM_", "(st: PEMiniState) -> Map of Text to CExpr:"),
8128        ("peStateWithEnvDepthM_", "(st: PEMiniState) and (newEnv: Map of Text to CVal) and (d: Int) -> PEMiniState:"),
8129        ("peStateWithEnvDepthStaticM_", "(st: PEMiniState) and (newEnv: Map of Text to CVal) and (d: Int) and (newSe: Map of Text to CExpr) -> PEMiniState:"),
8130    ]);
8131
8132    // Discover the PE-generated block entry point — this IS the compiler.
8133    let (block_entry, expr_entry) = discover_entry_points(&result, "peBlockM_", "peExprM_");
8134    if block_entry.is_empty() {
8135        return Err("Genuine P2: no peBlockM_ entry found in residual".to_string());
8136    }
8137
8138    // Strip the ## Main block from the residual — we only need the specialized function
8139    // definitions. The test harness provides its own ## Main.
8140    let func_defs_only = strip_main_block(&result);
8141
8142    // The specialized functions call pe_mini's unspecialized helpers (checkLiteralM,
8143    // valToExprM, etc.) which the PE couldn't fold because their args are dynamic.
8144    // Include the original pe_mini source so these helpers are available.
8145    let pe_mini_helpers = pe_mini_source_text();
8146
8147    // No Rust-generated block wrapper needed — the PE produced a specialized peBlockM_
8148    // function that handles all CStmt variants with specialized expression processing.
8149    // Generate a thin alias for backward compatibility.
8150    let alias = format!(
8151        "\n## To compileBlock (stmts: Seq of CStmt) -> Seq of CStmt:\n    Return {}(stmts).\n",
8152        block_entry
8153    );
8154
8155    // Combine: pe_mini helpers first (authoritative), then specialized functions, then alias.
8156    // Deduplicate: if the decompiled residual redefines a pe_mini function (unspecialized),
8157    // the dedup removes the second definition, keeping the original pe_mini version.
8158    let combined = format!("{}\n{}\n{}", pe_mini_helpers, func_defs_only, alias);
8159    let full_source = deduplicate_functions(&combined);
8160
8161    Ok(GenuineProjectionResult {
8162        source: full_source,
8163        block_entry: "compileBlock".to_string(),
8164        expr_entry,
8165    })
8166}
8167
8168/// Run genuine PE(pe_source, pe_mini(targetStmts)) and return the specialized
8169/// compiler residual as LOGOS source code.
8170///
8171/// This is the actual Futamura Projection 2: the outer PE (pe_source) specializes
8172/// pe_mini's peBlockM with known state (empty env, empty funcs, depth 200).
8173/// The result is a specialized compiler function that takes target statements and
8174/// compiles them with pe_mini's dispatch logic partially evaluated away.
8175///
8176/// Returns the decompiled LOGOS source of the genuine P2 residual, including
8177/// specialized function definitions extracted from peFuncs.
8178pub fn genuine_projection2_residual() -> Result<String, String> {
8179    let pe_mini = pe_mini_source_text();
8180    let pe = pe_source_text();
8181    let decompile = decompile_source_text();
8182
8183    // Build pe_mini program with peBlockM as entry — full block-level specialization
8184    let program = format!(
8185        "{}\n{}\n## Main\n    Let env be a new Map of Text to CVal.\n    Let funcs be a new Map of Text to CFunc.\n    Let state be makePeState(env, funcs, 200).\n    Let result be peBlockM(targetStmts, state).\n    Show \"done\".\n",
8186        CORE_TYPES_FOR_PE, pe_mini
8187    );
8188
8189    // Encode pe_mini + driver as CProgram data (compact encoding)
8190    let encoded = encode_program_source_compact(&program)
8191        .map_err(|e| format!("Failed to encode pe_mini: {:?}", e))?;
8192
8193    // Driver: run PE, then decompile residual + specialized functions
8194    let driver = r#"    Let state be makePeState(a new Map of Text to CVal, encodedFuncMap, 200).
8195    Let residual be peBlock(encodedMain, state).
8196    Let nl be chr(10).
8197    Let mutable output be "".
8198    Let specFuncs be peFuncs(state).
8199    Let specNames be collectCallNames(residual).
8200    Repeat for sn in specNames:
8201        Let snKey be "{sn}".
8202        If specFuncs contains snKey:
8203            Let fdef be item snKey of specFuncs.
8204            Let funcSrc be decompileFunc(fdef).
8205            If the length of funcSrc is greater than 0:
8206                Set output to "{output}{funcSrc}{nl}".
8207    Let mainSrc be decompileBlock(residual, 0).
8208    Set output to "{output}## Main{nl}{mainSrc}".
8209    Show output.
8210"#;
8211    let combined = format!("{}\n{}\n{}\n## Main\n{}\n{}", CORE_TYPES_FOR_PE, pe, decompile, encoded, driver);
8212
8213    let result = run_logos_source(&combined)?;
8214    Ok(result)
8215}
8216
8217/// Run genuine PE(pe_source, pe_bti(targetExpr)) and return the specialized
8218/// cogen residual as LOGOS source code.
8219///
8220/// This is the actual Futamura Projection 3: the outer PE (pe_source) specializes
8221/// pe_bti (a full PE with memoization) with known state (empty env, empty funcs,
8222/// depth 200). pe_bti is structurally identical to pe_source with renamed entry
8223/// points (peExprB, peBlockB, etc.) — so this is genuinely PE(PE, PE).
8224///
8225/// The result is a specialized cogen: pe_bti's dispatch partially evaluated away,
8226/// producing a program that takes a target CExpr and compiles it.
8227pub fn genuine_projection3_residual() -> Result<String, String> {
8228    let pe_bti = pe_bti_source_text();
8229    let pe = pe_source_text();
8230    let decompile = decompile_source_text();
8231
8232    // pe_bti uses memoCache/callGuard instead of specResults/onStack
8233    let bti_types = CORE_TYPES_FOR_PE
8234        .replace("specResults", "memoCache")
8235        .replace("onStack", "callGuard");
8236
8237    // Build pe_bti program with peBlockB as entry — full block-level specialization
8238    let program = format!(
8239        "{}\n{}\n## Main\n    Let env be a new Map of Text to CVal.\n    Let funcs be a new Map of Text to CFunc.\n    Let state be makePeState(env, funcs, 200).\n    Let result be peBlockB(targetStmts, state).\n    Show \"done\".\n",
8240        bti_types, pe_bti
8241    );
8242
8243    // Encode pe_bti + driver as CProgram data (compact encoding)
8244    let encoded = encode_program_source_compact(&program)
8245        .map_err(|e| format!("Failed to encode pe_bti: {:?}", e))?;
8246
8247    // Driver: run PE, then decompile residual + specialized functions
8248    let driver = r#"    Let state be makePeState(a new Map of Text to CVal, encodedFuncMap, 200).
8249    Let residual be peBlock(encodedMain, state).
8250    Let nl be chr(10).
8251    Let mutable output be "".
8252    Let specFuncs be peFuncs(state).
8253    Let specNames be collectCallNames(residual).
8254    Repeat for sn in specNames:
8255        Let snKey be "{sn}".
8256        If specFuncs contains snKey:
8257            Let fdef be item snKey of specFuncs.
8258            Let funcSrc be decompileFunc(fdef).
8259            If the length of funcSrc is greater than 0:
8260                Set output to "{output}{funcSrc}{nl}".
8261    Let mainSrc be decompileBlock(residual, 0).
8262    Set output to "{output}## Main{nl}{mainSrc}".
8263    Show output.
8264"#;
8265    let combined = format!("{}\n{}\n{}\n## Main\n{}\n{}", CORE_TYPES_FOR_PE, pe, decompile, encoded, driver);
8266
8267    let result = run_logos_source(&combined)?;
8268    Ok(result)
8269}
8270
8271/// Genuine Futamura Projection 3 via self-application: PE(pe_source, pe_bti(targetStmts))
8272///
8273/// The outer PE (pe_source) specializes pe_bti's FULL BLOCK evaluator (a full PE with
8274/// memoization, structurally identical to pe_source with renamed entry points) with
8275/// known state (empty env, empty funcs, depth 200). This is genuinely PE(PE, PE).
8276///
8277/// The result is a specialized cogen: the PE naturally produces block-level dispatch
8278/// through specialization of peBlockB, eliminating the need for a Rust-generated wrapper.
8279pub fn projection3_source_real(_core_types: &str) -> Result<GenuineProjectionResult, String> {
8280    let pe_bti = pe_bti_source_text();
8281    let pe = pe_source_text();
8282    let decompile = decompile_source_text();
8283
8284    // pe_bti uses memoCache/callGuard instead of specResults/onStack
8285    let bti_types = CORE_TYPES_FOR_PE
8286        .replace("specResults", "memoCache")
8287        .replace("onStack", "callGuard");
8288
8289    // Build pe_bti program with peBlockB as entry — FULL block-level specialization.
8290    let program = format!(
8291        "{}\n{}\n## Main\n    Let env be a new Map of Text to CVal.\n    Let funcs be a new Map of Text to CFunc.\n    Let state be makePeState(env, funcs, 200).\n    Let result be peBlockB(targetStmts, state).\n    Show \"done\".\n",
8292        bti_types, pe_bti
8293    );
8294
8295    // Encode pe_bti + driver as CProgram data (compact encoding)
8296    let encoded = encode_program_source_compact(&program)
8297        .map_err(|e| format!("Failed to encode pe_bti for P3: {:?}", e))?;
8298
8299    // Driver: fixpoint transitive collection (same algorithm as P2)
8300    let driver = r#"    Let state be makePeState(a new Map of Text to CVal, encodedFuncMap, 200).
8301    Let residual be peBlock(encodedMain, state).
8302    Let nl be chr(10).
8303    Let mutable output be "".
8304    Let specFuncs be peFuncs(state).
8305    Let mutable allNames be collectCallNames(residual).
8306    Let mutable emitted be a new Map of Text to Bool.
8307    Let mutable changed be true.
8308    While changed:
8309        Set changed to false.
8310        Let mutable toAdd be a new Seq of Text.
8311        Repeat for fnKey in allNames:
8312            Let fkStr be "{fnKey}".
8313            If emitted contains fkStr:
8314                Let skipE be true.
8315            Otherwise:
8316                Set item fkStr of emitted to true.
8317                Let fkStr2 be "{fnKey}".
8318                If specFuncs contains fkStr2:
8319                    Let fdef be item fkStr2 of specFuncs.
8320                    Inspect fdef:
8321                        When CFuncDef (fn0, ps0, pt0, rt0, body0):
8322                            Let children be collectCallNames(body0).
8323                            Repeat for child in children:
8324                                Let childStr be "{child}".
8325                                If not emitted contains childStr:
8326                                    Push child to toAdd.
8327                                    Set changed to true.
8328                        Otherwise:
8329                            Let skipF be true.
8330        Repeat for ta in toAdd:
8331            Push ta to allNames.
8332    Repeat for fnKey in allNames:
8333        Let fkStr be "{fnKey}".
8334        If specFuncs contains fkStr:
8335            Let fdef be item fkStr of specFuncs.
8336            Let funcSrc be decompileFunc(fdef).
8337            If the length of funcSrc is greater than 0:
8338                Set output to "{output}{funcSrc}{nl}".
8339    Let mainSrc be decompileBlock(residual, 0).
8340    Set output to "{output}## Main{nl}{mainSrc}".
8341    Show output.
8342"#;
8343    let combined = format!("{}\n{}\n{}\n## Main\n{}\n{}", CORE_TYPES_FOR_PE, pe, decompile, encoded, driver);
8344
8345    let result = run_logos_source(&combined)?;
8346
8347    // Fix decompiled types for pe_bti specialized functions (B suffix)
8348    // Fix decompiled types for pe_bti specialized functions (B suffix)
8349    let result = fix_decompiled_types(&result, &[
8350        ("peExprB_", "(e: CExpr) -> CExpr:"),
8351        ("peBlockB_", "(stmts: Seq of CStmt) -> Seq of CStmt:"),
8352        ("isStatic_", "(e: CExpr) -> Bool:"),
8353        ("isLiteral_", "(e: CExpr) -> Bool:"),
8354        ("allStatic_", "(args: Seq of CExpr) -> Bool:"),
8355        ("exprToVal_", "(e: CExpr) -> CVal:"),
8356        ("valToExpr_", "(v: CVal) -> CExpr:"),
8357        ("evalBinOp_", "(binOp: Text) and (lv: CVal) and (rv: CVal) -> CVal:"),
8358        ("isCopyPropSafe_", "(e: CExpr) -> Bool:"),
8359        ("isVNothing_", "(v: CVal) -> Bool:"),
8360        ("hasReturn_", "(stmts: Seq of CStmt) -> Bool:"),
8361        ("extractReturnB_", "(stmts: Seq of CStmt) -> CExpr:"),
8362        ("makeKey_", "(fnName: Text) and (args: Seq of CExpr) -> Text:"),
8363        ("exprToKeyPartB_", "(e: CExpr) -> Text:"),
8364        ("collectSetVars_", "(stmts: Seq of CStmt) -> Seq of Text:"),
8365    ]);
8366
8367    // Discover the PE-generated block entry point — this IS the cogen.
8368    let (block_entry, expr_entry) = discover_entry_points(&result, "peBlockB_", "peExprB_");
8369    if block_entry.is_empty() {
8370        return Err("Genuine P3: no peBlockB_ entry found in residual".to_string());
8371    }
8372
8373    // Strip the ## Main block — we only need the specialized function definitions
8374    let func_defs_only = strip_main_block(&result);
8375
8376    // Include pe_bti helpers (unspecialized functions called by specialized ones)
8377    let pe_bti_helpers = pe_bti_source_text();
8378
8379    // No Rust-generated block wrapper needed — the PE produced a specialized peBlockB_
8380    // function. Generate a thin alias for backward compatibility.
8381    let alias = format!(
8382        "\n## To cogenBlock (stmts: Seq of CStmt) -> Seq of CStmt:\n    Return {}(stmts).\n",
8383        block_entry
8384    );
8385    let combined = format!("{}\n{}\n{}", pe_bti_helpers, func_defs_only, alias);
8386    let full_source = deduplicate_functions(&combined);
8387
8388    Ok(GenuineProjectionResult {
8389        source: full_source,
8390        block_entry: "cogenBlock".to_string(),
8391        expr_entry,
8392    })
8393}
8394
8395/// Remove duplicate function definitions, keeping the first occurrence.
8396fn deduplicate_functions(source: &str) -> String {
8397    let mut seen = std::collections::HashSet::new();
8398    let mut result = String::with_capacity(source.len());
8399    let mut skip_until_next = false;
8400    for line in source.lines() {
8401        let trimmed = line.trim();
8402        if let Some(rest) = trimmed.strip_prefix("## To ") {
8403            let name = rest.split(' ').next().unwrap_or("");
8404            if !seen.insert(name.to_string()) {
8405                skip_until_next = true;
8406                continue;
8407            }
8408            skip_until_next = false;
8409        } else if trimmed.starts_with("## Main") {
8410            skip_until_next = false;
8411        } else if skip_until_next {
8412            // Skip body of duplicate function
8413            if !trimmed.starts_with("## ") {
8414                continue;
8415            }
8416            skip_until_next = false;
8417        }
8418        result.push_str(line);
8419        result.push('\n');
8420    }
8421    result
8422}
8423
8424/// Strip the ## Main block from decompiled source, keeping only function definitions.
8425/// The genuine residual's ## Main references internal PE variables (targetExpr, blockResult)
8426/// that don't exist in the test context. We only need the specialized function definitions.
8427fn strip_main_block(source: &str) -> String {
8428    let mut result = String::with_capacity(source.len());
8429    let mut in_main = false;
8430    for line in source.lines() {
8431        let trimmed = line.trim();
8432        if trimmed == "## Main" {
8433            in_main = true;
8434            continue;
8435        }
8436        if in_main {
8437            // Main block ends when we hit another ## To definition or end of file
8438            if trimmed.starts_with("## To ") {
8439                in_main = false;
8440            } else {
8441                continue;
8442            }
8443        }
8444        result.push_str(line);
8445        result.push('\n');
8446    }
8447    result
8448}
8449
8450/// Extract the ## Main block content from source.
8451fn extract_main_block(source: &str) -> String {
8452    let mut result = String::new();
8453    let mut in_main = false;
8454    for line in source.lines() {
8455        let trimmed = line.trim();
8456        if trimmed == "## Main" {
8457            in_main = true;
8458            continue;
8459        }
8460        if in_main {
8461            if trimmed.starts_with("## To ") {
8462                break;
8463            }
8464            result.push_str(line);
8465            result.push('\n');
8466        }
8467    }
8468    result
8469}
8470
8471/// Fix decompiled function signatures: replace `(param: Any) -> Any:` with correct types.
8472/// PE-generated specializations still use "Any" for types the PE couldn't propagate.
8473/// This restores correct types based on function name prefixes.
8474fn fix_decompiled_types(source: &str, type_map: &[(&str, &str)]) -> String {
8475    let mut result = String::with_capacity(source.len());
8476    for line in source.lines() {
8477        let trimmed = line.trim();
8478        if let Some(rest) = trimmed.strip_prefix("## To ") {
8479            let name = rest.split(' ').next().unwrap_or("");
8480            let mut fixed = false;
8481            for (prefix, sig) in type_map {
8482                if name.starts_with(prefix) {
8483                    result.push_str(&format!("## To {} {}\n", name, sig));
8484                    fixed = true;
8485                    break;
8486                }
8487            }
8488            if !fixed {
8489                result.push_str(line);
8490                result.push('\n');
8491            }
8492        } else {
8493            result.push_str(line);
8494            result.push('\n');
8495        }
8496    }
8497    let result = result
8498        .replace("Seq of Any", "Seq of CExpr")
8499        .replace("Set of Any", "Set of CExpr")
8500        .replace(": Any)", ": CExpr)")
8501        .replace("-> Any:", "-> CExpr:");
8502    result
8503}
8504
8505fn replace_word(source: &str, from: &str, to: &str) -> String {
8506    let mut result = String::with_capacity(source.len());
8507    let mut remaining = source;
8508    while let Some(pos) = remaining.find(from) {
8509        let before = if pos > 0 { remaining.as_bytes()[pos - 1] } else { b' ' };
8510        let after_pos = pos + from.len();
8511        let after = if after_pos < remaining.len() { remaining.as_bytes()[after_pos] } else { b' ' };
8512        let is_word = !before.is_ascii_alphanumeric() && before != b'_'
8513            && !after.is_ascii_alphanumeric() && after != b'_';
8514        result.push_str(&remaining[..pos]);
8515        if is_word {
8516            result.push_str(to);
8517        } else {
8518            result.push_str(from);
8519        }
8520        remaining = &remaining[after_pos..];
8521    }
8522    result.push_str(remaining);
8523    result
8524}
8525
8526#[cfg(test)]
8527mod tests {
8528    use super::*;
8529
8530    #[test]
8531    fn test_compile_let_statement() {
8532        let source = "## Main\nLet x be 5.";
8533        let result = compile_to_rust(source);
8534        assert!(result.is_ok(), "Should compile: {:?}", result);
8535        let rust = result.unwrap();
8536        assert!(rust.contains("fn main()"));
8537        assert!(rust.contains("let x = 5;"));
8538    }
8539
8540    #[test]
8541    fn test_compile_return_statement() {
8542        let source = "## Main\nReturn 42.";
8543        let result = compile_to_rust(source);
8544        assert!(result.is_ok(), "Should compile: {:?}", result);
8545        let rust = result.unwrap();
8546        assert!(rust.contains("return 42;"));
8547    }
8548
8549    /// Run `src` through the RUN-PATH optimizer (`optimize_for_run`, which
8550    /// includes the indexed-load LICM) and then the pure-bytecode VM (no native
8551    /// tier → no forge dependency), capturing the outcome.
8552    fn optimized_vm_outcome_no_tier(src: &str, argv: &[String]) -> (String, Option<String>) {
8553        crate::ui_bridge::with_optimized_program(src, |parsed, interner| match parsed {
8554            Ok((stmts, types, policies)) => crate::vm::run_to_outcome_with_args(
8555                stmts,
8556                interner,
8557                Some(types),
8558                Some(&policies),
8559                argv,
8560                None,
8561            ),
8562            Err(advice) => (String::new(), Some(advice)),
8563        })
8564    }
8565
8566    fn norm_lines(s: &str) -> String {
8567        s.lines()
8568            .map(|l| l.trim_end())
8569            .filter(|l| !l.is_empty())
8570            .collect::<Vec<_>>()
8571            .join("\n")
8572    }
8573
8574    /// Task C SOUNDNESS, in-process: an inner loop reading a loop-invariant
8575    /// `item i of arr` (i = OUTER counter, arr never mutated in the loop) mixed
8576    /// with a variant `item j of arr` must produce the EXACT outcome the
8577    /// tree-walker produces once the invariant read is hoisted. The guarded
8578    /// hoist preserves WHEN the read runs, so the optimized VM matches the
8579    /// oracle bit-for-bit on a real, in-bounds nbody-shaped loop.
8580    #[test]
8581    fn licm_indexed_load_nbody_shape_matches_oracle_inprocess() {
8582        let src = "## Main\n\
8583                   Let arr be [10, 20, 30, 40, 50].\n\
8584                   Let mutable acc be 0.\n\
8585                   Let mutable i be 1.\n\
8586                   While i is at most 5:\n\
8587                   \x20   Let mutable j be 1.\n\
8588                   \x20   While j is at most 5:\n\
8589                   \x20       Let d be item i of arr - item j of arr.\n\
8590                   \x20       Set acc to acc + d * d.\n\
8591                   \x20       Set j to j + 1.\n\
8592                   \x20   Set i to i + 1.\n\
8593                   Show acc.\n";
8594        let argv: [String; 0] = [];
8595        let tw = tw_outcome_with_args(src, &argv);
8596        let (out, err) = optimized_vm_outcome_no_tier(src, &argv);
8597        assert_eq!(
8598            (norm_lines(&tw.output), &tw.error),
8599            ("10000".to_string(), &None),
8600            "oracle: sum of (a_i - a_j)^2"
8601        );
8602        assert_eq!(
8603            (norm_lines(&out), &err),
8604            (norm_lines(&tw.output), &tw.error),
8605            "optimized VM (LICM) must match the tree-walker on the nbody-shaped loop"
8606        );
8607    }
8608
8609    /// SOUNDNESS GATE (forge-free): the OPTIMIZED bytecode VM — exercising the
8610    /// constant-pool sharing (Task B) and the indexed-load LICM (Task C) — must
8611    /// produce BIT-IDENTICAL output to the raw tree-walker on the whole
8612    /// benchmark corpus. This mirrors the `vm_opt_differential` integration
8613    /// test but runs without a native tier, so it stays inside this crate.
8614    #[test]
8615    fn corpus_optimized_vm_matches_treewalker_no_tier() {
8616        const CORPUS: &[(&str, &str)] = &[
8617            ("ackermann", "3"),
8618            ("array_fill", "2000"),
8619            ("array_reverse", "2000"),
8620            ("binary_trees", "6"),
8621            ("bubble_sort", "60"),
8622            ("coins", "500"),
8623            ("collatz", "300"),
8624            ("collect", "300"),
8625            ("counting_sort", "2000"),
8626            ("fannkuch", "5"),
8627            ("fib", "12"),
8628            ("fib_iterative", "500"),
8629            ("gcd", "60"),
8630            ("graph_bfs", "200"),
8631            ("heap_sort", "300"),
8632            ("histogram", "2000"),
8633            ("knapsack", "30"),
8634            ("loop_sum", "2000"),
8635            ("mandelbrot", "20"),
8636            ("matrix_mult", "8"),
8637            ("mergesort", "300"),
8638            ("nbody", "100"),
8639            ("nqueens", "5"),
8640            ("pi_leibniz", "2000"),
8641            ("prefix_sum", "2000"),
8642            ("primes", "500"),
8643            ("quicksort", "300"),
8644            ("sieve", "2000"),
8645            ("spectral_norm", "20"),
8646            ("string_search", "1200"),
8647            ("strings", "200"),
8648            ("two_sum", "300"),
8649        ];
8650        std::thread::Builder::new()
8651            .stack_size(256 * 1024 * 1024)
8652            .spawn(|| {
8653                for &(name, size) in CORPUS {
8654                    let path = format!(
8655                        "{}/../../benchmarks/programs/{}/main.lg",
8656                        env!("CARGO_MANIFEST_DIR"),
8657                        name
8658                    );
8659                    let src = std::fs::read_to_string(&path)
8660                        .unwrap_or_else(|e| panic!("cannot read {path}: {e}"));
8661                    let argv = vec!["bench".to_string(), size.to_string()];
8662                    let (out, err) = optimized_vm_outcome_no_tier(&src, &argv);
8663                    let tw = tw_outcome_with_args(&src, &argv);
8664                    assert_eq!(
8665                        (norm_lines(&out), &err),
8666                        (norm_lines(&tw.output), &tw.error),
8667                        "OPTIMIZED VM (no tier) diverged from the tree-walker on '{name}' at {size}"
8668                    );
8669                }
8670            })
8671            .expect("spawn")
8672            .join()
8673            .expect("corpus thread panicked");
8674    }
8675}