use crate::parser::*;
use std::collections::HashMap;
use super::type_conversion::{escape_js_keyword, type_to_jsdoc};
pub struct JavaScriptGenerator {
pub(crate) indent_level: usize,
pub(crate) async_functions: HashMap<String, bool>,
pub(crate) impl_methods: HashMap<String, Vec<(String, Vec<String>)>>,
pub(crate) var_scopes: Vec<HashMap<String, String>>,
pub(crate) shadow_counter: HashMap<String, usize>,
}
impl JavaScriptGenerator {
pub fn new() -> Self {
Self {
indent_level: 0,
async_functions: HashMap::new(),
impl_methods: HashMap::new(),
var_scopes: vec![HashMap::new()],
shadow_counter: HashMap::new(),
}
}
pub(crate) fn push_var_scope(&mut self) {
self.var_scopes.push(HashMap::new());
}
pub(crate) fn pop_var_scope(&mut self) {
self.var_scopes.pop();
}
pub(crate) fn declare_var(&mut self, name: &str) -> String {
let already_exists = self.var_scopes.iter().any(|s| s.contains_key(name));
let js_name = if already_exists {
let counter = self.shadow_counter.entry(name.to_string()).or_insert(0);
*counter += 1;
format!("{}${}", name, counter)
} else {
name.to_string()
};
if let Some(scope) = self.var_scopes.last_mut() {
scope.insert(name.to_string(), js_name.clone());
}
js_name
}
pub(crate) fn resolve_var(&self, name: &str) -> String {
for scope in self.var_scopes.iter().rev() {
if let Some(js_name) = scope.get(name) {
return js_name.clone();
}
}
name.to_string()
}
pub fn generate(&mut self, program: &Program) -> String {
let mut output = String::new();
output.push_str("// Generated by Windjammer JavaScript transpiler (v0.32.0)\n");
output.push_str("// https://windjammer.dev\n\n");
self.detect_async_functions(program);
self.collect_impl_methods(program);
for item in &program.items {
let code = self.generate_item(item);
if !code.is_empty() {
output.push_str(&code);
output.push_str("\n\n");
}
}
if self.has_main_function(program) {
output.push_str(&self.generate_auto_run_main());
}
output
}
fn detect_async_functions(&mut self, program: &Program) {
for item in &program.items {
if let Item::Function { decl: func, .. } = item {
let is_async = func.is_async || self.contains_await_in_body(&func.body);
self.async_functions.insert(func.name.clone(), is_async);
}
}
}
fn contains_await_in_body<'ast>(&self, statements: &[&'ast Statement<'ast>]) -> bool {
statements.iter().any(|s| Self::contains_await_stmt(s))
}
fn contains_await_stmt(stmt: &Statement) -> bool {
match stmt {
Statement::Expression { expr, .. } => Self::contains_await_expr(expr),
Statement::Let { value, .. } => Self::contains_await_expr(value),
Statement::Return {
value: Some(expr), ..
} => Self::contains_await_expr(expr),
Statement::If {
condition,
then_block,
else_block,
..
} => {
Self::contains_await_expr(condition)
|| then_block.iter().any(|s| Self::contains_await_stmt(s))
|| else_block
.as_ref()
.is_some_and(|block| block.iter().any(|s| Self::contains_await_stmt(s)))
}
_ => false,
}
}
fn contains_await_expr(expr: &Expression) -> bool {
match expr {
Expression::Await { .. } => true,
Expression::Binary { left, right, .. } => {
Self::contains_await_expr(left) || Self::contains_await_expr(right)
}
Expression::Call {
function,
arguments,
..
} => {
Self::contains_await_expr(function)
|| arguments
.iter()
.any(|(_, arg)| Self::contains_await_expr(arg))
}
Expression::MethodCall {
object, arguments, ..
} => {
Self::contains_await_expr(object)
|| arguments
.iter()
.any(|(_, arg)| Self::contains_await_expr(arg))
}
_ => false,
}
}
fn has_main_function(&self, program: &Program) -> bool {
program
.items
.iter()
.any(|item| matches!(item, Item::Function { decl: func, .. } if func.name == "main"))
}
fn generate_auto_run_main(&self) -> String {
let is_async = self.async_functions.get("main").copied().unwrap_or(false);
if is_async {
r#"// Auto-run main if executed directly (Node.js)
if (import.meta.url === `file://${process.argv[1]}`) {
main().catch(console.error);
}
"#
.to_string()
} else {
r#"// Auto-run main if executed directly (Node.js)
if (import.meta.url === `file://${process.argv[1]}`) {
main();
}
"#
.to_string()
}
}
fn collect_impl_methods(&mut self, program: &Program) {
for item in &program.items {
if let Item::Impl { block, .. } = item {
let type_name = block.type_name.clone();
let methods: Vec<(String, Vec<String>)> = block
.functions
.iter()
.map(|func| {
let method_code = self.generate_class_method(func);
(func.name.clone(), vec![method_code])
})
.collect();
self.impl_methods
.entry(type_name)
.or_default()
.extend(methods);
}
}
}
fn generate_class_method(&mut self, func: &FunctionDecl) -> String {
let mut output = String::new();
let indent = self.indent();
let is_async = self
.async_functions
.get(&func.name)
.copied()
.unwrap_or(false);
let is_static = !func.parameters.iter().any(|p| p.name == "self");
output.push_str(&indent);
if is_static {
output.push_str("static ");
}
if is_async {
output.push_str("async ");
}
let method_name = if func.name == "new" {
"create"
} else {
&func.name
};
output.push_str(method_name);
output.push('(');
let params: Vec<String> = func
.parameters
.iter()
.filter(|p| p.name != "self")
.map(|p| p.name.clone())
.collect();
output.push_str(¶ms.join(", "));
output.push_str(") {\n");
self.indent_level += 1;
let body_len = func.body.len();
let has_return_type = func.return_type.is_some();
for (i, stmt) in func.body.iter().enumerate() {
let is_last = i == body_len - 1;
if is_last && has_return_type {
if let Statement::Expression { expr, .. } = stmt {
let indent_inner = self.indent();
let expr_str = self.generate_expression(expr);
let expr_str = expr_str.replace("self.", "this.");
output.push_str(&format!("{}return {};\n", indent_inner, expr_str));
continue;
}
}
let stmt_code = self.generate_statement(stmt);
let stmt_code = stmt_code.replace("self.", "this.");
output.push_str(&stmt_code);
}
self.indent_level -= 1;
output.push_str(&self.indent());
output.push_str("}\n");
output
}
fn generate_item(&mut self, item: &Item) -> String {
match item {
Item::Function { decl: func, .. } => self.generate_function(func),
Item::Struct {
decl: struct_decl, ..
} => self.generate_struct(struct_decl),
Item::Enum {
decl: enum_decl, ..
} => self.generate_enum(enum_decl),
Item::Trait { .. } => String::from("// Trait (use duck typing in JavaScript)"),
Item::Impl { .. } => String::new(), Item::Use { .. } => String::new(), Item::Const { name, value, .. } => {
format!(
"export const {} = {};\n",
name,
self.generate_expression(value)
)
}
Item::Static { name, value, .. } => {
format!(
"export let {} = {};\n",
name,
self.generate_expression(value)
)
}
_ => "// TODO: Unsupported item type".to_string(),
}
}
fn generate_function(&mut self, func: &FunctionDecl) -> String {
let mut output = String::new();
if !func.parameters.is_empty() || func.return_type.is_some() {
output.push_str("/**\n");
for param in &func.parameters {
let param_name = escape_js_keyword(¶m.name);
output.push_str(&format!(
" * @param {{{}}} {}\n",
type_to_jsdoc(¶m.type_),
param_name
));
}
if let Some(ref ret_type) = func.return_type {
output.push_str(&format!(" * @returns {{{}}}\n", type_to_jsdoc(ret_type)));
}
output.push_str(" */\n");
}
output.push_str("export ");
let is_async = self
.async_functions
.get(&func.name)
.copied()
.unwrap_or(false);
if is_async {
output.push_str("async ");
}
output.push_str("function ");
output.push_str(&func.name);
output.push('(');
let params: Vec<String> = func
.parameters
.iter()
.map(|p| escape_js_keyword(&p.name))
.collect();
output.push_str(¶ms.join(", "));
output.push_str(") {\n");
self.push_var_scope();
for p in &func.parameters {
let escaped = escape_js_keyword(&p.name);
self.var_scopes
.last_mut()
.unwrap()
.insert(p.name.clone(), escaped);
}
self.indent_level += 1;
let body_len = func.body.len();
let has_return_type = func.return_type.is_some();
for (i, stmt) in func.body.iter().enumerate() {
let is_last = i == body_len - 1;
if is_last && has_return_type {
match stmt {
Statement::Expression { expr, .. } => {
let indent = self.indent();
let expr_str = self.generate_expression(expr);
output.push_str(&format!("{}return {};\n", indent, expr_str));
continue;
}
Statement::Match { .. } => {
output.push_str(&self.generate_statement_match_with_return(stmt));
continue;
}
_ => {}
}
}
output.push_str(&self.generate_statement(stmt));
}
self.indent_level -= 1;
self.pop_var_scope();
output.push('}');
output
}
fn generate_struct(&mut self, struct_decl: &StructDecl) -> String {
let mut output = String::new();
output.push_str(&format!("export class {} {{\n", struct_decl.name));
self.indent_level += 1;
output.push_str(&self.indent());
output.push_str("constructor(");
let params: Vec<String> = struct_decl.fields.iter().map(|f| f.name.clone()).collect();
output.push_str(¶ms.join(", "));
output.push_str(") {\n");
self.indent_level += 1;
for field in &struct_decl.fields {
output.push_str(&self.indent());
output.push_str(&format!("this.{} = {};\n", field.name, field.name));
}
self.indent_level -= 1;
output.push_str(&self.indent());
output.push_str("}\n");
if let Some(methods) = self.impl_methods.get(&struct_decl.name).cloned() {
for (_method_name, code_parts) in &methods {
output.push('\n');
for code in code_parts {
output.push_str(code);
}
}
}
self.indent_level -= 1;
output.push('}');
output
}
fn generate_enum(&mut self, enum_decl: &EnumDecl) -> String {
let mut output = String::new();
output.push_str(&format!(
"export const {} = Object.freeze({{\n",
enum_decl.name
));
self.indent_level += 1;
for (i, variant) in enum_decl.variants.iter().enumerate() {
output.push_str(&self.indent());
let tag = format!("{}.{}", enum_decl.name, variant.name);
match &variant.data {
EnumVariantData::Unit => {
output.push_str(&format!("{}: '{}'", variant.name, tag));
}
EnumVariantData::Tuple(types) => {
let params: Vec<String> = (0..types.len()).map(|i| format!("v{}", i)).collect();
output.push_str(&format!(
"{}: ({}) => ({{ type: '{}', value: [{}] }})",
variant.name,
params.join(", "),
tag,
params.join(", ")
));
}
EnumVariantData::Struct(fields) => {
let params: Vec<String> = fields.iter().map(|(n, _)| n.clone()).collect();
output.push_str(&format!(
"{}: ({}) => ({{ type: '{}', value: {{ {} }} }})",
variant.name,
params.join(", "),
tag,
params.join(", ")
));
}
}
if i < enum_decl.variants.len() - 1 {
output.push(',');
}
output.push('\n');
}
self.indent_level -= 1;
output.push_str("});");
output
}
pub(crate) fn indent(&self) -> String {
" ".repeat(self.indent_level)
}
}
impl Default for JavaScriptGenerator {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::codegen::javascript::type_conversion::binary_op_to_js;
#[test]
fn test_generate_empty_program() {
let mut gen = JavaScriptGenerator::new();
let program = Program { items: vec![] };
let code = gen.generate(&program);
assert!(code.contains("Windjammer JavaScript transpiler"));
}
#[test]
fn test_generate_simple_function() {
let mut gen = JavaScriptGenerator::new();
let program = Program {
items: vec![Item::Function {
decl: FunctionDecl {
name: "greet".to_string(),
is_pub: false,
is_extern: false,
parameters: vec![],
return_type: None,
return_decorators: Vec::new(),
body: vec![],
decorators: vec![],
is_async: false,
type_params: vec![],
where_clause: vec![],
parent_type: None,
impl_trait: None,
doc_comment: None,
},
location: None,
}],
};
let code = gen.generate(&program);
assert!(code.contains("export function greet"));
}
#[test]
fn test_generate_literal() {
let gen = JavaScriptGenerator::new();
assert_eq!(gen.generate_literal(&Literal::Int(42)), "42");
assert_eq!(
gen.generate_literal(&Literal::String("hello".to_string())),
"'hello'"
);
assert_eq!(gen.generate_literal(&Literal::Bool(true)), "true");
}
#[test]
fn test_binary_op_conversion() {
assert_eq!(binary_op_to_js(&BinaryOp::Eq), "===");
assert_eq!(binary_op_to_js(&BinaryOp::Ne), "!==");
assert_eq!(binary_op_to_js(&BinaryOp::And), "&&");
assert_eq!(binary_op_to_js(&BinaryOp::Add), "+");
}
}