feat: add JavaScript backend for browser/Node.js compilation
Implement Phase 1 of the browser/frontend plan with a complete JS code generator that compiles Lux programs to JavaScript: - Basic types: Int, Float, Bool, String, Unit → JS primitives - Functions and closures → native JS functions with closure capture - Pattern matching → if/else chains with tag checks - ADT definitions → tagged object constructors - Built-in types (Option, Result) → Lux.Some/None/Ok/Err helpers - Effects → handler objects passed as parameters - List operations → Array methods (map, filter, reduce, etc.) - Top-level let bindings and run expressions CLI usage: lux compile app.lux --target js -o app.js lux compile app.lux --target js --run Tested with factorial, datatypes, functional, tailcall, and hello examples - all producing correct output when run in Node.js. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
1071
src/codegen/js_backend.rs
Normal file
1071
src/codegen/js_backend.rs
Normal file
File diff suppressed because it is too large
Load Diff
@@ -2,10 +2,13 @@
|
|||||||
//!
|
//!
|
||||||
//! This module provides compilation to various targets:
|
//! This module provides compilation to various targets:
|
||||||
//! - C: For native compilation via GCC/Clang
|
//! - C: For native compilation via GCC/Clang
|
||||||
//! - JavaScript: For frontend/browser deployment (planned)
|
//! - JavaScript: For frontend/browser deployment
|
||||||
//! - WebAssembly: For portable deployment (planned)
|
//! - WebAssembly: For portable deployment (planned)
|
||||||
|
|
||||||
pub mod c_backend;
|
pub mod c_backend;
|
||||||
|
pub mod js_backend;
|
||||||
|
|
||||||
#[allow(unused_imports)]
|
#[allow(unused_imports)]
|
||||||
pub use c_backend::CBackend;
|
pub use c_backend::CBackend;
|
||||||
|
#[allow(unused_imports)]
|
||||||
|
pub use js_backend::JsBackend;
|
||||||
|
|||||||
103
src/main.rs
103
src/main.rs
@@ -140,20 +140,31 @@ fn main() {
|
|||||||
handle_pkg_command(&args[2..]);
|
handle_pkg_command(&args[2..]);
|
||||||
}
|
}
|
||||||
"compile" => {
|
"compile" => {
|
||||||
// Compile to native binary
|
// Compile to native binary or JavaScript
|
||||||
if args.len() < 3 {
|
if args.len() < 3 {
|
||||||
eprintln!("Usage: lux compile <file.lux> [-o binary]");
|
eprintln!("Usage: lux compile <file.lux> [-o binary]");
|
||||||
eprintln!(" lux compile <file.lux> --run");
|
eprintln!(" lux compile <file.lux> --run");
|
||||||
eprintln!(" lux compile <file.lux> --emit-c [-o file.c]");
|
eprintln!(" lux compile <file.lux> --emit-c [-o file.c]");
|
||||||
|
eprintln!(" lux compile <file.lux> --target js [-o file.js]");
|
||||||
std::process::exit(1);
|
std::process::exit(1);
|
||||||
}
|
}
|
||||||
let run_after = args.iter().any(|a| a == "--run");
|
let run_after = args.iter().any(|a| a == "--run");
|
||||||
let emit_c = args.iter().any(|a| a == "--emit-c");
|
let emit_c = args.iter().any(|a| a == "--emit-c");
|
||||||
|
let target_js = args.iter()
|
||||||
|
.position(|a| a == "--target")
|
||||||
|
.and_then(|i| args.get(i + 1))
|
||||||
|
.map(|s| s.as_str() == "js")
|
||||||
|
.unwrap_or(false);
|
||||||
let output_path = args.iter()
|
let output_path = args.iter()
|
||||||
.position(|a| a == "-o")
|
.position(|a| a == "-o")
|
||||||
.and_then(|i| args.get(i + 1))
|
.and_then(|i| args.get(i + 1))
|
||||||
.map(|s| s.as_str());
|
.map(|s| s.as_str());
|
||||||
compile_to_c(&args[2], output_path, run_after, emit_c);
|
|
||||||
|
if target_js {
|
||||||
|
compile_to_js(&args[2], output_path, run_after);
|
||||||
|
} else {
|
||||||
|
compile_to_c(&args[2], output_path, run_after, emit_c);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
path => {
|
path => {
|
||||||
// Run a file
|
// Run a file
|
||||||
@@ -176,6 +187,7 @@ fn print_help() {
|
|||||||
println!(" lux compile <f> -o app Compile to binary named 'app'");
|
println!(" lux compile <f> -o app Compile to binary named 'app'");
|
||||||
println!(" lux compile <f> --run Compile and execute");
|
println!(" lux compile <f> --run Compile and execute");
|
||||||
println!(" lux compile <f> --emit-c Output C code instead of binary");
|
println!(" lux compile <f> --emit-c Output C code instead of binary");
|
||||||
|
println!(" lux compile <f> --target js Compile to JavaScript");
|
||||||
println!(" lux fmt <file.lux> Format a file (--check to verify only)");
|
println!(" lux fmt <file.lux> Format a file (--check to verify only)");
|
||||||
println!(" lux check <file.lux> Type check without running");
|
println!(" lux check <file.lux> Type check without running");
|
||||||
println!(" lux test [pattern] Run tests (optional pattern filter)");
|
println!(" lux test [pattern] Run tests (optional pattern filter)");
|
||||||
@@ -398,6 +410,93 @@ fn compile_to_c(path: &str, output_path: Option<&str>, run_after: bool, emit_c:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn compile_to_js(path: &str, output_path: Option<&str>, run_after: bool) {
|
||||||
|
use codegen::js_backend::JsBackend;
|
||||||
|
use modules::ModuleLoader;
|
||||||
|
use std::path::Path;
|
||||||
|
use std::process::Command;
|
||||||
|
|
||||||
|
let file_path = Path::new(path);
|
||||||
|
let source = match std::fs::read_to_string(file_path) {
|
||||||
|
Ok(s) => s,
|
||||||
|
Err(e) => {
|
||||||
|
eprintln!("Error reading file '{}': {}", path, e);
|
||||||
|
std::process::exit(1);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Parse with module loading
|
||||||
|
let mut loader = ModuleLoader::new();
|
||||||
|
if let Some(parent) = file_path.parent() {
|
||||||
|
loader.add_search_path(parent.to_path_buf());
|
||||||
|
}
|
||||||
|
|
||||||
|
let program = match loader.load_source(&source, Some(file_path)) {
|
||||||
|
Ok(p) => p,
|
||||||
|
Err(e) => {
|
||||||
|
eprintln!("Module error: {}", e);
|
||||||
|
std::process::exit(1);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Type check
|
||||||
|
let mut checker = TypeChecker::new();
|
||||||
|
if let Err(errors) = checker.check_program_with_modules(&program, &loader) {
|
||||||
|
for error in errors {
|
||||||
|
let diagnostic = error.to_diagnostic();
|
||||||
|
eprint!("{}", render(&diagnostic, &source, Some(path)));
|
||||||
|
}
|
||||||
|
std::process::exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate JavaScript code
|
||||||
|
let mut backend = JsBackend::new();
|
||||||
|
let js_code = match backend.generate(&program) {
|
||||||
|
Ok(code) => code,
|
||||||
|
Err(e) => {
|
||||||
|
eprintln!("JS codegen error: {}", e);
|
||||||
|
std::process::exit(1);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Determine output file path
|
||||||
|
let output_js = if let Some(out) = output_path {
|
||||||
|
Path::new(out).to_path_buf()
|
||||||
|
} else {
|
||||||
|
// Derive from source filename: foo.lux -> ./foo.js
|
||||||
|
let stem = file_path.file_stem()
|
||||||
|
.and_then(|s| s.to_str())
|
||||||
|
.unwrap_or("output");
|
||||||
|
Path::new(".").join(format!("{}.js", stem))
|
||||||
|
};
|
||||||
|
|
||||||
|
// Write the JavaScript file
|
||||||
|
if let Err(e) = std::fs::write(&output_js, &js_code) {
|
||||||
|
eprintln!("Error writing file '{}': {}", output_js.display(), e);
|
||||||
|
std::process::exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if run_after {
|
||||||
|
// Run with Node.js
|
||||||
|
let node_result = Command::new("node")
|
||||||
|
.arg(&output_js)
|
||||||
|
.status();
|
||||||
|
|
||||||
|
match node_result {
|
||||||
|
Ok(status) => {
|
||||||
|
std::process::exit(status.code().unwrap_or(1));
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
eprintln!("Failed to run with Node.js: {}", e);
|
||||||
|
eprintln!("Make sure Node.js is installed.");
|
||||||
|
std::process::exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
eprintln!("Compiled to {}", output_js.display());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn run_tests(args: &[String]) {
|
fn run_tests(args: &[String]) {
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
use std::fs;
|
use std::fs;
|
||||||
|
|||||||
Reference in New Issue
Block a user