1use 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
16pub 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}
33pub(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}
104pub(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}
124pub 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};"#;
141pub 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}