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::Str(s) => JsExpr::Lit(JsLit::Str(s.clone())),
935        }
936    }
937}
938#[allow(dead_code)]
939pub struct JSConstantFoldingHelper;
940impl JSConstantFoldingHelper {
941    #[allow(dead_code)]
942    pub fn fold_add_i64(a: i64, b: i64) -> Option<i64> {
943        a.checked_add(b)
944    }
945    #[allow(dead_code)]
946    pub fn fold_sub_i64(a: i64, b: i64) -> Option<i64> {
947        a.checked_sub(b)
948    }
949    #[allow(dead_code)]
950    pub fn fold_mul_i64(a: i64, b: i64) -> Option<i64> {
951        a.checked_mul(b)
952    }
953    #[allow(dead_code)]
954    pub fn fold_div_i64(a: i64, b: i64) -> Option<i64> {
955        if b == 0 {
956            None
957        } else {
958            a.checked_div(b)
959        }
960    }
961    #[allow(dead_code)]
962    pub fn fold_add_f64(a: f64, b: f64) -> f64 {
963        a + b
964    }
965    #[allow(dead_code)]
966    pub fn fold_mul_f64(a: f64, b: f64) -> f64 {
967        a * b
968    }
969    #[allow(dead_code)]
970    pub fn fold_neg_i64(a: i64) -> Option<i64> {
971        a.checked_neg()
972    }
973    #[allow(dead_code)]
974    pub fn fold_not_bool(a: bool) -> bool {
975        !a
976    }
977    #[allow(dead_code)]
978    pub fn fold_and_bool(a: bool, b: bool) -> bool {
979        a && b
980    }
981    #[allow(dead_code)]
982    pub fn fold_or_bool(a: bool, b: bool) -> bool {
983        a || b
984    }
985    #[allow(dead_code)]
986    pub fn fold_shl_i64(a: i64, b: u32) -> Option<i64> {
987        a.checked_shl(b)
988    }
989    #[allow(dead_code)]
990    pub fn fold_shr_i64(a: i64, b: u32) -> Option<i64> {
991        a.checked_shr(b)
992    }
993    #[allow(dead_code)]
994    pub fn fold_rem_i64(a: i64, b: i64) -> Option<i64> {
995        if b == 0 {
996            None
997        } else {
998            Some(a % b)
999        }
1000    }
1001    #[allow(dead_code)]
1002    pub fn fold_bitand_i64(a: i64, b: i64) -> i64 {
1003        a & b
1004    }
1005    #[allow(dead_code)]
1006    pub fn fold_bitor_i64(a: i64, b: i64) -> i64 {
1007        a | b
1008    }
1009    #[allow(dead_code)]
1010    pub fn fold_bitxor_i64(a: i64, b: i64) -> i64 {
1011        a ^ b
1012    }
1013    #[allow(dead_code)]
1014    pub fn fold_bitnot_i64(a: i64) -> i64 {
1015        !a
1016    }
1017}
1018/// Extended name mangler with support for module namespaces.
1019#[allow(dead_code)]
1020pub struct JsNameMangler {
1021    /// Namespace prefix (e.g., "Lean" for Lean library functions).
1022    pub namespace: std::string::String,
1023    /// Whether to preserve original capitalization.
1024    pub preserve_case: bool,
1025}
1026impl JsNameMangler {
1027    /// Create a new name mangler.
1028    #[allow(dead_code)]
1029    pub fn new(namespace: &str) -> Self {
1030        JsNameMangler {
1031            namespace: namespace.to_string(),
1032            preserve_case: true,
1033        }
1034    }
1035    /// Mangle a name with optional namespace prefix.
1036    #[allow(dead_code)]
1037    pub fn mangle(&self, name: &str) -> std::string::String {
1038        let backend = JsBackend::new();
1039        let mangled = backend.mangle_name(name);
1040        if self.namespace.is_empty() {
1041            mangled
1042        } else {
1043            format!("{}_{}", self.namespace, mangled)
1044        }
1045    }
1046    /// Mangle a qualified name like `Nat.add` → `Lean_Nat_add`.
1047    #[allow(dead_code)]
1048    pub fn mangle_qualified(&self, parts: &[&str]) -> std::string::String {
1049        let joined = parts.join(".");
1050        self.mangle(&joined)
1051    }
1052}
1053/// A single source-map entry mapping an output position to a source position.
1054#[allow(dead_code)]
1055#[derive(Debug, Clone)]
1056pub struct SourceMapEntry {
1057    /// Generated (output) line number (0-based).
1058    pub gen_line: u32,
1059    /// Generated (output) column number (0-based).
1060    pub gen_col: u32,
1061    /// Original function name (if known).
1062    pub source_fn: std::string::String,
1063    /// Original source line (for display; may be 0 if unavailable).
1064    pub source_line: u32,
1065}
1066impl SourceMapEntry {
1067    /// Create a new source map entry.
1068    #[allow(dead_code)]
1069    pub fn new(gen_line: u32, gen_col: u32, source_fn: &str, source_line: u32) -> Self {
1070        SourceMapEntry {
1071            gen_line,
1072            gen_col,
1073            source_fn: source_fn.to_string(),
1074            source_line,
1075        }
1076    }
1077}
1078#[allow(dead_code)]
1079#[derive(Debug, Clone, PartialEq)]
1080pub enum JSPassPhase {
1081    Analysis,
1082    Transformation,
1083    Verification,
1084    Cleanup,
1085}
1086impl JSPassPhase {
1087    #[allow(dead_code)]
1088    pub fn name(&self) -> &str {
1089        match self {
1090            JSPassPhase::Analysis => "analysis",
1091            JSPassPhase::Transformation => "transformation",
1092            JSPassPhase::Verification => "verification",
1093            JSPassPhase::Cleanup => "cleanup",
1094        }
1095    }
1096    #[allow(dead_code)]
1097    pub fn is_modifying(&self) -> bool {
1098        matches!(self, JSPassPhase::Transformation | JSPassPhase::Cleanup)
1099    }
1100}
1101/// A table of all identifiers used in a JS module, for rename-on-collision.
1102#[allow(dead_code)]
1103#[derive(Debug, Clone, Default)]
1104pub struct JsIdentTable {
1105    /// All identifiers currently in scope.
1106    pub(super) idents: std::collections::HashSet<std::string::String>,
1107    /// Collision counter per base name.
1108    pub(super) collisions: std::collections::HashMap<std::string::String, usize>,
1109}
1110impl JsIdentTable {
1111    /// Create a new empty ident table.
1112    #[allow(dead_code)]
1113    pub fn new() -> Self {
1114        Self::default()
1115    }
1116    /// Register an identifier, returning a collision-free version.
1117    #[allow(dead_code)]
1118    pub fn register(&mut self, name: &str) -> std::string::String {
1119        if self.idents.contains(name) {
1120            let count = self.collisions.entry(name.to_string()).or_insert(0);
1121            *count += 1;
1122            let renamed = format!("{}_{}", name, count);
1123            self.idents.insert(renamed.clone());
1124            renamed
1125        } else {
1126            self.idents.insert(name.to_string());
1127            name.to_string()
1128        }
1129    }
1130    /// Whether a name is already taken.
1131    #[allow(dead_code)]
1132    pub fn is_taken(&self, name: &str) -> bool {
1133        self.idents.contains(name)
1134    }
1135    /// Number of registered identifiers.
1136    #[allow(dead_code)]
1137    pub fn len(&self) -> usize {
1138        self.idents.len()
1139    }
1140    /// Whether the table is empty.
1141    #[allow(dead_code)]
1142    pub fn is_empty(&self) -> bool {
1143        self.idents.is_empty()
1144    }
1145}
1146/// JavaScript statement for code generation.
1147#[derive(Debug, Clone, PartialEq)]
1148pub enum JsStmt {
1149    /// Expression statement: `expr;`
1150    Expr(JsExpr),
1151    /// Let binding: `let x = expr;`
1152    Let(std::string::String, JsExpr),
1153    /// Const binding: `const x = expr;`
1154    Const(std::string::String, JsExpr),
1155    /// Return statement: `return expr;`
1156    Return(JsExpr),
1157    /// Void return: `return;`
1158    ReturnVoid,
1159    /// If-else: `if (cond) { then } else { else_ }`
1160    If(JsExpr, Vec<JsStmt>, Vec<JsStmt>),
1161    /// While loop: `while (cond) { body }`
1162    While(JsExpr, Vec<JsStmt>),
1163    /// For-of loop: `for (const x of iter) { body }`
1164    For(std::string::String, JsExpr, Vec<JsStmt>),
1165    /// Block: `{ stmts }`
1166    Block(Vec<JsStmt>),
1167    /// Throw statement: `throw expr;`
1168    Throw(JsExpr),
1169    /// Try-catch: `try { body } catch (e) { handler }`
1170    TryCatch(Vec<JsStmt>, std::string::String, Vec<JsStmt>),
1171    /// Switch statement: `switch (expr) { case ...: ... default: ... }`
1172    Switch(JsExpr, Vec<(JsExpr, Vec<JsStmt>)>, Vec<JsStmt>),
1173}
1174/// Links multiple `JsModule` objects into a single output.
1175#[allow(dead_code)]
1176pub struct JsModuleLinker {
1177    pub(super) modules: Vec<JsModule>,
1178}
1179impl JsModuleLinker {
1180    /// Create a new linker.
1181    #[allow(dead_code)]
1182    pub fn new() -> Self {
1183        JsModuleLinker {
1184            modules: Vec::new(),
1185        }
1186    }
1187    /// Add a module to link.
1188    #[allow(dead_code)]
1189    pub fn add_module(&mut self, module: JsModule) {
1190        self.modules.push(module);
1191    }
1192    /// Link all added modules into a single `JsModule`.
1193    ///
1194    /// Functions from all modules are merged; preamble lines are deduplicated;
1195    /// exports from all modules are combined.
1196    #[allow(dead_code)]
1197    pub fn link(&self) -> JsModule {
1198        let mut combined = JsModule::new();
1199        let mut seen_preamble: std::collections::HashSet<std::string::String> =
1200            std::collections::HashSet::new();
1201        for module in &self.modules {
1202            for line in &module.preamble {
1203                if seen_preamble.insert(line.clone()) {
1204                    combined.preamble.push(line.clone());
1205                }
1206            }
1207            for func in &module.functions {
1208                combined.functions.push(func.clone());
1209            }
1210            for name in &module.exports {
1211                if !combined.exports.contains(name) {
1212                    combined.exports.push(name.clone());
1213                }
1214            }
1215        }
1216        combined
1217    }
1218    /// Number of modules.
1219    #[allow(dead_code)]
1220    pub fn len(&self) -> usize {
1221        self.modules.len()
1222    }
1223    /// Whether no modules have been added.
1224    #[allow(dead_code)]
1225    pub fn is_empty(&self) -> bool {
1226        self.modules.is_empty()
1227    }
1228}
1229/// Configuration for the JS backend.
1230#[allow(dead_code)]
1231#[derive(Debug, Clone)]
1232pub struct JsBackendConfig {
1233    /// Whether to use BigInt for Nat (default: true).
1234    pub use_bigint_for_nat: bool,
1235    /// Whether to emit strict mode (`"use strict";`).
1236    pub strict_mode: bool,
1237    /// Whether to include the runtime preamble.
1238    pub include_runtime: bool,
1239    /// Whether to emit JSDoc comments for functions.
1240    pub emit_jsdoc: bool,
1241    /// Module format: `es` (ES modules) or `cjs` (CommonJS).
1242    pub module_format: JsModuleFormat,
1243    /// Whether to minify the output.
1244    pub minify: bool,
1245}
1246/// JavaScript module output format.
1247#[allow(dead_code)]
1248#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1249pub enum JsModuleFormat {
1250    /// ES2020 modules (`export`/`import`).
1251    Es,
1252    /// CommonJS (`module.exports`/`require`).
1253    Cjs,
1254    /// No module wrapper (IIFE or bare script).
1255    None,
1256}
1257/// JavaScript type representation for type-directed code generation.
1258#[derive(Debug, Clone, PartialEq, Eq, Hash)]
1259pub enum JsType {
1260    /// `undefined`
1261    Undefined,
1262    /// `null`
1263    Null,
1264    /// `boolean`
1265    Boolean,
1266    /// `number` (64-bit float)
1267    Number,
1268    /// `bigint` (arbitrary-precision integer, used for Nat)
1269    BigInt,
1270    /// `string`
1271    String,
1272    /// `object` (heap-allocated record or constructor)
1273    Object,
1274    /// `Array` (JS Array)
1275    Array,
1276    /// `function`
1277    Function,
1278    /// Unknown or polymorphic type
1279    Unknown,
1280}
1281/// Context maintained during JS code emission.
1282#[allow(dead_code)]
1283pub struct JsEmitContext {
1284    /// Current indentation level.
1285    pub indent_level: usize,
1286    /// Indentation string per level.
1287    pub indent_str: std::string::String,
1288    /// Source map being built.
1289    pub source_map: JsSourceMap,
1290    /// Current output line number.
1291    pub current_line: u32,
1292    /// Current output column.
1293    pub current_col: u32,
1294    /// Whether we are inside an async function.
1295    pub in_async: bool,
1296}
1297impl JsEmitContext {
1298    /// Create a new emit context.
1299    #[allow(dead_code)]
1300    pub fn new(indent: &str) -> Self {
1301        JsEmitContext {
1302            indent_level: 0,
1303            indent_str: indent.to_string(),
1304            source_map: JsSourceMap::new(),
1305            current_line: 0,
1306            current_col: 0,
1307            in_async: false,
1308        }
1309    }
1310    /// Get the current indentation string.
1311    #[allow(dead_code)]
1312    pub fn indent(&self) -> std::string::String {
1313        self.indent_str.repeat(self.indent_level)
1314    }
1315    /// Increase indentation.
1316    #[allow(dead_code)]
1317    pub fn push_indent(&mut self) {
1318        self.indent_level += 1;
1319    }
1320    /// Decrease indentation.
1321    #[allow(dead_code)]
1322    pub fn pop_indent(&mut self) {
1323        if self.indent_level > 0 {
1324            self.indent_level -= 1;
1325        }
1326    }
1327    /// Emit a newline, updating line/column counters.
1328    #[allow(dead_code)]
1329    pub fn newline(&mut self) {
1330        self.current_line += 1;
1331        self.current_col = 0;
1332    }
1333    /// Record a source map entry for the current position.
1334    #[allow(dead_code)]
1335    pub fn record_mapping(&mut self, fn_name: &str, source_line: u32) {
1336        self.source_map.add(SourceMapEntry::new(
1337            self.current_line,
1338            self.current_col,
1339            fn_name,
1340            source_line,
1341        ));
1342    }
1343}
1344#[allow(dead_code)]
1345#[derive(Debug, Clone, Default)]
1346pub struct JSPassStats {
1347    pub total_runs: u32,
1348    pub successful_runs: u32,
1349    pub total_changes: u64,
1350    pub time_ms: u64,
1351    pub iterations_used: u32,
1352}
1353impl JSPassStats {
1354    #[allow(dead_code)]
1355    pub fn new() -> Self {
1356        Self::default()
1357    }
1358    #[allow(dead_code)]
1359    pub fn record_run(&mut self, changes: u64, time_ms: u64, iterations: u32) {
1360        self.total_runs += 1;
1361        self.successful_runs += 1;
1362        self.total_changes += changes;
1363        self.time_ms += time_ms;
1364        self.iterations_used = iterations;
1365    }
1366    #[allow(dead_code)]
1367    pub fn average_changes_per_run(&self) -> f64 {
1368        if self.total_runs == 0 {
1369            return 0.0;
1370        }
1371        self.total_changes as f64 / self.total_runs as f64
1372    }
1373    #[allow(dead_code)]
1374    pub fn success_rate(&self) -> f64 {
1375        if self.total_runs == 0 {
1376            return 0.0;
1377        }
1378        self.successful_runs as f64 / self.total_runs as f64
1379    }
1380    #[allow(dead_code)]
1381    pub fn format_summary(&self) -> String {
1382        format!(
1383            "Runs: {}/{}, Changes: {}, Time: {}ms",
1384            self.successful_runs, self.total_runs, self.total_changes, self.time_ms
1385        )
1386    }
1387}
1388#[allow(dead_code)]
1389#[derive(Debug, Clone)]
1390pub struct JSLivenessInfo {
1391    pub live_in: Vec<std::collections::HashSet<u32>>,
1392    pub live_out: Vec<std::collections::HashSet<u32>>,
1393    pub defs: Vec<std::collections::HashSet<u32>>,
1394    pub uses: Vec<std::collections::HashSet<u32>>,
1395}
1396impl JSLivenessInfo {
1397    #[allow(dead_code)]
1398    pub fn new(block_count: usize) -> Self {
1399        JSLivenessInfo {
1400            live_in: vec![std::collections::HashSet::new(); block_count],
1401            live_out: vec![std::collections::HashSet::new(); block_count],
1402            defs: vec![std::collections::HashSet::new(); block_count],
1403            uses: vec![std::collections::HashSet::new(); block_count],
1404        }
1405    }
1406    #[allow(dead_code)]
1407    pub fn add_def(&mut self, block: usize, var: u32) {
1408        if block < self.defs.len() {
1409            self.defs[block].insert(var);
1410        }
1411    }
1412    #[allow(dead_code)]
1413    pub fn add_use(&mut self, block: usize, var: u32) {
1414        if block < self.uses.len() {
1415            self.uses[block].insert(var);
1416        }
1417    }
1418    #[allow(dead_code)]
1419    pub fn is_live_in(&self, block: usize, var: u32) -> bool {
1420        self.live_in
1421            .get(block)
1422            .map(|s| s.contains(&var))
1423            .unwrap_or(false)
1424    }
1425    #[allow(dead_code)]
1426    pub fn is_live_out(&self, block: usize, var: u32) -> bool {
1427        self.live_out
1428            .get(block)
1429            .map(|s| s.contains(&var))
1430            .unwrap_or(false)
1431    }
1432}
1433#[allow(dead_code)]
1434#[derive(Debug, Clone)]
1435pub struct JSDepGraph {
1436    pub(super) nodes: Vec<u32>,
1437    pub(super) edges: Vec<(u32, u32)>,
1438}
1439impl JSDepGraph {
1440    #[allow(dead_code)]
1441    pub fn new() -> Self {
1442        JSDepGraph {
1443            nodes: Vec::new(),
1444            edges: Vec::new(),
1445        }
1446    }
1447    #[allow(dead_code)]
1448    pub fn add_node(&mut self, id: u32) {
1449        if !self.nodes.contains(&id) {
1450            self.nodes.push(id);
1451        }
1452    }
1453    #[allow(dead_code)]
1454    pub fn add_dep(&mut self, dep: u32, dependent: u32) {
1455        self.add_node(dep);
1456        self.add_node(dependent);
1457        self.edges.push((dep, dependent));
1458    }
1459    #[allow(dead_code)]
1460    pub fn dependents_of(&self, node: u32) -> Vec<u32> {
1461        self.edges
1462            .iter()
1463            .filter(|(d, _)| *d == node)
1464            .map(|(_, dep)| *dep)
1465            .collect()
1466    }
1467    #[allow(dead_code)]
1468    pub fn dependencies_of(&self, node: u32) -> Vec<u32> {
1469        self.edges
1470            .iter()
1471            .filter(|(_, dep)| *dep == node)
1472            .map(|(d, _)| *d)
1473            .collect()
1474    }
1475    #[allow(dead_code)]
1476    pub fn topological_sort(&self) -> Vec<u32> {
1477        let mut in_degree: std::collections::HashMap<u32, u32> = std::collections::HashMap::new();
1478        for &n in &self.nodes {
1479            in_degree.insert(n, 0);
1480        }
1481        for (_, dep) in &self.edges {
1482            *in_degree.entry(*dep).or_insert(0) += 1;
1483        }
1484        let mut queue: std::collections::VecDeque<u32> = self
1485            .nodes
1486            .iter()
1487            .filter(|&&n| in_degree[&n] == 0)
1488            .copied()
1489            .collect();
1490        let mut result = Vec::new();
1491        while let Some(node) = queue.pop_front() {
1492            result.push(node);
1493            for dep in self.dependents_of(node) {
1494                let cnt = in_degree.entry(dep).or_insert(0);
1495                *cnt = cnt.saturating_sub(1);
1496                if *cnt == 0 {
1497                    queue.push_back(dep);
1498                }
1499            }
1500        }
1501        result
1502    }
1503    #[allow(dead_code)]
1504    pub fn has_cycle(&self) -> bool {
1505        self.topological_sort().len() < self.nodes.len()
1506    }
1507}