Skip to main content

oxilean_codegen/js_backend/
functions.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::types::{
9    JSAnalysisCache, JSConstantFoldingHelper, JSDepGraph, JSDominatorTree, JSLivenessInfo,
10    JSPassConfig, JSPassPhase, JSPassRegistry, JSPassStats, JSWorklist, JsBackend, JsBackendConfig,
11    JsEmitContext, JsExpr, JsFunction, JsIdentTable, JsLit, JsMinifier, JsModule, JsModuleFormat,
12    JsModuleLinker, JsNameMangler, JsPeephole, JsPrettyPrinter, JsSizeEstimator, JsSourceMap,
13    JsStmt, JsType, JsTypeChecker, SourceMapEntry,
14};
15
16/// Format a list of statements with the given indentation level.
17pub fn display_indented(stmts: &[JsStmt], indent: usize) -> std::string::String {
18    let pad = " ".repeat(indent);
19    let mut out = std::string::String::new();
20    for stmt in stmts {
21        let text = format_stmt_indented(stmt, indent);
22        for line in text.lines() {
23            out.push_str(&pad);
24            out.push_str(line);
25            out.push('\n');
26        }
27    }
28    if out.ends_with('\n') {
29        out.pop();
30    }
31    out
32}
33/// Format a single statement with the given base indentation.
34pub(super) fn format_stmt_indented(stmt: &JsStmt, indent: usize) -> std::string::String {
35    let pad = " ".repeat(indent);
36    let inner_pad = " ".repeat(indent + 2);
37    match stmt {
38        JsStmt::Expr(e) => format!("{};", e),
39        JsStmt::Let(name, expr) => format!("let {} = {};", name, expr),
40        JsStmt::Const(name, expr) => format!("const {} = {};", name, expr),
41        JsStmt::Return(e) => format!("return {};", e),
42        JsStmt::ReturnVoid => "return;".to_string(),
43        JsStmt::If(cond, then_stmts, else_stmts) => {
44            let then_body = display_indented_with_pad(then_stmts, indent + 2, &inner_pad);
45            let mut s = format!("if ({}) {{\n{}\n{}}}", cond, then_body, pad);
46            if !else_stmts.is_empty() {
47                let else_body = display_indented_with_pad(else_stmts, indent + 2, &inner_pad);
48                s.push_str(&format!(" else {{\n{}\n{}}}", else_body, pad));
49            }
50            s
51        }
52        JsStmt::While(cond, body) => {
53            let body_text = display_indented_with_pad(body, indent + 2, &inner_pad);
54            format!("while ({}) {{\n{}\n{}}}", cond, body_text, pad)
55        }
56        JsStmt::For(var, iter, body) => {
57            let body_text = display_indented_with_pad(body, indent + 2, &inner_pad);
58            format!(
59                "for (const {} of {}) {{\n{}\n{}}}",
60                var, iter, body_text, pad
61            )
62        }
63        JsStmt::Block(stmts) => {
64            let body = display_indented_with_pad(stmts, indent + 2, &inner_pad);
65            format!("{{\n{}\n{}}}", body, pad)
66        }
67        JsStmt::Throw(e) => format!("throw {};", e),
68        JsStmt::TryCatch(try_body, catch_var, catch_body) => {
69            let try_text = display_indented_with_pad(try_body, indent + 2, &inner_pad);
70            let catch_text = display_indented_with_pad(catch_body, indent + 2, &inner_pad);
71            format!(
72                "try {{\n{}\n{}}} catch ({}) {{\n{}\n{}}}",
73                try_text, pad, catch_var, catch_text, pad
74            )
75        }
76        JsStmt::Switch(expr, cases, default) => {
77            let mut s = format!("switch ({}) {{\n", expr);
78            for (case_expr, case_stmts) in cases {
79                s.push_str(&format!("{}  case {}:\n", pad, case_expr));
80                for cs in case_stmts {
81                    s.push_str(&format!(
82                        "{}    {}\n",
83                        pad,
84                        format_stmt_indented(cs, indent + 4)
85                    ));
86                }
87                s.push_str(&format!("{}    break;\n", pad));
88            }
89            if !default.is_empty() {
90                s.push_str(&format!("{}  default:\n", pad));
91                for ds in default {
92                    s.push_str(&format!(
93                        "{}    {}\n",
94                        pad,
95                        format_stmt_indented(ds, indent + 4)
96                    ));
97                }
98            }
99            s.push_str(&format!("{}}}", pad));
100            s
101        }
102    }
103}
104/// Helper: format statements with explicit inner padding.
105pub(super) fn display_indented_with_pad(
106    stmts: &[JsStmt],
107    _indent: usize,
108    pad: &str,
109) -> std::string::String {
110    let mut out = std::string::String::new();
111    for stmt in stmts {
112        let text = format_stmt_indented(stmt, _indent);
113        for line in text.lines() {
114            out.push_str(pad);
115            out.push_str(line);
116            out.push('\n');
117        }
118    }
119    if out.ends_with('\n') {
120        out.pop();
121    }
122    out
123}
124/// The minimal OxiLean JavaScript runtime, prepended to every module.
125pub const JS_RUNTIME: &str = r#"// OxiLean JS Runtime
126const _OL = {
127  natAdd: (a, b) => a + b,
128  natMul: (a, b) => a * b,
129  natSub: (a, b) => a >= b ? a - b : 0n,
130  natDiv: (a, b) => b === 0n ? 0n : a / b,
131  natMod: (a, b) => b === 0n ? 0n : a % b,
132  natLt: (a, b) => a < b,
133  natLe: (a, b) => a <= b,
134  natEq: (a, b) => a === b,
135  strAppend: (a, b) => a + b,
136  strLength: (s) => BigInt(s.length),
137  ctor: (tag, ...fields) => ({ tag, fields }),
138  proj: (obj, i) => obj.fields[i],
139  panic: (msg) => { throw new Error(msg); },
140};"#;
141/// JavaScript reserved words that must not be used as identifiers.
142pub const JS_KEYWORDS: &[&str] = &[
143    "break",
144    "case",
145    "catch",
146    "class",
147    "const",
148    "continue",
149    "debugger",
150    "default",
151    "delete",
152    "do",
153    "else",
154    "export",
155    "extends",
156    "false",
157    "finally",
158    "for",
159    "function",
160    "if",
161    "import",
162    "in",
163    "instanceof",
164    "let",
165    "new",
166    "null",
167    "return",
168    "static",
169    "super",
170    "switch",
171    "this",
172    "throw",
173    "true",
174    "try",
175    "typeof",
176    "undefined",
177    "var",
178    "void",
179    "while",
180    "with",
181    "yield",
182    "await",
183    "async",
184    "of",
185    "from",
186    "get",
187    "set",
188    "target",
189    "meta",
190];
191#[cfg(test)]
192mod tests {
193    use super::*;
194    #[test]
195    pub(super) fn test_js_expr_display() {
196        let expr = JsExpr::BinOp(
197            "+".to_string(),
198            Box::new(JsExpr::Lit(JsLit::Num(1.0))),
199            Box::new(JsExpr::Lit(JsLit::Num(2.0))),
200        );
201        assert_eq!(expr.to_string(), "1 + 2");
202    }
203    #[test]
204    pub(super) fn test_js_lit_bigint_display() {
205        let lit = JsLit::BigInt(42);
206        assert_eq!(lit.to_string(), "42n");
207        let lit_zero = JsLit::BigInt(0);
208        assert_eq!(lit_zero.to_string(), "0n");
209    }
210    #[test]
211    pub(super) fn test_js_lit_str_escape() {
212        let lit = JsLit::Str("hello \"world\"\nnewline".to_string());
213        assert_eq!(lit.to_string(), "\"hello \\\"world\\\"\\nnewline\"");
214    }
215    #[test]
216    pub(super) fn test_js_function_display() {
217        let func = JsFunction {
218            name: "add".to_string(),
219            params: vec!["a".to_string(), "b".to_string()],
220            body: vec![JsStmt::Return(JsExpr::BinOp(
221                "+".to_string(),
222                Box::new(JsExpr::Var("a".to_string())),
223                Box::new(JsExpr::Var("b".to_string())),
224            ))],
225            is_async: false,
226            is_export: false,
227        };
228        let s = func.to_string();
229        assert!(s.contains("function add(a, b)"));
230        assert!(s.contains("return a + b;"));
231    }
232    #[test]
233    pub(super) fn test_js_async_export_function_display() {
234        let func = JsFunction {
235            name: "fetchData".to_string(),
236            params: vec!["url".to_string()],
237            body: vec![JsStmt::ReturnVoid],
238            is_async: true,
239            is_export: true,
240        };
241        let s = func.to_string();
242        assert!(s.starts_with("export async function fetchData(url)"));
243    }
244    #[test]
245    pub(super) fn test_mangle_name() {
246        let backend = JsBackend::new();
247        assert_eq!(backend.mangle_name("Nat.add"), "Nat_add");
248        assert_eq!(backend.mangle_name("List.cons"), "List_cons");
249        assert_eq!(backend.mangle_name("return"), "_return");
250        assert_eq!(backend.mangle_name("class"), "_class");
251        assert_eq!(backend.mangle_name("foo'"), "foo_");
252        assert_eq!(backend.mangle_name(""), "_anon");
253    }
254    #[test]
255    pub(super) fn test_compile_simple_decl() {
256        let decl = LcnfFunDecl {
257            name: "answer".to_string(),
258            original_name: None,
259            params: vec![],
260            ret_type: LcnfType::Nat,
261            body: LcnfExpr::Return(LcnfArg::Lit(LcnfLit::Nat(42))),
262            is_recursive: false,
263            is_lifted: false,
264            inline_cost: 0,
265        };
266        let mut backend = JsBackend::new();
267        let func = backend.compile_decl(&decl).expect("compile_decl failed");
268        assert_eq!(func.name, "answer");
269        assert!(func.params.is_empty());
270        let s = func.to_string();
271        assert!(s.contains("42n"), "Expected BigInt literal 42n, got: {}", s);
272    }
273    #[test]
274    pub(super) fn test_compile_let() {
275        let x_id = LcnfVarId(0);
276        let y_id = LcnfVarId(1);
277        let decl = LcnfFunDecl {
278            name: "double".to_string(),
279            original_name: None,
280            params: vec![LcnfParam {
281                id: x_id,
282                name: "x".to_string(),
283                ty: LcnfType::Nat,
284                erased: false,
285                borrowed: false,
286            }],
287            ret_type: LcnfType::Nat,
288            body: LcnfExpr::Let {
289                id: y_id,
290                name: "y".to_string(),
291                ty: LcnfType::Nat,
292                value: LcnfLetValue::App(LcnfArg::Var(x_id), vec![LcnfArg::Var(x_id)]),
293                body: Box::new(LcnfExpr::Return(LcnfArg::Var(y_id))),
294            },
295            is_recursive: false,
296            is_lifted: false,
297            inline_cost: 0,
298        };
299        let mut backend = JsBackend::new();
300        let func = backend.compile_decl(&decl).expect("compile_decl failed");
301        let s = func.to_string();
302        assert!(
303            s.contains("function double"),
304            "Expected function double, got: {}",
305            s
306        );
307        assert!(
308            s.contains("const y"),
309            "Expected const y binding, got: {}",
310            s
311        );
312    }
313    #[test]
314    pub(super) fn test_emit_module() {
315        let decl = LcnfFunDecl {
316            name: "main".to_string(),
317            original_name: None,
318            params: vec![],
319            ret_type: LcnfType::Unit,
320            body: LcnfExpr::Return(LcnfArg::Erased),
321            is_recursive: false,
322            is_lifted: false,
323            inline_cost: 0,
324        };
325        let js = JsBackend::compile_module(&[decl]).expect("compile_module failed");
326        assert!(js.contains("const _OL ="), "Missing runtime preamble");
327        assert!(js.contains("natAdd"), "Missing natAdd in runtime");
328        assert!(js.contains("function main()"), "Missing main function");
329        assert!(js.contains("export {"), "Missing export statement");
330        assert!(js.contains("main"), "Missing 'main' in exports");
331    }
332    #[test]
333    pub(super) fn test_js_object_expr_display() {
334        let expr = JsExpr::Object(vec![
335            (
336                "tag".to_string(),
337                JsExpr::Lit(JsLit::Str("Some".to_string())),
338            ),
339            (
340                "fields".to_string(),
341                JsExpr::Array(vec![JsExpr::Lit(JsLit::BigInt(1))]),
342            ),
343        ]);
344        let s = expr.to_string();
345        assert!(s.contains("tag: \"Some\""));
346        assert!(s.contains("fields: [1n]"));
347    }
348    #[test]
349    pub(super) fn test_js_ternary_display() {
350        let expr = JsExpr::Ternary(
351            Box::new(JsExpr::Var("cond".to_string())),
352            Box::new(JsExpr::Lit(JsLit::Num(1.0))),
353            Box::new(JsExpr::Lit(JsLit::Num(0.0))),
354        );
355        assert_eq!(expr.to_string(), "(cond) ? (1) : (0)");
356    }
357    #[test]
358    pub(super) fn test_js_type_display() {
359        assert_eq!(JsType::BigInt.to_string(), "bigint");
360        assert_eq!(JsType::Boolean.to_string(), "boolean");
361        assert_eq!(JsType::Unknown.to_string(), "unknown");
362    }
363    #[test]
364    pub(super) fn test_switch_stmt_display() {
365        let stmt = JsStmt::Switch(
366            JsExpr::Var("x".to_string()),
367            vec![(
368                JsExpr::Lit(JsLit::Str("Some".to_string())),
369                vec![JsStmt::Return(JsExpr::Lit(JsLit::Bool(true)))],
370            )],
371            vec![JsStmt::Return(JsExpr::Lit(JsLit::Bool(false)))],
372        );
373        let s = stmt.to_string();
374        assert!(s.contains("switch (x)"));
375        assert!(s.contains("case \"Some\":"));
376        assert!(s.contains("default:"));
377    }
378}
379#[cfg(test)]
380mod js_extended_tests {
381    use super::*;
382    #[test]
383    pub(super) fn test_source_map_entry_new() {
384        let entry = SourceMapEntry::new(10, 5, "my_fn", 42);
385        assert_eq!(entry.gen_line, 10);
386        assert_eq!(entry.gen_col, 5);
387        assert_eq!(entry.source_fn, "my_fn");
388        assert_eq!(entry.source_line, 42);
389    }
390    #[test]
391    pub(super) fn test_source_map_entry_display() {
392        let entry = SourceMapEntry::new(1, 0, "f", 10);
393        let s = entry.to_string();
394        assert!(s.contains("1:0"));
395        assert!(s.contains("f:10"));
396    }
397    #[test]
398    pub(super) fn test_js_source_map_new() {
399        let sm = JsSourceMap::new();
400        assert!(sm.is_empty());
401    }
402    #[test]
403    pub(super) fn test_js_source_map_add_and_query() {
404        let mut sm = JsSourceMap::new();
405        sm.add(SourceMapEntry::new(5, 0, "add", 1));
406        sm.add(SourceMapEntry::new(5, 10, "sub", 2));
407        sm.add(SourceMapEntry::new(6, 0, "mul", 3));
408        assert_eq!(sm.len(), 3);
409        assert_eq!(sm.entries_for_line(5).len(), 2);
410        assert_eq!(sm.entries_for_line(6).len(), 1);
411        assert!(sm.entries_for_line(99).is_empty());
412    }
413    #[test]
414    pub(super) fn test_js_minifier_strip_comments() {
415        let source = "const x = 1; // this is a comment\nconst y = 2;\n";
416        let minified = JsMinifier::minify(source);
417        assert!(!minified.contains("// this is a comment"));
418        assert!(minified.contains("const x = 1;"));
419    }
420    #[test]
421    pub(super) fn test_js_minifier_empty_lines() {
422        let source = "\n\n\nconst z = 3;\n\n";
423        let minified = JsMinifier::minify(source);
424        assert!(minified.contains("const z = 3;"));
425    }
426    #[test]
427    pub(super) fn test_js_minifier_strip_block_comments() {
428        let source = "const a = /* inline comment */ 5;";
429        let stripped = JsMinifier::strip_block_comments(source);
430        assert!(!stripped.contains("inline comment"));
431        assert!(stripped.contains("const a ="));
432        assert!(stripped.contains("5;"));
433    }
434    #[test]
435    pub(super) fn test_js_pretty_printer_new() {
436        let printer = JsPrettyPrinter::new();
437        assert_eq!(printer.indent_width, 2);
438        assert_eq!(printer.line_width, 80);
439    }
440    #[test]
441    pub(super) fn test_js_pretty_printer_print_function() {
442        let printer = JsPrettyPrinter::new();
443        let func = JsFunction {
444            name: "id".to_string(),
445            params: vec!["x".to_string()],
446            body: vec![JsStmt::Return(JsExpr::Var("x".to_string()))],
447            is_async: false,
448            is_export: false,
449        };
450        let s = printer.print_function(&func);
451        assert!(s.contains("function id"));
452    }
453    #[test]
454    pub(super) fn test_type_checker_lit_types() {
455        assert_eq!(JsTypeChecker::infer_lit(&JsLit::BigInt(42)), JsType::BigInt);
456        assert_eq!(
457            JsTypeChecker::infer_lit(&JsLit::Bool(true)),
458            JsType::Boolean
459        );
460        assert_eq!(
461            JsTypeChecker::infer_lit(&JsLit::Str("hi".to_string())),
462            JsType::String
463        );
464        assert_eq!(JsTypeChecker::infer_lit(&JsLit::Num(3.14)), JsType::Number);
465        assert_eq!(JsTypeChecker::infer_lit(&JsLit::Null), JsType::Null);
466        assert_eq!(
467            JsTypeChecker::infer_lit(&JsLit::Undefined),
468            JsType::Undefined
469        );
470    }
471    #[test]
472    pub(super) fn test_type_checker_binop_comparison() {
473        let expr = JsExpr::BinOp(
474            "===".to_string(),
475            Box::new(JsExpr::Lit(JsLit::Num(1.0))),
476            Box::new(JsExpr::Lit(JsLit::Num(2.0))),
477        );
478        assert_eq!(JsTypeChecker::infer_expr(&expr), JsType::Boolean);
479    }
480    #[test]
481    pub(super) fn test_type_checker_object_array() {
482        let obj = JsExpr::Object(vec![]);
483        assert_eq!(JsTypeChecker::infer_expr(&obj), JsType::Object);
484        let arr = JsExpr::Array(vec![]);
485        assert_eq!(JsTypeChecker::infer_expr(&arr), JsType::Array);
486    }
487    #[test]
488    pub(super) fn test_type_checker_arrow_function() {
489        let arrow = JsExpr::Arrow(vec![], Box::new(JsStmt::ReturnVoid));
490        assert_eq!(JsTypeChecker::infer_expr(&arrow), JsType::Function);
491    }
492    #[test]
493    pub(super) fn test_type_checker_typeof() {
494        let expr = JsExpr::UnOp("typeof".to_string(), Box::new(JsExpr::Var("x".to_string())));
495        assert_eq!(JsTypeChecker::infer_expr(&expr), JsType::String);
496    }
497    #[test]
498    pub(super) fn test_name_mangler_no_namespace() {
499        let mangler = JsNameMangler::new("");
500        assert_eq!(mangler.mangle("Nat.add"), "Nat_add");
501    }
502    #[test]
503    pub(super) fn test_name_mangler_with_namespace() {
504        let mangler = JsNameMangler::new("OL");
505        assert_eq!(mangler.mangle("add"), "OL_add");
506    }
507    #[test]
508    pub(super) fn test_name_mangler_qualified() {
509        let mangler = JsNameMangler::new("Lean");
510        let result = mangler.mangle_qualified(&["Nat", "add"]);
511        assert!(result.contains("Lean"));
512        assert!(result.contains("Nat_add"));
513    }
514    #[test]
515    pub(super) fn test_js_module_linker_new() {
516        let linker = JsModuleLinker::new();
517        assert!(linker.is_empty());
518    }
519    #[test]
520    pub(super) fn test_js_module_linker_link_empty() {
521        let linker = JsModuleLinker::new();
522        let linked = linker.link();
523        assert!(linked.functions.is_empty());
524        assert!(linked.exports.is_empty());
525    }
526    #[test]
527    pub(super) fn test_js_module_linker_link_multiple() {
528        let mut linker = JsModuleLinker::new();
529        let mut m1 = JsModule::new();
530        m1.add_function(JsFunction {
531            name: "f".to_string(),
532            params: vec![],
533            body: vec![JsStmt::ReturnVoid],
534            is_async: false,
535            is_export: false,
536        });
537        m1.add_export("f".to_string());
538        let mut m2 = JsModule::new();
539        m2.add_function(JsFunction {
540            name: "g".to_string(),
541            params: vec![],
542            body: vec![JsStmt::ReturnVoid],
543            is_async: false,
544            is_export: false,
545        });
546        m2.add_export("g".to_string());
547        linker.add_module(m1);
548        linker.add_module(m2);
549        let linked = linker.link();
550        assert_eq!(linked.functions.len(), 2);
551        assert_eq!(linked.exports.len(), 2);
552    }
553    #[test]
554    pub(super) fn test_js_module_linker_dedup_preamble() {
555        let mut linker = JsModuleLinker::new();
556        let mut m1 = JsModule::new();
557        m1.add_preamble("const X = 1;".to_string());
558        let mut m2 = JsModule::new();
559        m2.add_preamble("const X = 1;".to_string());
560        m2.add_preamble("const Y = 2;".to_string());
561        linker.add_module(m1);
562        linker.add_module(m2);
563        let linked = linker.link();
564        assert_eq!(linked.preamble.len(), 2);
565    }
566    #[test]
567    pub(super) fn test_peephole_fold_add() {
568        let expr = JsExpr::BinOp(
569            "+".to_string(),
570            Box::new(JsExpr::Lit(JsLit::Num(3.0))),
571            Box::new(JsExpr::Lit(JsLit::Num(4.0))),
572        );
573        let result = JsPeephole::fold_arith(&expr);
574        assert_eq!(result, JsExpr::Lit(JsLit::Num(7.0)));
575    }
576    #[test]
577    pub(super) fn test_peephole_fold_mul() {
578        let expr = JsExpr::BinOp(
579            "*".to_string(),
580            Box::new(JsExpr::Lit(JsLit::Num(5.0))),
581            Box::new(JsExpr::Lit(JsLit::Num(6.0))),
582        );
583        let result = JsPeephole::fold_arith(&expr);
584        assert_eq!(result, JsExpr::Lit(JsLit::Num(30.0)));
585    }
586    #[test]
587    pub(super) fn test_peephole_identity_eq() {
588        let x = JsExpr::Var("x".to_string());
589        let expr = JsExpr::BinOp("===".to_string(), Box::new(x.clone()), Box::new(x));
590        let result = JsPeephole::simplify_identity(&expr);
591        assert_eq!(result, JsExpr::Lit(JsLit::Bool(true)));
592    }
593    #[test]
594    pub(super) fn test_peephole_not_true() {
595        let expr = JsExpr::UnOp("!".to_string(), Box::new(JsExpr::Lit(JsLit::Bool(true))));
596        let result = JsPeephole::simplify_not(&expr);
597        assert_eq!(result, JsExpr::Lit(JsLit::Bool(false)));
598    }
599    #[test]
600    pub(super) fn test_peephole_not_false() {
601        let expr = JsExpr::UnOp("!".to_string(), Box::new(JsExpr::Lit(JsLit::Bool(false))));
602        let result = JsPeephole::simplify_not(&expr);
603        assert_eq!(result, JsExpr::Lit(JsLit::Bool(true)));
604    }
605    #[test]
606    pub(super) fn test_peephole_no_fold_non_numeric() {
607        let expr = JsExpr::BinOp(
608            "+".to_string(),
609            Box::new(JsExpr::Lit(JsLit::Str("a".to_string()))),
610            Box::new(JsExpr::Lit(JsLit::Str("b".to_string()))),
611        );
612        let result = JsPeephole::fold_arith(&expr);
613        assert_eq!(result, expr);
614    }
615    #[test]
616    pub(super) fn test_js_backend_config_default() {
617        let cfg = JsBackendConfig::default();
618        assert!(cfg.use_bigint_for_nat);
619        assert!(!cfg.minify);
620        assert!(cfg.include_runtime);
621        assert_eq!(cfg.module_format, JsModuleFormat::Es);
622    }
623    #[test]
624    pub(super) fn test_js_backend_config_display() {
625        let cfg = JsBackendConfig::default();
626        let s = cfg.to_string();
627        assert!(s.contains("bigint=true"));
628    }
629    #[test]
630    pub(super) fn test_js_ident_table_new() {
631        let table = JsIdentTable::new();
632        assert!(table.is_empty());
633    }
634    #[test]
635    pub(super) fn test_js_ident_table_register_unique() {
636        let mut table = JsIdentTable::new();
637        let name = table.register("add");
638        assert_eq!(name, "add");
639        assert!(table.is_taken("add"));
640    }
641    #[test]
642    pub(super) fn test_js_ident_table_register_collision() {
643        let mut table = JsIdentTable::new();
644        table.register("x");
645        let renamed = table.register("x");
646        assert_ne!(renamed, "x");
647        assert!(renamed.starts_with("x_"));
648    }
649    #[test]
650    pub(super) fn test_js_ident_table_len() {
651        let mut table = JsIdentTable::new();
652        table.register("a");
653        table.register("b");
654        table.register("c");
655        assert_eq!(table.len(), 3);
656    }
657    #[test]
658    pub(super) fn test_js_emit_context_new() {
659        let ctx = JsEmitContext::new("  ");
660        assert_eq!(ctx.indent_level, 0);
661        assert_eq!(ctx.current_line, 0);
662    }
663    #[test]
664    pub(super) fn test_js_emit_context_indent() {
665        let mut ctx = JsEmitContext::new("  ");
666        ctx.push_indent();
667        ctx.push_indent();
668        assert_eq!(ctx.indent(), "    ");
669        ctx.pop_indent();
670        assert_eq!(ctx.indent(), "  ");
671    }
672    #[test]
673    pub(super) fn test_js_emit_context_newline() {
674        let mut ctx = JsEmitContext::new("  ");
675        ctx.newline();
676        assert_eq!(ctx.current_line, 1);
677        assert_eq!(ctx.current_col, 0);
678    }
679    #[test]
680    pub(super) fn test_js_emit_context_record_mapping() {
681        let mut ctx = JsEmitContext::new("  ");
682        ctx.record_mapping("my_fn", 42);
683        assert_eq!(ctx.source_map.len(), 1);
684    }
685    #[test]
686    pub(super) fn test_js_size_estimator_expr() {
687        let expr = JsExpr::Lit(JsLit::Num(42.0));
688        let size = JsSizeEstimator::estimate_expr(&expr);
689        assert!(size > 0);
690    }
691    #[test]
692    pub(super) fn test_js_size_estimator_function() {
693        let func = JsFunction {
694            name: "f".to_string(),
695            params: vec![],
696            body: vec![JsStmt::Return(JsExpr::Lit(JsLit::Num(0.0)))],
697            is_async: false,
698            is_export: false,
699        };
700        let size = JsSizeEstimator::estimate_function(&func);
701        assert!(size > 0);
702    }
703    #[test]
704    pub(super) fn test_js_size_estimator_module() {
705        let module = JsModule::new();
706        let size = JsSizeEstimator::estimate_module(&module);
707        assert!(size > 0);
708    }
709    #[test]
710    pub(super) fn test_js_size_estimator_stmt() {
711        let stmt = JsStmt::Return(JsExpr::Lit(JsLit::Bool(true)));
712        let size = JsSizeEstimator::estimate_stmt(&stmt);
713        assert!(size > 0);
714    }
715    #[test]
716    pub(super) fn test_js_lit_bool_display() {
717        assert_eq!(JsLit::Bool(true).to_string(), "true");
718        assert_eq!(JsLit::Bool(false).to_string(), "false");
719    }
720    #[test]
721    pub(super) fn test_js_lit_null_display() {
722        assert_eq!(JsLit::Null.to_string(), "null");
723    }
724    #[test]
725    pub(super) fn test_js_lit_undefined_display() {
726        assert_eq!(JsLit::Undefined.to_string(), "undefined");
727    }
728    #[test]
729    pub(super) fn test_js_stmt_while_display() {
730        let stmt = JsStmt::While(
731            JsExpr::Lit(JsLit::Bool(true)),
732            vec![JsStmt::Return(JsExpr::Lit(JsLit::Num(0.0)))],
733        );
734        let s = stmt.to_string();
735        assert!(s.contains("while (true)"));
736    }
737    #[test]
738    pub(super) fn test_js_stmt_try_catch_display() {
739        let stmt = JsStmt::TryCatch(
740            vec![JsStmt::Expr(JsExpr::Lit(JsLit::Num(1.0)))],
741            "e".to_string(),
742            vec![JsStmt::Throw(JsExpr::Var("e".to_string()))],
743        );
744        let s = stmt.to_string();
745        assert!(s.contains("try"));
746        assert!(s.contains("catch (e)"));
747    }
748    #[test]
749    pub(super) fn test_js_stmt_for_display() {
750        let stmt = JsStmt::For(
751            "item".to_string(),
752            JsExpr::Var("items".to_string()),
753            vec![JsStmt::Expr(JsExpr::Var("item".to_string()))],
754        );
755        let s = stmt.to_string();
756        assert!(s.contains("for (const item of items)"));
757    }
758    #[test]
759    pub(super) fn test_js_expr_await_display() {
760        let expr = JsExpr::Await(Box::new(JsExpr::Var("promise".to_string())));
761        assert_eq!(expr.to_string(), "await promise");
762    }
763    #[test]
764    pub(super) fn test_js_expr_spread_display() {
765        let expr = JsExpr::Spread(Box::new(JsExpr::Var("arr".to_string())));
766        assert_eq!(expr.to_string(), "...arr");
767    }
768    #[test]
769    pub(super) fn test_js_expr_new_display() {
770        let expr = JsExpr::New("MyClass".to_string(), vec![JsExpr::Lit(JsLit::Num(1.0))]);
771        assert_eq!(expr.to_string(), "new MyClass(1)");
772    }
773    #[test]
774    pub(super) fn test_js_expr_index_display() {
775        let expr = JsExpr::Index(
776            Box::new(JsExpr::Var("arr".to_string())),
777            Box::new(JsExpr::Lit(JsLit::Num(0.0))),
778        );
779        assert_eq!(expr.to_string(), "arr[0]");
780    }
781    #[test]
782    pub(super) fn test_js_module_emit_includes_runtime() {
783        let module = JsModule::new();
784        let output = module.emit();
785        assert!(output.contains("_OL"));
786        assert!(output.contains("natAdd"));
787    }
788    #[test]
789    pub(super) fn test_js_module_emit_exports() {
790        let mut module = JsModule::new();
791        module.add_export("foo".to_string());
792        module.add_export("bar".to_string());
793        let output = module.emit();
794        assert!(output.contains("export {"));
795        assert!(output.contains("foo"));
796        assert!(output.contains("bar"));
797    }
798    #[test]
799    pub(super) fn test_js_module_emit_preamble() {
800        let mut module = JsModule::new();
801        module.add_preamble("const VERSION = '1.0';".to_string());
802        let output = module.emit();
803        assert!(output.contains("const VERSION = '1.0';"));
804    }
805}
806#[cfg(test)]
807mod JS_infra_tests {
808    use super::*;
809    #[test]
810    pub(super) fn test_pass_config() {
811        let config = JSPassConfig::new("test_pass", JSPassPhase::Transformation);
812        assert!(config.enabled);
813        assert!(config.phase.is_modifying());
814        assert_eq!(config.phase.name(), "transformation");
815    }
816    #[test]
817    pub(super) fn test_pass_stats() {
818        let mut stats = JSPassStats::new();
819        stats.record_run(10, 100, 3);
820        stats.record_run(20, 200, 5);
821        assert_eq!(stats.total_runs, 2);
822        assert!((stats.average_changes_per_run() - 15.0).abs() < 0.01);
823        assert!((stats.success_rate() - 1.0).abs() < 0.01);
824        let s = stats.format_summary();
825        assert!(s.contains("Runs: 2/2"));
826    }
827    #[test]
828    pub(super) fn test_pass_registry() {
829        let mut reg = JSPassRegistry::new();
830        reg.register(JSPassConfig::new("pass_a", JSPassPhase::Analysis));
831        reg.register(JSPassConfig::new("pass_b", JSPassPhase::Transformation).disabled());
832        assert_eq!(reg.total_passes(), 2);
833        assert_eq!(reg.enabled_count(), 1);
834        reg.update_stats("pass_a", 5, 50, 2);
835        let stats = reg.get_stats("pass_a").expect("stats should exist");
836        assert_eq!(stats.total_changes, 5);
837    }
838    #[test]
839    pub(super) fn test_analysis_cache() {
840        let mut cache = JSAnalysisCache::new(10);
841        cache.insert("key1".to_string(), vec![1, 2, 3]);
842        assert!(cache.get("key1").is_some());
843        assert!(cache.get("key2").is_none());
844        assert!((cache.hit_rate() - 0.5).abs() < 0.01);
845        cache.invalidate("key1");
846        assert!(!cache.entries["key1"].valid);
847        assert_eq!(cache.size(), 1);
848    }
849    #[test]
850    pub(super) fn test_worklist() {
851        let mut wl = JSWorklist::new();
852        assert!(wl.push(1));
853        assert!(wl.push(2));
854        assert!(!wl.push(1));
855        assert_eq!(wl.len(), 2);
856        assert_eq!(wl.pop(), Some(1));
857        assert!(!wl.contains(1));
858        assert!(wl.contains(2));
859    }
860    #[test]
861    pub(super) fn test_dominator_tree() {
862        let mut dt = JSDominatorTree::new(5);
863        dt.set_idom(1, 0);
864        dt.set_idom(2, 0);
865        dt.set_idom(3, 1);
866        assert!(dt.dominates(0, 3));
867        assert!(dt.dominates(1, 3));
868        assert!(!dt.dominates(2, 3));
869        assert!(dt.dominates(3, 3));
870    }
871    #[test]
872    pub(super) fn test_liveness() {
873        let mut liveness = JSLivenessInfo::new(3);
874        liveness.add_def(0, 1);
875        liveness.add_use(1, 1);
876        assert!(liveness.defs[0].contains(&1));
877        assert!(liveness.uses[1].contains(&1));
878    }
879    #[test]
880    pub(super) fn test_constant_folding() {
881        assert_eq!(JSConstantFoldingHelper::fold_add_i64(3, 4), Some(7));
882        assert_eq!(JSConstantFoldingHelper::fold_div_i64(10, 0), None);
883        assert_eq!(JSConstantFoldingHelper::fold_div_i64(10, 2), Some(5));
884        assert_eq!(
885            JSConstantFoldingHelper::fold_bitand_i64(0b1100, 0b1010),
886            0b1000
887        );
888        assert_eq!(JSConstantFoldingHelper::fold_bitnot_i64(0), -1);
889    }
890    #[test]
891    pub(super) fn test_dep_graph() {
892        let mut g = JSDepGraph::new();
893        g.add_dep(1, 2);
894        g.add_dep(2, 3);
895        g.add_dep(1, 3);
896        assert_eq!(g.dependencies_of(2), vec![1]);
897        let topo = g.topological_sort();
898        assert_eq!(topo.len(), 3);
899        assert!(!g.has_cycle());
900        let pos: std::collections::HashMap<u32, usize> =
901            topo.iter().enumerate().map(|(i, &n)| (n, i)).collect();
902        assert!(pos[&1] < pos[&2]);
903        assert!(pos[&1] < pos[&3]);
904        assert!(pos[&2] < pos[&3]);
905    }
906}