Skip to main content

oxilean_codegen/js_backend/
types.rs

1//! Auto-generated module
2//!
3//! 🤖 Generated with [SplitRS](https://github.com/cool-japan/splitrs)
4
5use crate::lcnf::*;
6use std::collections::HashMap;
7
8use super::functions::{JS_KEYWORDS, JS_RUNTIME};
9
10use std::collections::{HashSet, VecDeque};
11
12/// Simple peephole optimizations on `JsExpr`.
13#[allow(dead_code)]
14pub struct JsPeephole;
15impl JsPeephole {
16    /// Fold constant arithmetic on numeric literals.
17    #[allow(dead_code)]
18    pub fn fold_arith(expr: &JsExpr) -> JsExpr {
19        if let JsExpr::BinOp(op, lhs, rhs) = expr {
20            if let (JsExpr::Lit(JsLit::Num(a)), JsExpr::Lit(JsLit::Num(b))) =
21                (lhs.as_ref(), rhs.as_ref())
22            {
23                let folded = match op.as_str() {
24                    "+" => Some(a + b),
25                    "-" => Some(a - b),
26                    "*" => Some(a * b),
27                    "/" if *b != 0.0 => Some(a / b),
28                    _ => None,
29                };
30                if let Some(v) = folded {
31                    return JsExpr::Lit(JsLit::Num(v));
32                }
33            }
34        }
35        expr.clone()
36    }
37    /// Simplify `x === x` → `true`.
38    #[allow(dead_code)]
39    pub fn simplify_identity(expr: &JsExpr) -> JsExpr {
40        if let JsExpr::BinOp(op, lhs, rhs) = expr {
41            if op == "===" && lhs == rhs {
42                return JsExpr::Lit(JsLit::Bool(true));
43            }
44            if op == "!==" && lhs == rhs {
45                return JsExpr::Lit(JsLit::Bool(false));
46            }
47        }
48        expr.clone()
49    }
50    /// Simplify `!true` → `false`, `!false` → `true`.
51    #[allow(dead_code)]
52    pub fn simplify_not(expr: &JsExpr) -> JsExpr {
53        if let JsExpr::UnOp(op, inner) = expr {
54            if op == "!" {
55                if let JsExpr::Lit(JsLit::Bool(b)) = inner.as_ref() {
56                    return JsExpr::Lit(JsLit::Bool(!b));
57                }
58            }
59        }
60        expr.clone()
61    }
62    /// Apply all peephole optimizations.
63    #[allow(dead_code)]
64    pub fn optimize(expr: &JsExpr) -> JsExpr {
65        let e = Self::fold_arith(expr);
66        let e = Self::simplify_identity(&e);
67        Self::simplify_not(&e)
68    }
69}
70/// Estimates the uncompressed byte size of generated JS code.
71#[allow(dead_code)]
72pub struct JsSizeEstimator;
73impl JsSizeEstimator {
74    /// Estimate the byte size of a JS function.
75    #[allow(dead_code)]
76    pub fn estimate_function(func: &JsFunction) -> usize {
77        func.to_string().len()
78    }
79    /// Estimate the byte size of a JS module.
80    #[allow(dead_code)]
81    pub fn estimate_module(module: &JsModule) -> usize {
82        module.emit().len()
83    }
84    /// Estimate the byte size of a JS expression.
85    #[allow(dead_code)]
86    pub fn estimate_expr(expr: &JsExpr) -> usize {
87        expr.to_string().len()
88    }
89    /// Estimate the byte size of a JS statement.
90    #[allow(dead_code)]
91    pub fn estimate_stmt(stmt: &JsStmt) -> usize {
92        stmt.to_string().len()
93    }
94}
95#[allow(dead_code)]
96pub struct JSPassRegistry {
97    pub(super) configs: Vec<JSPassConfig>,
98    pub(super) stats: std::collections::HashMap<String, JSPassStats>,
99}
100impl JSPassRegistry {
101    #[allow(dead_code)]
102    pub fn new() -> Self {
103        JSPassRegistry {
104            configs: Vec::new(),
105            stats: std::collections::HashMap::new(),
106        }
107    }
108    #[allow(dead_code)]
109    pub fn register(&mut self, config: JSPassConfig) {
110        self.stats
111            .insert(config.pass_name.clone(), JSPassStats::new());
112        self.configs.push(config);
113    }
114    #[allow(dead_code)]
115    pub fn enabled_passes(&self) -> Vec<&JSPassConfig> {
116        self.configs.iter().filter(|c| c.enabled).collect()
117    }
118    #[allow(dead_code)]
119    pub fn get_stats(&self, name: &str) -> Option<&JSPassStats> {
120        self.stats.get(name)
121    }
122    #[allow(dead_code)]
123    pub fn total_passes(&self) -> usize {
124        self.configs.len()
125    }
126    #[allow(dead_code)]
127    pub fn enabled_count(&self) -> usize {
128        self.enabled_passes().len()
129    }
130    #[allow(dead_code)]
131    pub fn update_stats(&mut self, name: &str, changes: u64, time_ms: u64, iter: u32) {
132        if let Some(stats) = self.stats.get_mut(name) {
133            stats.record_run(changes, time_ms, iter);
134        }
135    }
136}
137/// A very simple JavaScript minifier that removes comments and collapses
138/// unnecessary whitespace.  Not a full minifier, but reduces output size.
139#[allow(dead_code)]
140pub struct JsMinifier;
141impl JsMinifier {
142    /// Minify a JS source string.
143    ///
144    /// Operations performed:
145    /// - Remove `// ...` line comments
146    /// - Collapse runs of whitespace to a single space
147    /// - Remove leading/trailing whitespace per line
148    #[allow(dead_code)]
149    pub fn minify(source: &str) -> std::string::String {
150        let mut out = std::string::String::new();
151        for line in source.lines() {
152            let line = if let Some(pos) = line.find("//") {
153                &line[..pos]
154            } else {
155                line
156            };
157            let trimmed = line.trim();
158            if !trimmed.is_empty() {
159                let collapsed: std::string::String =
160                    trimmed.split_whitespace().collect::<Vec<_>>().join(" ");
161                out.push_str(&collapsed);
162                out.push('\n');
163            }
164        }
165        out
166    }
167    /// Strip block comments `/* ... */` from a JS string.
168    #[allow(dead_code)]
169    pub fn strip_block_comments(source: &str) -> std::string::String {
170        let mut out = std::string::String::new();
171        let mut in_comment = false;
172        let bytes = source.as_bytes();
173        let mut i = 0;
174        while i < bytes.len() {
175            if !in_comment && i + 1 < bytes.len() && bytes[i] == b'/' && bytes[i + 1] == b'*' {
176                in_comment = true;
177                i += 2;
178            } else if in_comment && i + 1 < bytes.len() && bytes[i] == b'*' && bytes[i + 1] == b'/'
179            {
180                in_comment = false;
181                i += 2;
182            } else if !in_comment {
183                out.push(bytes[i] as char);
184                i += 1;
185            } else {
186                i += 1;
187            }
188        }
189        out
190    }
191}
192/// A structural type checker for JS expressions.
193///
194/// Infers the most specific `JsType` for an expression based on its structure.
195#[allow(dead_code)]
196pub struct JsTypeChecker;
197impl JsTypeChecker {
198    /// Infer the JS type of a literal.
199    #[allow(dead_code)]
200    pub fn infer_lit(lit: &JsLit) -> JsType {
201        match lit {
202            JsLit::Num(_) => JsType::Number,
203            JsLit::BigInt(_) => JsType::BigInt,
204            JsLit::Str(_) => JsType::String,
205            JsLit::Bool(_) => JsType::Boolean,
206            JsLit::Null => JsType::Null,
207            JsLit::Undefined => JsType::Undefined,
208        }
209    }
210    /// Infer the JS type of an expression (best-effort).
211    #[allow(dead_code)]
212    pub fn infer_expr(expr: &JsExpr) -> JsType {
213        match expr {
214            JsExpr::Lit(lit) => Self::infer_lit(lit),
215            JsExpr::BinOp(op, lhs, _) => match op.as_str() {
216                "===" | "!==" | "==" | "!=" | "<" | ">" | "<=" | ">=" => JsType::Boolean,
217                "+" => {
218                    let lhs_ty = Self::infer_expr(lhs);
219                    if lhs_ty == JsType::String {
220                        JsType::String
221                    } else if lhs_ty == JsType::BigInt {
222                        JsType::BigInt
223                    } else {
224                        JsType::Number
225                    }
226                }
227                "-" | "*" | "/" | "%" => {
228                    if Self::infer_expr(lhs) == JsType::BigInt {
229                        JsType::BigInt
230                    } else {
231                        JsType::Number
232                    }
233                }
234                _ => JsType::Unknown,
235            },
236            JsExpr::UnOp(op, _) => match op.as_str() {
237                "!" => JsType::Boolean,
238                "-" | "+" => JsType::Number,
239                "typeof" => JsType::String,
240                _ => JsType::Unknown,
241            },
242            JsExpr::Object(_) => JsType::Object,
243            JsExpr::Array(_) => JsType::Array,
244            JsExpr::Arrow(_, _) => JsType::Function,
245            JsExpr::New(_, _) => JsType::Object,
246            _ => JsType::Unknown,
247        }
248    }
249}
250/// A JavaScript ES2020 module, containing functions and a preamble.
251#[derive(Debug, Clone)]
252pub struct JsModule {
253    /// Top-level function declarations.
254    pub functions: Vec<JsFunction>,
255    /// Top-level `const`/`let` lines (preamble, runtime, etc.).
256    pub preamble: Vec<std::string::String>,
257    /// Names to include in the default export object.
258    pub exports: Vec<std::string::String>,
259}
260impl JsModule {
261    /// Create a new empty JS module.
262    pub fn new() -> Self {
263        JsModule {
264            functions: Vec::new(),
265            preamble: Vec::new(),
266            exports: Vec::new(),
267        }
268    }
269    /// Add a function to the module.
270    pub fn add_function(&mut self, f: JsFunction) {
271        self.functions.push(f);
272    }
273    /// Add a top-level preamble line (raw JS text).
274    pub fn add_preamble(&mut self, line: std::string::String) {
275        self.preamble.push(line);
276    }
277    /// Add a name to the export list.
278    pub fn add_export(&mut self, name: std::string::String) {
279        self.exports.push(name);
280    }
281    /// Emit the full JS module as a string.
282    ///
283    /// The output includes:
284    /// 1. The OxiLean JS runtime preamble (`_OL`)
285    /// 2. Any additional preamble lines
286    /// 3. All function declarations
287    /// 4. A named export object if `exports` is non-empty
288    pub fn emit(&self) -> std::string::String {
289        let mut out = std::string::String::new();
290        out.push_str(JS_RUNTIME);
291        out.push('\n');
292        for line in &self.preamble {
293            out.push_str(line);
294            out.push('\n');
295        }
296        if !self.preamble.is_empty() {
297            out.push('\n');
298        }
299        for func in &self.functions {
300            out.push_str(&func.to_string());
301            out.push_str("\n\n");
302        }
303        if !self.exports.is_empty() {
304            out.push_str("export { ");
305            for (i, name) in self.exports.iter().enumerate() {
306                if i > 0 {
307                    out.push_str(", ");
308                }
309                out.push_str(name);
310            }
311            out.push_str(" };\n");
312        }
313        out
314    }
315}
316#[allow(dead_code)]
317#[derive(Debug, Clone)]
318pub struct JSDominatorTree {
319    pub idom: Vec<Option<u32>>,
320    pub dom_children: Vec<Vec<u32>>,
321    pub dom_depth: Vec<u32>,
322}
323impl JSDominatorTree {
324    #[allow(dead_code)]
325    pub fn new(size: usize) -> Self {
326        JSDominatorTree {
327            idom: vec![None; size],
328            dom_children: vec![Vec::new(); size],
329            dom_depth: vec![0; size],
330        }
331    }
332    #[allow(dead_code)]
333    pub fn set_idom(&mut self, node: usize, idom: u32) {
334        self.idom[node] = Some(idom);
335    }
336    #[allow(dead_code)]
337    pub fn dominates(&self, a: usize, b: usize) -> bool {
338        if a == b {
339            return true;
340        }
341        let mut cur = b;
342        loop {
343            match self.idom[cur] {
344                Some(parent) if parent as usize == a => return true,
345                Some(parent) if parent as usize == cur => return false,
346                Some(parent) => cur = parent as usize,
347                None => return false,
348            }
349        }
350    }
351    #[allow(dead_code)]
352    pub fn depth(&self, node: usize) -> u32 {
353        self.dom_depth.get(node).copied().unwrap_or(0)
354    }
355}
356/// JavaScript literal values.
357#[derive(Debug, Clone, PartialEq)]
358pub enum JsLit {
359    /// Floating-point number literal: `1.0`, `42`, `-3.14`
360    Num(f64),
361    /// BigInt literal for Nat: `0n`, `42n`
362    BigInt(i64),
363    /// String literal: `"hello"`
364    Str(std::string::String),
365    /// Boolean literal: `true` or `false`
366    Bool(bool),
367    /// `null` literal
368    Null,
369    /// `undefined` literal
370    Undefined,
371}
372/// A top-level JavaScript function declaration.
373#[derive(Debug, Clone, PartialEq)]
374pub struct JsFunction {
375    /// The name of the function.
376    pub name: std::string::String,
377    /// The parameter names.
378    pub params: Vec<std::string::String>,
379    /// The function body statements.
380    pub body: Vec<JsStmt>,
381    /// Whether this function is declared with `async`.
382    pub is_async: bool,
383    /// Whether this function is exported (for ES module export).
384    pub is_export: bool,
385}
386#[allow(dead_code)]
387#[derive(Debug, Clone)]
388pub struct JSPassConfig {
389    pub phase: JSPassPhase,
390    pub enabled: bool,
391    pub max_iterations: u32,
392    pub debug_output: bool,
393    pub pass_name: String,
394}
395impl JSPassConfig {
396    #[allow(dead_code)]
397    pub fn new(name: impl Into<String>, phase: JSPassPhase) -> Self {
398        JSPassConfig {
399            phase,
400            enabled: true,
401            max_iterations: 10,
402            debug_output: false,
403            pass_name: name.into(),
404        }
405    }
406    #[allow(dead_code)]
407    pub fn disabled(mut self) -> Self {
408        self.enabled = false;
409        self
410    }
411    #[allow(dead_code)]
412    pub fn with_debug(mut self) -> Self {
413        self.debug_output = true;
414        self
415    }
416    #[allow(dead_code)]
417    pub fn max_iter(mut self, n: u32) -> Self {
418        self.max_iterations = n;
419        self
420    }
421}
422/// JavaScript expression for code generation.
423#[derive(Debug, Clone, PartialEq)]
424pub enum JsExpr {
425    /// A literal value: `42n`, `"hello"`, `true`, etc.
426    Lit(JsLit),
427    /// A variable identifier: `x`, `_t0`, `Nat_add`
428    Var(std::string::String),
429    /// A function call: `f(a, b, c)`
430    Call(Box<JsExpr>, Vec<JsExpr>),
431    /// A method call: `obj.method(a, b)`
432    Method(Box<JsExpr>, std::string::String, Vec<JsExpr>),
433    /// Property access: `obj.field`
434    Field(Box<JsExpr>, std::string::String),
435    /// Array index: `arr[i]`
436    Index(Box<JsExpr>, Box<JsExpr>),
437    /// Arrow function: `(x, y) => { stmts }`
438    Arrow(Vec<std::string::String>, Box<JsStmt>),
439    /// Ternary expression: `cond ? then_expr : else_expr`
440    Ternary(Box<JsExpr>, Box<JsExpr>, Box<JsExpr>),
441    /// Binary operator: `lhs + rhs`, `a === b`, etc.
442    BinOp(std::string::String, Box<JsExpr>, Box<JsExpr>),
443    /// Unary operator: `!x`, `-n`, `typeof x`
444    UnOp(std::string::String, Box<JsExpr>),
445    /// Await expression: `await promise`
446    Await(Box<JsExpr>),
447    /// Constructor call: `new MyClass(a, b)`
448    New(std::string::String, Vec<JsExpr>),
449    /// Spread expression: `...arr`
450    Spread(Box<JsExpr>),
451    /// Object literal: `{ key: val, ... }`
452    Object(Vec<(std::string::String, JsExpr)>),
453    /// Array literal: `[a, b, c]`
454    Array(Vec<JsExpr>),
455}
456#[allow(dead_code)]
457#[derive(Debug, Clone)]
458pub struct JSAnalysisCache {
459    pub(super) entries: std::collections::HashMap<String, JSCacheEntry>,
460    pub(super) max_size: usize,
461    pub(super) hits: u64,
462    pub(super) misses: u64,
463}
464impl JSAnalysisCache {
465    #[allow(dead_code)]
466    pub fn new(max_size: usize) -> Self {
467        JSAnalysisCache {
468            entries: std::collections::HashMap::new(),
469            max_size,
470            hits: 0,
471            misses: 0,
472        }
473    }
474    #[allow(dead_code)]
475    pub fn get(&mut self, key: &str) -> Option<&JSCacheEntry> {
476        if self.entries.contains_key(key) {
477            self.hits += 1;
478            self.entries.get(key)
479        } else {
480            self.misses += 1;
481            None
482        }
483    }
484    #[allow(dead_code)]
485    pub fn insert(&mut self, key: String, data: Vec<u8>) {
486        if self.entries.len() >= self.max_size {
487            if let Some(oldest) = self.entries.keys().next().cloned() {
488                self.entries.remove(&oldest);
489            }
490        }
491        self.entries.insert(
492            key.clone(),
493            JSCacheEntry {
494                key,
495                data,
496                timestamp: 0,
497                valid: true,
498            },
499        );
500    }
501    #[allow(dead_code)]
502    pub fn invalidate(&mut self, key: &str) {
503        if let Some(entry) = self.entries.get_mut(key) {
504            entry.valid = false;
505        }
506    }
507    #[allow(dead_code)]
508    pub fn clear(&mut self) {
509        self.entries.clear();
510    }
511    #[allow(dead_code)]
512    pub fn hit_rate(&self) -> f64 {
513        let total = self.hits + self.misses;
514        if total == 0 {
515            return 0.0;
516        }
517        self.hits as f64 / total as f64
518    }
519    #[allow(dead_code)]
520    pub fn size(&self) -> usize {
521        self.entries.len()
522    }
523}
524/// A simple source map associating generated positions with source functions.
525#[allow(dead_code)]
526#[derive(Debug, Clone, Default)]
527pub struct JsSourceMap {
528    pub(super) entries: Vec<SourceMapEntry>,
529}
530impl JsSourceMap {
531    /// Create a new empty source map.
532    #[allow(dead_code)]
533    pub fn new() -> Self {
534        Self::default()
535    }
536    /// Add an entry.
537    #[allow(dead_code)]
538    pub fn add(&mut self, entry: SourceMapEntry) {
539        self.entries.push(entry);
540    }
541    /// Number of entries.
542    #[allow(dead_code)]
543    pub fn len(&self) -> usize {
544        self.entries.len()
545    }
546    /// Whether the source map is empty.
547    #[allow(dead_code)]
548    pub fn is_empty(&self) -> bool {
549        self.entries.is_empty()
550    }
551    /// Find entries for a given generated line.
552    #[allow(dead_code)]
553    pub fn entries_for_line(&self, gen_line: u32) -> Vec<&SourceMapEntry> {
554        self.entries
555            .iter()
556            .filter(|e| e.gen_line == gen_line)
557            .collect()
558    }
559}
560/// Pretty-prints a `JsModule` with configurable indentation and line width.
561#[allow(dead_code)]
562pub struct JsPrettyPrinter {
563    /// Indentation width (spaces per level).
564    pub indent_width: usize,
565    /// Target line width (for soft wrapping heuristics).
566    pub line_width: usize,
567    /// Whether to emit trailing commas in objects/arrays.
568    pub trailing_commas: bool,
569}
570impl JsPrettyPrinter {
571    /// Create a new pretty-printer with standard settings.
572    #[allow(dead_code)]
573    pub fn new() -> Self {
574        JsPrettyPrinter {
575            indent_width: 2,
576            line_width: 80,
577            trailing_commas: false,
578        }
579    }
580    /// Pretty-print a `JsModule`.
581    #[allow(dead_code)]
582    pub fn print_module(&self, module: &JsModule) -> std::string::String {
583        module.emit()
584    }
585    /// Pretty-print a single `JsFunction`.
586    #[allow(dead_code)]
587    pub fn print_function(&self, func: &JsFunction) -> std::string::String {
588        func.to_string()
589    }
590    /// Pretty-print a `JsExpr` at the given indentation depth.
591    #[allow(dead_code)]
592    pub fn print_expr(&self, expr: &JsExpr, _depth: usize) -> std::string::String {
593        expr.to_string()
594    }
595}
596#[allow(dead_code)]
597#[derive(Debug, Clone)]
598pub struct JSCacheEntry {
599    pub key: String,
600    pub data: Vec<u8>,
601    pub timestamp: u64,
602    pub valid: bool,
603}
604#[allow(dead_code)]
605#[derive(Debug, Clone)]
606pub struct JSWorklist {
607    pub(super) items: std::collections::VecDeque<u32>,
608    pub(super) in_worklist: std::collections::HashSet<u32>,
609}
610impl JSWorklist {
611    #[allow(dead_code)]
612    pub fn new() -> Self {
613        JSWorklist {
614            items: std::collections::VecDeque::new(),
615            in_worklist: std::collections::HashSet::new(),
616        }
617    }
618    #[allow(dead_code)]
619    pub fn push(&mut self, item: u32) -> bool {
620        if self.in_worklist.insert(item) {
621            self.items.push_back(item);
622            true
623        } else {
624            false
625        }
626    }
627    #[allow(dead_code)]
628    pub fn pop(&mut self) -> Option<u32> {
629        let item = self.items.pop_front()?;
630        self.in_worklist.remove(&item);
631        Some(item)
632    }
633    #[allow(dead_code)]
634    pub fn is_empty(&self) -> bool {
635        self.items.is_empty()
636    }
637    #[allow(dead_code)]
638    pub fn len(&self) -> usize {
639        self.items.len()
640    }
641    #[allow(dead_code)]
642    pub fn contains(&self, item: u32) -> bool {
643        self.in_worklist.contains(&item)
644    }
645}
646/// JavaScript code generation backend.
647///
648/// Compiles LCNF function declarations to a `JsModule` containing
649/// ES2020+ JavaScript functions.
650pub struct JsBackend {
651    /// The module being built.
652    pub module: JsModule,
653    /// Mapping from LCNF names to mangled JS names.
654    pub fn_map: HashMap<std::string::String, std::string::String>,
655    /// Counter for generating fresh temporary variable names.
656    pub fresh_counter: usize,
657}
658impl JsBackend {
659    /// Create a new JS backend.
660    pub fn new() -> Self {
661        JsBackend {
662            module: JsModule::new(),
663            fn_map: HashMap::new(),
664            fresh_counter: 0,
665        }
666    }
667    /// Generate a fresh temporary variable name: `_t0`, `_t1`, etc.
668    pub fn fresh_var(&mut self) -> std::string::String {
669        let n = self.fresh_counter;
670        self.fresh_counter += 1;
671        format!("_t{}", n)
672    }
673    /// Mangle an LCNF name into a valid JavaScript identifier.
674    ///
675    /// Rules:
676    /// - Replace `.` with `_`
677    /// - Replace `'` (prime) with `_prime`
678    /// - Prefix reserved words with `_`
679    pub fn mangle_name(&self, name: &str) -> std::string::String {
680        let mangled: std::string::String = name
681            .chars()
682            .map(|c| match c {
683                '.' => '_',
684                '\'' => '_',
685                '-' => '_',
686                c if c.is_alphanumeric() || c == '_' => c,
687                _ => '_',
688            })
689            .collect();
690        if JS_KEYWORDS.contains(&mangled.as_str())
691            || mangled.starts_with(|c: char| c.is_ascii_digit())
692        {
693            format!("_{}", mangled)
694        } else if mangled.is_empty() {
695            "_anon".to_string()
696        } else {
697            mangled
698        }
699    }
700    /// Top-level entry point: compile a slice of LCNF function declarations
701    /// into a JavaScript module string.
702    pub fn compile_module(
703        decls: &[LcnfFunDecl],
704    ) -> Result<std::string::String, std::string::String> {
705        let mut backend = JsBackend::new();
706        for decl in decls {
707            let js_name = backend.mangle_name(&decl.name);
708            backend.fn_map.insert(decl.name.clone(), js_name);
709        }
710        for decl in decls {
711            let func = backend.compile_decl(decl)?;
712            backend.module.add_function(func);
713        }
714        for decl in decls {
715            if let Some(js_name) = backend.fn_map.get(&decl.name) {
716                backend.module.add_export(js_name.clone());
717            }
718        }
719        Ok(backend.module.emit())
720    }
721    /// Compile a single LCNF function declaration into a `JsFunction`.
722    pub fn compile_decl(&mut self, decl: &LcnfFunDecl) -> Result<JsFunction, std::string::String> {
723        let js_name = self.mangle_name(&decl.name);
724        self.fn_map.insert(decl.name.clone(), js_name.clone());
725        let params: Vec<std::string::String> = decl
726            .params
727            .iter()
728            .map(|p| {
729                if p.erased {
730                    format!("_{}", self.mangle_name(&p.name))
731                } else {
732                    self.mangle_name(&p.name)
733                }
734            })
735            .collect();
736        let mut body_stmts: Vec<JsStmt> = Vec::new();
737        let result = self.compile_expr(&decl.body, &mut body_stmts)?;
738        let last_is_return = body_stmts.last().is_some_and(|s| {
739            matches!(s, JsStmt::Return(_) | JsStmt::ReturnVoid | JsStmt::Throw(_))
740        });
741        if !last_is_return {
742            body_stmts.push(JsStmt::Return(result));
743        }
744        Ok(JsFunction {
745            name: js_name,
746            params,
747            body: body_stmts,
748            is_async: false,
749            is_export: false,
750        })
751    }
752    /// Compile an LCNF expression into a sequence of JS statements,
753    /// returning the JS expression that holds the result.
754    pub fn compile_expr(
755        &mut self,
756        expr: &LcnfExpr,
757        stmts: &mut Vec<JsStmt>,
758    ) -> Result<JsExpr, std::string::String> {
759        match expr {
760            LcnfExpr::Let {
761                id: _,
762                name,
763                ty: _,
764                value,
765                body,
766            } => {
767                let js_val = self.compile_let_value(value, stmts)?;
768                let js_name = self.mangle_name(name);
769                stmts.push(JsStmt::Const(js_name.clone(), js_val));
770                self.compile_expr(body, stmts)
771            }
772            LcnfExpr::Case {
773                scrutinee,
774                scrutinee_ty: _,
775                alts,
776                default,
777            } => {
778                let scrutinee_name = format!("_x{}", scrutinee.0);
779                let tag_expr = JsExpr::Field(
780                    Box::new(JsExpr::Var(scrutinee_name.clone())),
781                    "tag".to_string(),
782                );
783                if alts.len() == 1 && alts[0].ctor_name.is_empty() {
784                    let alt = &alts[0];
785                    for (i, param) in alt.params.iter().enumerate() {
786                        let field_expr = JsExpr::Call(
787                            Box::new(JsExpr::Field(
788                                Box::new(JsExpr::Var("_OL".to_string())),
789                                "proj".to_string(),
790                            )),
791                            vec![
792                                JsExpr::Var(scrutinee_name.clone()),
793                                JsExpr::Lit(JsLit::Num(i as f64)),
794                            ],
795                        );
796                        let pname = self.mangle_name(&param.name);
797                        stmts.push(JsStmt::Const(pname, field_expr));
798                    }
799                    return self.compile_expr(&alt.body, stmts);
800                }
801                let result_var = self.fresh_var();
802                stmts.push(JsStmt::Let(
803                    result_var.clone(),
804                    JsExpr::Lit(JsLit::Undefined),
805                ));
806                let mut cases: Vec<(JsExpr, Vec<JsStmt>)> = Vec::new();
807                for alt in alts {
808                    let mut case_stmts: Vec<JsStmt> = Vec::new();
809                    for (i, param) in alt.params.iter().enumerate() {
810                        let field_expr = JsExpr::Call(
811                            Box::new(JsExpr::Field(
812                                Box::new(JsExpr::Var("_OL".to_string())),
813                                "proj".to_string(),
814                            )),
815                            vec![
816                                JsExpr::Var(scrutinee_name.clone()),
817                                JsExpr::Lit(JsLit::Num(i as f64)),
818                            ],
819                        );
820                        let pname = self.mangle_name(&param.name);
821                        case_stmts.push(JsStmt::Const(pname, field_expr));
822                    }
823                    let branch_result = self.compile_expr(&alt.body, &mut case_stmts)?;
824                    case_stmts.push(JsStmt::Expr(JsExpr::BinOp(
825                        "=".to_string(),
826                        Box::new(JsExpr::Var(result_var.clone())),
827                        Box::new(branch_result),
828                    )));
829                    let tag_lit = JsExpr::Lit(JsLit::Str(alt.ctor_name.clone()));
830                    cases.push((tag_lit, case_stmts));
831                }
832                let default_stmts = if let Some(def) = default {
833                    let mut def_stmts: Vec<JsStmt> = Vec::new();
834                    let def_result = self.compile_expr(def, &mut def_stmts)?;
835                    def_stmts.push(JsStmt::Expr(JsExpr::BinOp(
836                        "=".to_string(),
837                        Box::new(JsExpr::Var(result_var.clone())),
838                        Box::new(def_result),
839                    )));
840                    def_stmts
841                } else {
842                    vec![JsStmt::Throw(JsExpr::New(
843                        "Error".to_string(),
844                        vec![JsExpr::Lit(JsLit::Str("Unreachable case".to_string()))],
845                    ))]
846                };
847                stmts.push(JsStmt::Switch(tag_expr, cases, default_stmts));
848                Ok(JsExpr::Var(result_var))
849            }
850            LcnfExpr::Return(arg) => Ok(self.compile_arg(arg)),
851            LcnfExpr::Unreachable => {
852                stmts.push(JsStmt::Throw(JsExpr::New(
853                    "Error".to_string(),
854                    vec![JsExpr::Lit(JsLit::Str("Unreachable".to_string()))],
855                )));
856                Ok(JsExpr::Lit(JsLit::Undefined))
857            }
858            LcnfExpr::TailCall(func, args) => {
859                let js_func = self.compile_arg(func);
860                let js_args: Vec<JsExpr> = args.iter().map(|a| self.compile_arg(a)).collect();
861                Ok(JsExpr::Call(Box::new(js_func), js_args))
862            }
863        }
864    }
865    /// Compile an LCNF let-value into a JS expression.
866    pub(super) fn compile_let_value(
867        &mut self,
868        value: &LcnfLetValue,
869        stmts: &mut Vec<JsStmt>,
870    ) -> Result<JsExpr, std::string::String> {
871        match value {
872            LcnfLetValue::Lit(lit) => Ok(self.compile_lit(lit)),
873            LcnfLetValue::App(func, args) => {
874                let js_func = self.compile_arg(func);
875                let js_args: Vec<JsExpr> = args.iter().map(|a| self.compile_arg(a)).collect();
876                Ok(JsExpr::Call(Box::new(js_func), js_args))
877            }
878            LcnfLetValue::Ctor(name, tag, args) => {
879                let mut ctor_args = vec![JsExpr::Lit(JsLit::Str(name.clone()))];
880                for a in args {
881                    ctor_args.push(self.compile_arg(a));
882                }
883                let _ = tag;
884                Ok(JsExpr::Call(
885                    Box::new(JsExpr::Field(
886                        Box::new(JsExpr::Var("_OL".to_string())),
887                        "ctor".to_string(),
888                    )),
889                    ctor_args,
890                ))
891            }
892            LcnfLetValue::Proj(_, idx, var) => {
893                let var_expr = JsExpr::Var(format!("_x{}", var.0));
894                Ok(JsExpr::Call(
895                    Box::new(JsExpr::Field(
896                        Box::new(JsExpr::Var("_OL".to_string())),
897                        "proj".to_string(),
898                    )),
899                    vec![var_expr, JsExpr::Lit(JsLit::Num(*idx as f64))],
900                ))
901            }
902            LcnfLetValue::Erased => Ok(JsExpr::Lit(JsLit::Undefined)),
903            LcnfLetValue::FVar(id) => Ok(JsExpr::Var(format!("_x{}", id.0))),
904            LcnfLetValue::Reset(_var) => Ok(JsExpr::Lit(JsLit::Null)),
905            LcnfLetValue::Reuse(_slot, name, _tag, args) => {
906                let mut ctor_args = vec![JsExpr::Lit(JsLit::Str(name.clone()))];
907                for a in args {
908                    ctor_args.push(self.compile_arg(a));
909                }
910                let _ = stmts;
911                Ok(JsExpr::Call(
912                    Box::new(JsExpr::Field(
913                        Box::new(JsExpr::Var("_OL".to_string())),
914                        "ctor".to_string(),
915                    )),
916                    ctor_args,
917                ))
918            }
919        }
920    }
921    /// Compile an LCNF argument (atomic value) into a JS expression.
922    pub fn compile_arg(&self, arg: &LcnfArg) -> JsExpr {
923        match arg {
924            LcnfArg::Var(id) => JsExpr::Var(format!("_x{}", id.0)),
925            LcnfArg::Lit(lit) => self.compile_lit(lit),
926            LcnfArg::Erased => JsExpr::Lit(JsLit::Undefined),
927            LcnfArg::Type(_) => JsExpr::Lit(JsLit::Undefined),
928        }
929    }
930    /// Compile an LCNF literal into a JS expression.
931    pub fn compile_lit(&self, lit: &LcnfLit) -> JsExpr {
932        match lit {
933            LcnfLit::Nat(n) => JsExpr::Lit(JsLit::BigInt(*n as i64)),
934            LcnfLit::Int(i) => JsExpr::Lit(JsLit::BigInt(*i)),
935            LcnfLit::Str(s) => JsExpr::Lit(JsLit::Str(s.clone())),
936        }
937    }
938}
939#[allow(dead_code)]
940pub struct JSConstantFoldingHelper;
941impl JSConstantFoldingHelper {
942    #[allow(dead_code)]
943    pub fn fold_add_i64(a: i64, b: i64) -> Option<i64> {
944        a.checked_add(b)
945    }
946    #[allow(dead_code)]
947    pub fn fold_sub_i64(a: i64, b: i64) -> Option<i64> {
948        a.checked_sub(b)
949    }
950    #[allow(dead_code)]
951    pub fn fold_mul_i64(a: i64, b: i64) -> Option<i64> {
952        a.checked_mul(b)
953    }
954    #[allow(dead_code)]
955    pub fn fold_div_i64(a: i64, b: i64) -> Option<i64> {
956        if b == 0 {
957            None
958        } else {
959            a.checked_div(b)
960        }
961    }
962    #[allow(dead_code)]
963    pub fn fold_add_f64(a: f64, b: f64) -> f64 {
964        a + b
965    }
966    #[allow(dead_code)]
967    pub fn fold_mul_f64(a: f64, b: f64) -> f64 {
968        a * b
969    }
970    #[allow(dead_code)]
971    pub fn fold_neg_i64(a: i64) -> Option<i64> {
972        a.checked_neg()
973    }
974    #[allow(dead_code)]
975    pub fn fold_not_bool(a: bool) -> bool {
976        !a
977    }
978    #[allow(dead_code)]
979    pub fn fold_and_bool(a: bool, b: bool) -> bool {
980        a && b
981    }
982    #[allow(dead_code)]
983    pub fn fold_or_bool(a: bool, b: bool) -> bool {
984        a || b
985    }
986    #[allow(dead_code)]
987    pub fn fold_shl_i64(a: i64, b: u32) -> Option<i64> {
988        a.checked_shl(b)
989    }
990    #[allow(dead_code)]
991    pub fn fold_shr_i64(a: i64, b: u32) -> Option<i64> {
992        a.checked_shr(b)
993    }
994    #[allow(dead_code)]
995    pub fn fold_rem_i64(a: i64, b: i64) -> Option<i64> {
996        if b == 0 {
997            None
998        } else {
999            Some(a % b)
1000        }
1001    }
1002    #[allow(dead_code)]
1003    pub fn fold_bitand_i64(a: i64, b: i64) -> i64 {
1004        a & b
1005    }
1006    #[allow(dead_code)]
1007    pub fn fold_bitor_i64(a: i64, b: i64) -> i64 {
1008        a | b
1009    }
1010    #[allow(dead_code)]
1011    pub fn fold_bitxor_i64(a: i64, b: i64) -> i64 {
1012        a ^ b
1013    }
1014    #[allow(dead_code)]
1015    pub fn fold_bitnot_i64(a: i64) -> i64 {
1016        !a
1017    }
1018}
1019/// Extended name mangler with support for module namespaces.
1020#[allow(dead_code)]
1021pub struct JsNameMangler {
1022    /// Namespace prefix (e.g., "Lean" for Lean library functions).
1023    pub namespace: std::string::String,
1024    /// Whether to preserve original capitalization.
1025    pub preserve_case: bool,
1026}
1027impl JsNameMangler {
1028    /// Create a new name mangler.
1029    #[allow(dead_code)]
1030    pub fn new(namespace: &str) -> Self {
1031        JsNameMangler {
1032            namespace: namespace.to_string(),
1033            preserve_case: true,
1034        }
1035    }
1036    /// Mangle a name with optional namespace prefix.
1037    #[allow(dead_code)]
1038    pub fn mangle(&self, name: &str) -> std::string::String {
1039        let backend = JsBackend::new();
1040        let mangled = backend.mangle_name(name);
1041        if self.namespace.is_empty() {
1042            mangled
1043        } else {
1044            format!("{}_{}", self.namespace, mangled)
1045        }
1046    }
1047    /// Mangle a qualified name like `Nat.add` → `Lean_Nat_add`.
1048    #[allow(dead_code)]
1049    pub fn mangle_qualified(&self, parts: &[&str]) -> std::string::String {
1050        let joined = parts.join(".");
1051        self.mangle(&joined)
1052    }
1053}
1054/// A single source-map entry mapping an output position to a source position.
1055#[allow(dead_code)]
1056#[derive(Debug, Clone)]
1057pub struct SourceMapEntry {
1058    /// Generated (output) line number (0-based).
1059    pub gen_line: u32,
1060    /// Generated (output) column number (0-based).
1061    pub gen_col: u32,
1062    /// Original function name (if known).
1063    pub source_fn: std::string::String,
1064    /// Original source line (for display; may be 0 if unavailable).
1065    pub source_line: u32,
1066}
1067impl SourceMapEntry {
1068    /// Create a new source map entry.
1069    #[allow(dead_code)]
1070    pub fn new(gen_line: u32, gen_col: u32, source_fn: &str, source_line: u32) -> Self {
1071        SourceMapEntry {
1072            gen_line,
1073            gen_col,
1074            source_fn: source_fn.to_string(),
1075            source_line,
1076        }
1077    }
1078}
1079#[allow(dead_code)]
1080#[derive(Debug, Clone, PartialEq)]
1081pub enum JSPassPhase {
1082    Analysis,
1083    Transformation,
1084    Verification,
1085    Cleanup,
1086}
1087impl JSPassPhase {
1088    #[allow(dead_code)]
1089    pub fn name(&self) -> &str {
1090        match self {
1091            JSPassPhase::Analysis => "analysis",
1092            JSPassPhase::Transformation => "transformation",
1093            JSPassPhase::Verification => "verification",
1094            JSPassPhase::Cleanup => "cleanup",
1095        }
1096    }
1097    #[allow(dead_code)]
1098    pub fn is_modifying(&self) -> bool {
1099        matches!(self, JSPassPhase::Transformation | JSPassPhase::Cleanup)
1100    }
1101}
1102/// A table of all identifiers used in a JS module, for rename-on-collision.
1103#[allow(dead_code)]
1104#[derive(Debug, Clone, Default)]
1105pub struct JsIdentTable {
1106    /// All identifiers currently in scope.
1107    pub(super) idents: std::collections::HashSet<std::string::String>,
1108    /// Collision counter per base name.
1109    pub(super) collisions: std::collections::HashMap<std::string::String, usize>,
1110}
1111impl JsIdentTable {
1112    /// Create a new empty ident table.
1113    #[allow(dead_code)]
1114    pub fn new() -> Self {
1115        Self::default()
1116    }
1117    /// Register an identifier, returning a collision-free version.
1118    #[allow(dead_code)]
1119    pub fn register(&mut self, name: &str) -> std::string::String {
1120        if self.idents.contains(name) {
1121            let count = self.collisions.entry(name.to_string()).or_insert(0);
1122            *count += 1;
1123            let renamed = format!("{}_{}", name, count);
1124            self.idents.insert(renamed.clone());
1125            renamed
1126        } else {
1127            self.idents.insert(name.to_string());
1128            name.to_string()
1129        }
1130    }
1131    /// Whether a name is already taken.
1132    #[allow(dead_code)]
1133    pub fn is_taken(&self, name: &str) -> bool {
1134        self.idents.contains(name)
1135    }
1136    /// Number of registered identifiers.
1137    #[allow(dead_code)]
1138    pub fn len(&self) -> usize {
1139        self.idents.len()
1140    }
1141    /// Whether the table is empty.
1142    #[allow(dead_code)]
1143    pub fn is_empty(&self) -> bool {
1144        self.idents.is_empty()
1145    }
1146}
1147/// JavaScript statement for code generation.
1148#[derive(Debug, Clone, PartialEq)]
1149pub enum JsStmt {
1150    /// Expression statement: `expr;`
1151    Expr(JsExpr),
1152    /// Let binding: `let x = expr;`
1153    Let(std::string::String, JsExpr),
1154    /// Const binding: `const x = expr;`
1155    Const(std::string::String, JsExpr),
1156    /// Return statement: `return expr;`
1157    Return(JsExpr),
1158    /// Void return: `return;`
1159    ReturnVoid,
1160    /// If-else: `if (cond) { then } else { else_ }`
1161    If(JsExpr, Vec<JsStmt>, Vec<JsStmt>),
1162    /// While loop: `while (cond) { body }`
1163    While(JsExpr, Vec<JsStmt>),
1164    /// For-of loop: `for (const x of iter) { body }`
1165    For(std::string::String, JsExpr, Vec<JsStmt>),
1166    /// Block: `{ stmts }`
1167    Block(Vec<JsStmt>),
1168    /// Throw statement: `throw expr;`
1169    Throw(JsExpr),
1170    /// Try-catch: `try { body } catch (e) { handler }`
1171    TryCatch(Vec<JsStmt>, std::string::String, Vec<JsStmt>),
1172    /// Switch statement: `switch (expr) { case ...: ... default: ... }`
1173    Switch(JsExpr, Vec<(JsExpr, Vec<JsStmt>)>, Vec<JsStmt>),
1174}
1175/// Links multiple `JsModule` objects into a single output.
1176#[allow(dead_code)]
1177pub struct JsModuleLinker {
1178    pub(super) modules: Vec<JsModule>,
1179}
1180impl JsModuleLinker {
1181    /// Create a new linker.
1182    #[allow(dead_code)]
1183    pub fn new() -> Self {
1184        JsModuleLinker {
1185            modules: Vec::new(),
1186        }
1187    }
1188    /// Add a module to link.
1189    #[allow(dead_code)]
1190    pub fn add_module(&mut self, module: JsModule) {
1191        self.modules.push(module);
1192    }
1193    /// Link all added modules into a single `JsModule`.
1194    ///
1195    /// Functions from all modules are merged; preamble lines are deduplicated;
1196    /// exports from all modules are combined.
1197    #[allow(dead_code)]
1198    pub fn link(&self) -> JsModule {
1199        let mut combined = JsModule::new();
1200        let mut seen_preamble: std::collections::HashSet<std::string::String> =
1201            std::collections::HashSet::new();
1202        for module in &self.modules {
1203            for line in &module.preamble {
1204                if seen_preamble.insert(line.clone()) {
1205                    combined.preamble.push(line.clone());
1206                }
1207            }
1208            for func in &module.functions {
1209                combined.functions.push(func.clone());
1210            }
1211            for name in &module.exports {
1212                if !combined.exports.contains(name) {
1213                    combined.exports.push(name.clone());
1214                }
1215            }
1216        }
1217        combined
1218    }
1219    /// Number of modules.
1220    #[allow(dead_code)]
1221    pub fn len(&self) -> usize {
1222        self.modules.len()
1223    }
1224    /// Whether no modules have been added.
1225    #[allow(dead_code)]
1226    pub fn is_empty(&self) -> bool {
1227        self.modules.is_empty()
1228    }
1229}
1230/// Configuration for the JS backend.
1231#[allow(dead_code)]
1232#[derive(Debug, Clone)]
1233pub struct JsBackendConfig {
1234    /// Whether to use BigInt for Nat (default: true).
1235    pub use_bigint_for_nat: bool,
1236    /// Whether to emit strict mode (`"use strict";`).
1237    pub strict_mode: bool,
1238    /// Whether to include the runtime preamble.
1239    pub include_runtime: bool,
1240    /// Whether to emit JSDoc comments for functions.
1241    pub emit_jsdoc: bool,
1242    /// Module format: `es` (ES modules) or `cjs` (CommonJS).
1243    pub module_format: JsModuleFormat,
1244    /// Whether to minify the output.
1245    pub minify: bool,
1246}
1247/// JavaScript module output format.
1248#[allow(dead_code)]
1249#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1250pub enum JsModuleFormat {
1251    /// ES2020 modules (`export`/`import`).
1252    Es,
1253    /// CommonJS (`module.exports`/`require`).
1254    Cjs,
1255    /// No module wrapper (IIFE or bare script).
1256    None,
1257}
1258/// JavaScript type representation for type-directed code generation.
1259#[derive(Debug, Clone, PartialEq, Eq, Hash)]
1260pub enum JsType {
1261    /// `undefined`
1262    Undefined,
1263    /// `null`
1264    Null,
1265    /// `boolean`
1266    Boolean,
1267    /// `number` (64-bit float)
1268    Number,
1269    /// `bigint` (arbitrary-precision integer, used for Nat)
1270    BigInt,
1271    /// `string`
1272    String,
1273    /// `object` (heap-allocated record or constructor)
1274    Object,
1275    /// `Array` (JS Array)
1276    Array,
1277    /// `function`
1278    Function,
1279    /// Unknown or polymorphic type
1280    Unknown,
1281}
1282/// Context maintained during JS code emission.
1283#[allow(dead_code)]
1284pub struct JsEmitContext {
1285    /// Current indentation level.
1286    pub indent_level: usize,
1287    /// Indentation string per level.
1288    pub indent_str: std::string::String,
1289    /// Source map being built.
1290    pub source_map: JsSourceMap,
1291    /// Current output line number.
1292    pub current_line: u32,
1293    /// Current output column.
1294    pub current_col: u32,
1295    /// Whether we are inside an async function.
1296    pub in_async: bool,
1297}
1298impl JsEmitContext {
1299    /// Create a new emit context.
1300    #[allow(dead_code)]
1301    pub fn new(indent: &str) -> Self {
1302        JsEmitContext {
1303            indent_level: 0,
1304            indent_str: indent.to_string(),
1305            source_map: JsSourceMap::new(),
1306            current_line: 0,
1307            current_col: 0,
1308            in_async: false,
1309        }
1310    }
1311    /// Get the current indentation string.
1312    #[allow(dead_code)]
1313    pub fn indent(&self) -> std::string::String {
1314        self.indent_str.repeat(self.indent_level)
1315    }
1316    /// Increase indentation.
1317    #[allow(dead_code)]
1318    pub fn push_indent(&mut self) {
1319        self.indent_level += 1;
1320    }
1321    /// Decrease indentation.
1322    #[allow(dead_code)]
1323    pub fn pop_indent(&mut self) {
1324        if self.indent_level > 0 {
1325            self.indent_level -= 1;
1326        }
1327    }
1328    /// Emit a newline, updating line/column counters.
1329    #[allow(dead_code)]
1330    pub fn newline(&mut self) {
1331        self.current_line += 1;
1332        self.current_col = 0;
1333    }
1334    /// Record a source map entry for the current position.
1335    #[allow(dead_code)]
1336    pub fn record_mapping(&mut self, fn_name: &str, source_line: u32) {
1337        self.source_map.add(SourceMapEntry::new(
1338            self.current_line,
1339            self.current_col,
1340            fn_name,
1341            source_line,
1342        ));
1343    }
1344}
1345#[allow(dead_code)]
1346#[derive(Debug, Clone, Default)]
1347pub struct JSPassStats {
1348    pub total_runs: u32,
1349    pub successful_runs: u32,
1350    pub total_changes: u64,
1351    pub time_ms: u64,
1352    pub iterations_used: u32,
1353}
1354impl JSPassStats {
1355    #[allow(dead_code)]
1356    pub fn new() -> Self {
1357        Self::default()
1358    }
1359    #[allow(dead_code)]
1360    pub fn record_run(&mut self, changes: u64, time_ms: u64, iterations: u32) {
1361        self.total_runs += 1;
1362        self.successful_runs += 1;
1363        self.total_changes += changes;
1364        self.time_ms += time_ms;
1365        self.iterations_used = iterations;
1366    }
1367    #[allow(dead_code)]
1368    pub fn average_changes_per_run(&self) -> f64 {
1369        if self.total_runs == 0 {
1370            return 0.0;
1371        }
1372        self.total_changes as f64 / self.total_runs as f64
1373    }
1374    #[allow(dead_code)]
1375    pub fn success_rate(&self) -> f64 {
1376        if self.total_runs == 0 {
1377            return 0.0;
1378        }
1379        self.successful_runs as f64 / self.total_runs as f64
1380    }
1381    #[allow(dead_code)]
1382    pub fn format_summary(&self) -> String {
1383        format!(
1384            "Runs: {}/{}, Changes: {}, Time: {}ms",
1385            self.successful_runs, self.total_runs, self.total_changes, self.time_ms
1386        )
1387    }
1388}
1389#[allow(dead_code)]
1390#[derive(Debug, Clone)]
1391pub struct JSLivenessInfo {
1392    pub live_in: Vec<std::collections::HashSet<u32>>,
1393    pub live_out: Vec<std::collections::HashSet<u32>>,
1394    pub defs: Vec<std::collections::HashSet<u32>>,
1395    pub uses: Vec<std::collections::HashSet<u32>>,
1396}
1397impl JSLivenessInfo {
1398    #[allow(dead_code)]
1399    pub fn new(block_count: usize) -> Self {
1400        JSLivenessInfo {
1401            live_in: vec![std::collections::HashSet::new(); block_count],
1402            live_out: vec![std::collections::HashSet::new(); block_count],
1403            defs: vec![std::collections::HashSet::new(); block_count],
1404            uses: vec![std::collections::HashSet::new(); block_count],
1405        }
1406    }
1407    #[allow(dead_code)]
1408    pub fn add_def(&mut self, block: usize, var: u32) {
1409        if block < self.defs.len() {
1410            self.defs[block].insert(var);
1411        }
1412    }
1413    #[allow(dead_code)]
1414    pub fn add_use(&mut self, block: usize, var: u32) {
1415        if block < self.uses.len() {
1416            self.uses[block].insert(var);
1417        }
1418    }
1419    #[allow(dead_code)]
1420    pub fn is_live_in(&self, block: usize, var: u32) -> bool {
1421        self.live_in
1422            .get(block)
1423            .map(|s| s.contains(&var))
1424            .unwrap_or(false)
1425    }
1426    #[allow(dead_code)]
1427    pub fn is_live_out(&self, block: usize, var: u32) -> bool {
1428        self.live_out
1429            .get(block)
1430            .map(|s| s.contains(&var))
1431            .unwrap_or(false)
1432    }
1433}
1434#[allow(dead_code)]
1435#[derive(Debug, Clone)]
1436pub struct JSDepGraph {
1437    pub(super) nodes: Vec<u32>,
1438    pub(super) edges: Vec<(u32, u32)>,
1439}
1440impl JSDepGraph {
1441    #[allow(dead_code)]
1442    pub fn new() -> Self {
1443        JSDepGraph {
1444            nodes: Vec::new(),
1445            edges: Vec::new(),
1446        }
1447    }
1448    #[allow(dead_code)]
1449    pub fn add_node(&mut self, id: u32) {
1450        if !self.nodes.contains(&id) {
1451            self.nodes.push(id);
1452        }
1453    }
1454    #[allow(dead_code)]
1455    pub fn add_dep(&mut self, dep: u32, dependent: u32) {
1456        self.add_node(dep);
1457        self.add_node(dependent);
1458        self.edges.push((dep, dependent));
1459    }
1460    #[allow(dead_code)]
1461    pub fn dependents_of(&self, node: u32) -> Vec<u32> {
1462        self.edges
1463            .iter()
1464            .filter(|(d, _)| *d == node)
1465            .map(|(_, dep)| *dep)
1466            .collect()
1467    }
1468    #[allow(dead_code)]
1469    pub fn dependencies_of(&self, node: u32) -> Vec<u32> {
1470        self.edges
1471            .iter()
1472            .filter(|(_, dep)| *dep == node)
1473            .map(|(d, _)| *d)
1474            .collect()
1475    }
1476    #[allow(dead_code)]
1477    pub fn topological_sort(&self) -> Vec<u32> {
1478        let mut in_degree: std::collections::HashMap<u32, u32> = std::collections::HashMap::new();
1479        for &n in &self.nodes {
1480            in_degree.insert(n, 0);
1481        }
1482        for (_, dep) in &self.edges {
1483            *in_degree.entry(*dep).or_insert(0) += 1;
1484        }
1485        let mut queue: std::collections::VecDeque<u32> = self
1486            .nodes
1487            .iter()
1488            .filter(|&&n| in_degree[&n] == 0)
1489            .copied()
1490            .collect();
1491        let mut result = Vec::new();
1492        while let Some(node) = queue.pop_front() {
1493            result.push(node);
1494            for dep in self.dependents_of(node) {
1495                let cnt = in_degree.entry(dep).or_insert(0);
1496                *cnt = cnt.saturating_sub(1);
1497                if *cnt == 0 {
1498                    queue.push_back(dep);
1499                }
1500            }
1501        }
1502        result
1503    }
1504    #[allow(dead_code)]
1505    pub fn has_cycle(&self) -> bool {
1506        self.topological_sort().len() < self.nodes.len()
1507    }
1508}