test: add comprehensive integration tests for JS backend
Add 10 integration tests that compile Lux to JavaScript and verify correct execution in Node.js: - test_js_factorial: Recursion and effects - test_js_fibonacci: Classic recursive algorithm - test_js_adt_and_pattern_matching: Custom ADTs with match - test_js_option_type: Built-in Option type handling - test_js_closures: Closure creation and variable capture - test_js_higher_order_functions: Functions as values - test_js_list_operations: List.map, List.foldl - test_js_pipe_operator: Pipe (|>) operator - test_js_records: Record literal and field access - test_js_string_concatenation: String operations Also fix List module operations being incorrectly treated as effects by adding special-case handling in EffectOp emission. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -508,6 +508,11 @@ impl JsBackend {
|
|||||||
args,
|
args,
|
||||||
..
|
..
|
||||||
} => {
|
} => {
|
||||||
|
// Special case: List module operations (not an effect)
|
||||||
|
if effect.name == "List" {
|
||||||
|
return self.emit_list_operation(&operation.name, args);
|
||||||
|
}
|
||||||
|
|
||||||
let arg_strs: Result<Vec<_>, _> = args.iter().map(|a| self.emit_expr(a)).collect();
|
let arg_strs: Result<Vec<_>, _> = args.iter().map(|a| self.emit_expr(a)).collect();
|
||||||
let args_str = arg_strs?.join(", ");
|
let args_str = arg_strs?.join(", ");
|
||||||
|
|
||||||
@@ -1024,6 +1029,8 @@ impl Default for JsBackend {
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
use crate::parser::Parser;
|
||||||
|
use std::process::Command;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_literal_int() {
|
fn test_literal_int() {
|
||||||
@@ -1068,4 +1075,194 @@ mod tests {
|
|||||||
assert_eq!(backend.escape_js_keyword("class"), "class_");
|
assert_eq!(backend.escape_js_keyword("class"), "class_");
|
||||||
assert_eq!(backend.escape_js_keyword("foo"), "foo");
|
assert_eq!(backend.escape_js_keyword("foo"), "foo");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Helper to compile Lux source to JS and run in Node.js
|
||||||
|
fn compile_and_run(source: &str) -> Result<String, String> {
|
||||||
|
let program = Parser::parse_source(source).map_err(|e| format!("Parse error: {}", e))?;
|
||||||
|
|
||||||
|
let mut backend = JsBackend::new();
|
||||||
|
let js_code = backend
|
||||||
|
.generate(&program)
|
||||||
|
.map_err(|e| format!("Codegen error: {}", e))?;
|
||||||
|
|
||||||
|
let output = Command::new("node")
|
||||||
|
.arg("-e")
|
||||||
|
.arg(&js_code)
|
||||||
|
.output()
|
||||||
|
.map_err(|e| format!("Node.js error: {}", e))?;
|
||||||
|
|
||||||
|
if !output.status.success() {
|
||||||
|
return Err(format!(
|
||||||
|
"Node.js execution failed:\n{}",
|
||||||
|
String::from_utf8_lossy(&output.stderr)
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(String::from_utf8_lossy(&output.stdout).trim().to_string())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_js_factorial() {
|
||||||
|
let source = r#"
|
||||||
|
fn factorial(n: Int): Int =
|
||||||
|
if n <= 1 then 1
|
||||||
|
else n * factorial(n - 1)
|
||||||
|
|
||||||
|
fn main(): Unit with {Console} =
|
||||||
|
Console.print("Result: " + toString(factorial(5)))
|
||||||
|
"#;
|
||||||
|
|
||||||
|
let output = compile_and_run(source).expect("Should compile and run");
|
||||||
|
assert_eq!(output, "Result: 120");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_js_fibonacci() {
|
||||||
|
let source = r#"
|
||||||
|
fn fib(n: Int): Int =
|
||||||
|
if n <= 1 then n
|
||||||
|
else fib(n - 1) + fib(n - 2)
|
||||||
|
|
||||||
|
fn main(): Unit with {Console} =
|
||||||
|
Console.print("fib(10) = " + toString(fib(10)))
|
||||||
|
"#;
|
||||||
|
|
||||||
|
let output = compile_and_run(source).expect("Should compile and run");
|
||||||
|
assert_eq!(output, "fib(10) = 55");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_js_adt_and_pattern_matching() {
|
||||||
|
let source = r#"
|
||||||
|
type Tree =
|
||||||
|
| Leaf(Int)
|
||||||
|
| Node(Tree, Tree)
|
||||||
|
|
||||||
|
fn sumTree(tree: Tree): Int =
|
||||||
|
match tree {
|
||||||
|
Leaf(n) => n,
|
||||||
|
Node(left, right) => sumTree(left) + sumTree(right)
|
||||||
|
}
|
||||||
|
|
||||||
|
let tree = Node(Node(Leaf(1), Leaf(2)), Leaf(3))
|
||||||
|
|
||||||
|
fn main(): Unit with {Console} =
|
||||||
|
Console.print("Sum: " + toString(sumTree(tree)))
|
||||||
|
"#;
|
||||||
|
|
||||||
|
let output = compile_and_run(source).expect("Should compile and run");
|
||||||
|
assert_eq!(output, "Sum: 6");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_js_option_type() {
|
||||||
|
let source = r#"
|
||||||
|
fn safeDivide(a: Int, b: Int): Option<Int> =
|
||||||
|
if b == 0 then None
|
||||||
|
else Some(a / b)
|
||||||
|
|
||||||
|
fn showResult(opt: Option<Int>): String =
|
||||||
|
match opt {
|
||||||
|
Some(n) => "Got: " + toString(n),
|
||||||
|
None => "None"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main(): Unit with {Console} = {
|
||||||
|
Console.print(showResult(safeDivide(10, 2)))
|
||||||
|
Console.print(showResult(safeDivide(10, 0)))
|
||||||
|
}
|
||||||
|
"#;
|
||||||
|
|
||||||
|
let output = compile_and_run(source).expect("Should compile and run");
|
||||||
|
assert_eq!(output, "Got: 5\nNone");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_js_closures() {
|
||||||
|
let source = r#"
|
||||||
|
fn makeAdder(x: Int): fn(Int): Int =
|
||||||
|
fn(y: Int): Int => x + y
|
||||||
|
|
||||||
|
let add5 = makeAdder(5)
|
||||||
|
|
||||||
|
fn main(): Unit with {Console} =
|
||||||
|
Console.print("5 + 10 = " + toString(add5(10)))
|
||||||
|
"#;
|
||||||
|
|
||||||
|
let output = compile_and_run(source).expect("Should compile and run");
|
||||||
|
assert_eq!(output, "5 + 10 = 15");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_js_higher_order_functions() {
|
||||||
|
let source = r#"
|
||||||
|
fn apply(f: fn(Int): Int, x: Int): Int = f(x)
|
||||||
|
fn double(x: Int): Int = x * 2
|
||||||
|
|
||||||
|
fn main(): Unit with {Console} =
|
||||||
|
Console.print("double(21) = " + toString(apply(double, 21)))
|
||||||
|
"#;
|
||||||
|
|
||||||
|
let output = compile_and_run(source).expect("Should compile and run");
|
||||||
|
assert_eq!(output, "double(21) = 42");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_js_list_operations() {
|
||||||
|
let source = r#"
|
||||||
|
let nums = [1, 2, 3, 4, 5]
|
||||||
|
let doubled = List.map(nums, fn(x: Int): Int => x * 2)
|
||||||
|
let sum = List.foldl(doubled, 0, fn(acc: Int, x: Int): Int => acc + x)
|
||||||
|
|
||||||
|
fn main(): Unit with {Console} =
|
||||||
|
Console.print("Sum of doubled: " + toString(sum))
|
||||||
|
"#;
|
||||||
|
|
||||||
|
let output = compile_and_run(source).expect("Should compile and run");
|
||||||
|
assert_eq!(output, "Sum of doubled: 30");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_js_pipe_operator() {
|
||||||
|
let source = r#"
|
||||||
|
fn double(x: Int): Int = x * 2
|
||||||
|
fn addOne(x: Int): Int = x + 1
|
||||||
|
|
||||||
|
let result = 5 |> double |> addOne
|
||||||
|
|
||||||
|
fn main(): Unit with {Console} =
|
||||||
|
Console.print("Result: " + toString(result))
|
||||||
|
"#;
|
||||||
|
|
||||||
|
let output = compile_and_run(source).expect("Should compile and run");
|
||||||
|
assert_eq!(output, "Result: 11");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_js_records() {
|
||||||
|
let source = r#"
|
||||||
|
let point = { x: 10, y: 20 }
|
||||||
|
let sum = point.x + point.y
|
||||||
|
|
||||||
|
fn main(): Unit with {Console} =
|
||||||
|
Console.print("Sum: " + toString(sum))
|
||||||
|
"#;
|
||||||
|
|
||||||
|
let output = compile_and_run(source).expect("Should compile and run");
|
||||||
|
assert_eq!(output, "Sum: 30");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_js_string_concatenation() {
|
||||||
|
let source = r#"
|
||||||
|
let name = "World"
|
||||||
|
let greeting = "Hello, " + name + "!"
|
||||||
|
|
||||||
|
fn main(): Unit with {Console} =
|
||||||
|
Console.print(greeting)
|
||||||
|
"#;
|
||||||
|
|
||||||
|
let output = compile_and_run(source).expect("Should compile and run");
|
||||||
|
assert_eq!(output, "Hello, World!");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user