diff --git a/crates/prometeu-compiler/src/compiler.rs b/crates/prometeu-compiler/src/compiler.rs index 599202fc..560368eb 100644 --- a/crates/prometeu-compiler/src/compiler.rs +++ b/crates/prometeu-compiler/src/compiler.rs @@ -6,7 +6,6 @@ use crate::backend; use crate::common::files::FileManager; use crate::common::symbols::Symbol; -use crate::frontends::ts::TypescriptFrontend; use crate::frontends::Frontend; use crate::ir; use anyhow::Result; @@ -61,7 +60,7 @@ pub fn compile(entry: &Path) -> Result { // 1. Select Frontend (Currently only TS is supported) // The frontend is responsible for parsing source code and producing the IR. - let frontend = TypescriptFrontend; + let frontend = /** ??? **/; // 2. Compile to IR (Intermediate Representation) // This step abstracts away source-specific syntax (like TypeScript) into a diff --git a/crates/prometeu-compiler/src/frontends/lua/input_map.rs b/crates/prometeu-compiler/src/frontends/lua/input_map.rs deleted file mode 100644 index ad2b5e43..00000000 --- a/crates/prometeu-compiler/src/frontends/lua/input_map.rs +++ /dev/null @@ -1,68 +0,0 @@ -use anyhow::anyhow; -use prometeu_core::model::ButtonId; - -/// Mapping of physical button names to their virtual Button ID. -/// -/// These constants match the `ButtonId` enum in `prometeu-core`. -pub const BTN_UP: u32 = ButtonId::Up as u32; -pub const BTN_DOWN: u32 = ButtonId::Down as u32; -pub const BTN_LEFT: u32 = ButtonId::Left as u32; -pub const BTN_RIGHT: u32 = ButtonId::Right as u32; -pub const BTN_A: u32 = ButtonId::A as u32; -pub const BTN_B: u32 = ButtonId::B as u32; -pub const BTN_X: u32 = ButtonId::X as u32; -pub const BTN_Y: u32 = ButtonId::Y as u32; -pub const BTN_L: u32 = ButtonId::L as u32; -pub const BTN_R: u32 = ButtonId::R as u32; -pub const BTN_START: u32 = ButtonId::Start as u32; -pub const BTN_SELECT: u32 = ButtonId::Select as u32; - -/// Translates a string identifier (e.g., "up") into a numeric Button ID. -pub fn map_btn_name(btn_name: &str) -> anyhow::Result { - match btn_name.to_lowercase().as_str() { - "up" => Ok(BTN_UP), - "down" => Ok(BTN_DOWN), - "left" => Ok(BTN_LEFT), - "right" => Ok(BTN_RIGHT), - "a" => Ok(BTN_A), - "b" => Ok(BTN_B), - "x" => Ok(BTN_X), - "y" => Ok(BTN_Y), - "l" => Ok(BTN_L), - "r" => Ok(BTN_R), - "start" => Ok(BTN_START), - "select" => Ok(BTN_SELECT), - _ => Err(anyhow!("Unsupported button: {}. Expected one of: up, down, left, right, a, b, x, y, l, r, start, select.", btn_name)), - } -} - -/// Translates a pad state name (e.g., "pressed") into the corresponding syscall name. -pub fn map_pad_state(state_name: &str) -> anyhow::Result<&'static str> { - match state_name { - "down" => Ok("input.getPad"), - "pressed" => Ok("input.getPadPressed"), - "released" => Ok("input.getPadReleased"), - "holdFrames" => Ok("input.getPadHold"), - _ => Err(anyhow!("Unsupported button state: {}. Expected one of: down, pressed, released, holdFrames.", state_name)), - } -} - -/// Translates a touch property name (e.g., "x") into the corresponding syscall name. -pub fn map_touch_prop(prop_name: &str) -> anyhow::Result<&'static str> { - match prop_name { - "x" => Ok("touch.getX"), - "y" => Ok("touch.getY"), - _ => Err(anyhow!("Unsupported touch property: {}. Expected one of: x, y.", prop_name)), - } -} - -/// Translates a touch button state name (e.g., "down") into the corresponding syscall name. -pub fn map_touch_button_state(state_name: &str) -> anyhow::Result<&'static str> { - match state_name { - "down" => Ok("touch.isDown"), - "pressed" => Ok("touch.isPressed"), - "released" => Ok("touch.isReleased"), - "holdFrames" => Ok("touch.getHold"), - _ => Err(anyhow!("Unsupported touch button state: {}. Expected one of: down, pressed, released, holdFrames.", state_name)), - } -} \ No newline at end of file diff --git a/crates/prometeu-compiler/src/frontends/lua/mod.rs b/crates/prometeu-compiler/src/frontends/lua/mod.rs deleted file mode 100644 index ebc8b2ed..00000000 --- a/crates/prometeu-compiler/src/frontends/lua/mod.rs +++ /dev/null @@ -1,164 +0,0 @@ -pub mod parse; -pub mod resolve; -pub mod validate; -pub mod to_ir; -pub mod syscall_map; -pub mod input_map; - -use crate::common::diagnostics::DiagnosticBundle; -use crate::common::files::FileManager; -use crate::frontends::Frontend; -use crate::ir; -use std::collections::{HashMap, VecDeque}; -use std::path::{Path, PathBuf}; -use std::sync::{Arc, Mutex}; -use crate::frontends::lua::to_ir::SharedGlobalTable; - -pub struct LuaFrontend; - -impl LuaFrontend { - fn resolve_module(&self, base_path: &Path, module_name: &str) -> Option { - let parent = base_path.parent()?; - let mut path = parent.join(format!("{}.lua", module_name)); - if path.exists() { - return Some(path.canonicalize().ok()?); - } - - // Try in src/ - path = parent.join("src").join(format!("{}.lua", module_name)); - if path.exists() { - return Some(path.canonicalize().ok()?); - } - - None - } -} - -impl Frontend for LuaFrontend { - fn language(&self) -> &'static str { - "lua" - } - - fn compile_to_ir( - &self, - entry: &Path, - file_manager: &mut FileManager, - ) -> Result { - let mut modules = HashMap::new(); - let mut queue = VecDeque::new(); - - let entry_abs = entry.canonicalize().map_err(|e| DiagnosticBundle::error(format!("Failed to canonicalize entry path: {}", e), None))?; - queue.push_back(entry_abs.clone()); - - while let Some(path) = queue.pop_front() { - let path_str = path.to_string_lossy().to_string(); - if modules.contains_key(&path_str) { - continue; - } - - let source_text = std::fs::read_to_string(&path).map_err(|e| { - DiagnosticBundle::error(format!("Failed to read file {}: {}", path.display(), e), None) - })?; - let file_id = file_manager.add(path.clone(), source_text.clone()); - - let ast = parse::parse_file(&path, file_id)?; - validate::validate_ast(&ast, file_id)?; - - // Discover requires - for stmt in ast.nodes().stmts() { - if let full_moon::ast::Stmt::LocalAssignment(assignment) = stmt { - for expr in assignment.expressions() { - if let full_moon::ast::Expression::FunctionCall(call) = expr { - if let full_moon::ast::Prefix::Name(name) = call.prefix() { - if name.to_string().trim() == "require" { - if let Some(full_moon::ast::Suffix::Call(full_moon::ast::Call::AnonymousCall(full_moon::ast::FunctionArgs::Parentheses { arguments, .. }))) = call.suffixes().next() { - if let Some(full_moon::ast::Expression::String(module_name_token)) = arguments.iter().next() { - let module_name = module_name_token.to_string(); - let module_name = module_name.trim().trim_matches(|c| c == '"' || c == '\''); - if let Some(resolved) = self.resolve_module(&path, module_name) { - queue.push_back(resolved); - } - } - } - } - } - } - } - } - } - - modules.insert(path_str, (file_id, ast)); - } - - // To IR - let mut module = ir::Module::new("main".to_string()); - let shared_globals = Arc::new(Mutex::new(SharedGlobalTable::default())); - - let mut init_bodies = Vec::new(); - let entry_path_str = entry_abs.to_string_lossy().to_string(); - - for (path_str, (file_id, ast)) in modules { - let symbols = resolve::resolve_symbols(&ast)?; - - let module_name = Path::new(&path_str).file_stem().unwrap().to_string_lossy().to_string(); - let prefix = if path_str == entry_path_str { - None - } else { - Some(module_name) - }; - - let mut to_ir = to_ir::ToIR::new(file_id, shared_globals.clone(), prefix, false); - let sub_module = to_ir.convert(&ast, symbols)?; - - // Extract __init body - if let Some(init_fn) = sub_module.functions.iter().find(|f| f.name == "__init") { - init_bodies.push(init_fn.body.clone()); - } - - // Add other functions - module.functions.extend(sub_module.functions.into_iter().filter(|f| f.name != "__init")); - } - - // Create the final merged __init function and put it FIRST - let mut final_init_body = Vec::new(); - for body in init_bodies { - final_init_body.extend(body); - } - - // Add the frame loop to the final __init - let entry_label = ir::instr::Label("entry".to_string()); - final_init_body.push(ir::instr::Instruction::new(ir::instr::InstrKind::Label(entry_label.clone()), None)); - final_init_body.push(ir::instr::Instruction::new(ir::instr::InstrKind::Call { name: "frame".to_string(), arg_count: 0 }, None)); - final_init_body.push(ir::instr::Instruction::new(ir::instr::InstrKind::Pop, None)); - final_init_body.push(ir::instr::Instruction::new(ir::instr::InstrKind::FrameSync, None)); - final_init_body.push(ir::instr::Instruction::new(ir::instr::InstrKind::Jmp(entry_label), None)); - - let final_init = ir::module::Function { - name: "__init".to_string(), - params: Vec::new(), - return_type: ir::types::Type::Void, - body: final_init_body, - }; - - // Insert at the beginning so it's at PC 0 - module.functions.insert(0, final_init); - - // Populate globals from shared table - let shared = shared_globals.lock().unwrap(); - for (name, slot) in &shared.map { - module.globals.push(ir::module::Global { - name: name.clone(), - r#type: ir::types::Type::Any, - slot: *slot, - }); - } - - // --- ENTRY POINT VERIFICATION --- - let has_frame = module.functions.iter().any(|f| f.name == "frame"); - if !has_frame { - return Err(DiagnosticBundle::error("function frame() not found in any Lua module".into(), None)); - } - - Ok(module) - } -} diff --git a/crates/prometeu-compiler/src/frontends/lua/parse.rs b/crates/prometeu-compiler/src/frontends/lua/parse.rs deleted file mode 100644 index 6439b0c0..00000000 --- a/crates/prometeu-compiler/src/frontends/lua/parse.rs +++ /dev/null @@ -1,26 +0,0 @@ -use full_moon; -use std::path::Path; -use std::fs; -use crate::common::diagnostics::{DiagnosticBundle, Diagnostic, DiagnosticLevel}; - -use crate::common::spans::Span; - -pub fn parse_file(entry: &Path, file_id: usize) -> Result { - let source = fs::read_to_string(entry).map_err(|e| { - DiagnosticBundle::error(format!("Failed to read file: {}", e), None) - })?; - - full_moon::parse(&source).map_err(|errors| { - let mut bundle = DiagnosticBundle::new(); - for error in errors { - let (start, end) = error.range(); - let span = Some(Span::new(file_id, start.bytes() as u32, end.bytes() as u32)); - bundle.push(Diagnostic { - message: format!("Lua syntax error: {}", error), - level: DiagnosticLevel::Error, - span, - }); - } - bundle - }) -} diff --git a/crates/prometeu-compiler/src/frontends/lua/resolve.rs b/crates/prometeu-compiler/src/frontends/lua/resolve.rs deleted file mode 100644 index 12aee669..00000000 --- a/crates/prometeu-compiler/src/frontends/lua/resolve.rs +++ /dev/null @@ -1,25 +0,0 @@ -use crate::common::diagnostics::DiagnosticBundle; - -#[derive(Debug, Clone)] -pub enum SymbolKind { - Local(u32), - Global, -} - -#[derive(Debug, Clone)] -pub struct SymbolInfo { - pub name: String, - pub kind: SymbolKind, -} - -pub struct SymbolTable { - // Mapeamento de nomes para informações de símbolos - // Como Lua tem escopo, talvez precisemos de algo mais complexo se quisermos pré-resolver tudo. - // Mas para o subset, talvez possamos simplificar ou apenas fornecer o ajudante de escopo aqui. -} - -pub fn resolve_symbols(_ast: &full_moon::ast::Ast) -> Result { - // Por enquanto, retorna uma tabela vazia. - // A resolução real pode ser feita durante o ToIR ou aqui se anotarmos a AST. - Ok(SymbolTable {}) -} diff --git a/crates/prometeu-compiler/src/frontends/lua/syscall_map.rs b/crates/prometeu-compiler/src/frontends/lua/syscall_map.rs deleted file mode 100644 index d10bf65e..00000000 --- a/crates/prometeu-compiler/src/frontends/lua/syscall_map.rs +++ /dev/null @@ -1,56 +0,0 @@ -use prometeu_core::hardware::Syscall; - -/// Maps a high-level function name to its corresponding Syscall ID in the Virtual Machine. -/// -/// This logic resides in the TypeScript frontend because string name resolution -/// from the source code is dependent on the language and the provided SDK. -pub fn map_syscall(name: &str) -> Option { - // Check if the name corresponds to a standard syscall defined in the core firmware - from_name(name) -} - -/// Converts a textual name (e.g., "gfx.fillRect") to the numeric ID of the syscall. -fn from_name(name: &str) -> Option { - let id = match name.to_lowercase().as_str() { - "system.hascart" | "system.has_cart" => Syscall::SystemHasCart, - "system.runcart" | "system.run_cart" => Syscall::SystemRunCart, - "gfx.clear" => Syscall::GfxClear, - "gfx.fillrect" | "gfx.draw_rect" => Syscall::GfxFillRect, - "gfx.drawline" | "gfx.draw_line" => Syscall::GfxDrawLine, - "gfx.drawcircle" | "gfx.draw_circle" => Syscall::GfxDrawCircle, - "gfx.drawdisc" | "gfx.draw_disc" => Syscall::GfxDrawDisc, - "gfx.drawsquare" | "gfx.draw_square" => Syscall::GfxDrawSquare, - "gfx.setsprite" | "gfx.set_sprite" => Syscall::GfxSetSprite, - "gfx.drawtext" | "gfx.draw_text" => Syscall::GfxDrawText, - "input.getpad" | "input.get_pad" => Syscall::InputGetPad, - "input.getpadpressed" | "input.get_pad_pressed" => Syscall::InputGetPadPressed, - "input.getpadreleased" | "input.get_pad_released" => Syscall::InputGetPadReleased, - "input.getpadhold" | "input.get_pad_hold" => Syscall::InputGetPadHold, - "touch.getx" | "touch.get_x" => Syscall::TouchGetX, - "touch.gety" | "touch.get_y" => Syscall::TouchGetY, - "touch.isdown" | "touch.is_down" => Syscall::TouchIsDown, - "touch.ispressed" | "touch.is_pressed" => Syscall::TouchIsPressed, - "touch.isreleased" | "touch.is_released" => Syscall::TouchIsReleased, - "touch.gethold" | "touch.get_hold" => Syscall::TouchGetHold, - "audio.playsample" | "audio.play_sample" => Syscall::AudioPlaySample, - "audio.play" => Syscall::AudioPlay, - "fs.open" => Syscall::FsOpen, - "fs.read" => Syscall::FsRead, - "fs.write" => Syscall::FsWrite, - "fs.close" => Syscall::FsClose, - "fs.listdir" | "fs.list_dir" => Syscall::FsListDir, - "fs.exists" => Syscall::FsExists, - "fs.delete" => Syscall::FsDelete, - "log.write" => Syscall::LogWrite, - "log.writetag" | "log.write_tag" => Syscall::LogWriteTag, - "asset.load" => Syscall::AssetLoad, - "asset.status" => Syscall::AssetStatus, - "asset.commit" => Syscall::AssetCommit, - "asset.cancel" => Syscall::AssetCancel, - "bank.info" => Syscall::BankInfo, - "bank.slotinfo" | "bank.slot_info" => Syscall::BankSlotInfo, - _ => return None, - }; - Some(id as u32) -} - diff --git a/crates/prometeu-compiler/src/frontends/lua/to_ir.rs b/crates/prometeu-compiler/src/frontends/lua/to_ir.rs deleted file mode 100644 index 8be034b7..00000000 --- a/crates/prometeu-compiler/src/frontends/lua/to_ir.rs +++ /dev/null @@ -1,831 +0,0 @@ -use crate::common::diagnostics::DiagnosticBundle; -use crate::common::spans::Span; -use crate::frontends::lua::resolve::SymbolTable; -use crate::frontends::lua::syscall_map; -use crate::ir::instr::{InstrKind, Instruction, Label}; -use crate::ir::module::{Function, Module, Param}; -use crate::ir::types::Type; -use full_moon::ast::{Ast, BinOp, Expression, FunctionArgs, Index, Prefix, Stmt, Suffix, UnOp, Var}; -use full_moon::node::Node; -use prometeu_core::model::Color; -use crate::frontends::lua::input_map; -use std::collections::HashMap; -use std::sync::{Arc, Mutex}; - -#[derive(Debug, Clone, Default)] -pub struct SharedGlobalTable { - pub map: HashMap, - pub next_slot: u32, -} - -#[derive(Debug, Clone)] -pub enum Symbol { - Local(u32), - Global(u32), -} - -pub struct ToIR { - instructions: Vec, - label_counter: u32, - locals: Vec>, - next_local_slot: u32, - shared_globals: Arc>, - file_id: usize, - module_prefix: Option, - is_function: bool, - module_exit_label: Option, -} - -impl ToIR { - pub fn new(file_id: usize, shared_globals: Arc>, module_prefix: Option, is_function: bool) -> Self { - Self { - instructions: Vec::new(), - label_counter: 0, - locals: vec![HashMap::new()], - next_local_slot: 0, - shared_globals, - file_id, - module_prefix, - is_function, - module_exit_label: None, - } - } - - fn get_span(&self, node: impl Node) -> Option { - node.tokens().next().and_then(|t| { - let start = t.token().start_position(); - node.tokens().last().map(|last_t| { - let end = last_t.token().end_position(); - Span::new(self.file_id, start.bytes() as u32, end.bytes() as u32) - }) - }) - } - - pub fn convert(&mut self, ast: &Ast, _symbols: SymbolTable) -> Result { - let mut module = Module::new("main".to_string()); - let exit_label = self.new_label("module_exit"); - self.module_exit_label = Some(exit_label.clone()); - - let block = ast.nodes(); - - // Coletar funções - for stmt in block.stmts() { - if let Stmt::FunctionDeclaration(f) = stmt { - let mut name = f.name().to_string().trim().to_string(); - - // Mangle global functions if not in entry point - if let Some(prefix) = &self.module_prefix { - if !name.contains('.') && name != "frame" { - name = format!("{}.{}", prefix, name); - } else if name.starts_with("M.") { - name = format!("{}.{}", prefix, &name[2..]); - } - } - - let mut function_to_ir = ToIR::new(self.file_id, self.shared_globals.clone(), self.module_prefix.clone(), true); - // Adicionar parâmetros como locais - for param in f.body().parameters() { - if let full_moon::ast::Parameter::Name(token) = param { - let param_name = token.to_string().trim().to_string(); - function_to_ir.declare_local(¶m_name); - } - } - - let func_ir = function_to_ir.compile_function(&name, f)?; - module.functions.push(func_ir); - } else if let Stmt::Assignment(assignment) = stmt { - // Handle M.func = function(...) ... end - let variables: Vec<_> = assignment.variables().iter().collect(); - let exprs: Vec<_> = assignment.expressions().iter().collect(); - - for (i, var) in variables.iter().enumerate() { - if let Some(Expression::Function(f)) = exprs.get(i) { - if let Var::Expression(var_expr) = var { - let prefix = var_expr.prefix(); - let suffixes: Vec<_> = var_expr.suffixes().collect(); - if !suffixes.is_empty() { - let prefix_name = match prefix { - Prefix::Name(token) => token.to_string().trim().to_string(), - _ => continue, - }; - - // Special case: if prefix is local, don't use it as global name prefix - let mut name = if self.resolve_local(&prefix_name).is_some() { - let mut n = String::new(); - for (j, suffix) in suffixes.iter().enumerate() { - if let Suffix::Index(Index::Dot { name: dot_name, .. }) = suffix { - if j > 0 { n.push('.'); } - n.push_str(&dot_name.to_string().trim()); - } - } - n - } else { - let mut n = prefix_name; - for suffix in suffixes { - if let Suffix::Index(Index::Dot { name: dot_name, .. }) = suffix { - n.push('.'); - n.push_str(&dot_name.to_string().trim()); - } - } - n - }; - - if name.is_empty() { continue; } - - // Mangle - if let Some(mod_prefix) = &self.module_prefix { - if !name.contains('.') { - name = format!("{}.{}", mod_prefix, name); - } - } - - let mut function_to_ir = ToIR::new(self.file_id, self.shared_globals.clone(), self.module_prefix.clone(), true); - // Params - for param in f.1.parameters() { - if let full_moon::ast::Parameter::Name(token) = param { - function_to_ir.declare_local(&token.to_string().trim()); - } - } - - let func_ir = function_to_ir.compile_function_body(&name, f.1.block(), f.1.parameters())?; - module.functions.push(func_ir); - } - } - } - } - } - } - - // Compilar o corpo principal do script para __init - self.instructions.clear(); - for stmt in block.stmts() { - if !matches!(stmt, Stmt::FunctionDeclaration(_)) { - // Skip assignments that were compiled as functions - if let Stmt::Assignment(assignment) = stmt { - if assignment.expressions().iter().any(|e| matches!(e, Expression::Function(_))) { - continue; - } - } - self.compile_stmt(stmt)?; - } - } - - if let Some(last_stmt) = block.last_stmt() { - self.compile_last_stmt(last_stmt)?; - } - - self.emit_instr(InstrKind::Label(Label(exit_label)), None); - - module.functions.push(Function { - name: "__init".to_string(), - params: Vec::new(), - return_type: Type::Void, - body: self.instructions.clone(), - }); - - Ok(module) - } - - fn compile_function(&mut self, name: &str, f: &full_moon::ast::FunctionDeclaration) -> Result { - self.compile_function_body(name, f.body().block(), f.body().parameters()) - } - - fn compile_function_body(&mut self, name: &str, body_block: &full_moon::ast::Block, parameters: &full_moon::ast::punctuated::Punctuated) -> Result { - let mut params = Vec::new(); - for param in parameters { - if let full_moon::ast::Parameter::Name(token) = param { - params.push(Param { - name: token.to_string().trim().to_string(), - r#type: Type::Any, - }); - } - } - - for stmt in body_block.stmts() { - self.compile_stmt(stmt)?; - } - - if let Some(last_stmt) = body_block.last_stmt() { - self.compile_last_stmt(last_stmt)?; - } - - // Retorno implícito - self.emit_instr(InstrKind::PushNull, None); - self.emit_instr(InstrKind::Ret, None); - - Ok(Function { - name: name.to_string(), - params, - return_type: Type::Any, - body: self.instructions.clone(), - }) - } - - fn compile_stmt(&mut self, stmt: &Stmt) -> Result<(), DiagnosticBundle> { - match stmt { - Stmt::LocalAssignment(assignment) => { - let names: Vec<_> = assignment.names().iter().collect(); - let exprs: Vec<_> = assignment.expressions().iter().collect(); - - for (i, name) in names.iter().enumerate() { - if let Some(expr) = exprs.get(i) { - self.compile_expr(expr)?; - } else { - self.emit_instr(InstrKind::PushNull, self.get_span(name)); - } - let symbol = self.declare_local(&name.to_string().trim()); - let kind = match symbol { - Symbol::Local(s) => InstrKind::SetLocal(s), - Symbol::Global(s) => InstrKind::SetGlobal(s), - }; - self.emit_instr(kind, self.get_span(name)); - } - } - Stmt::Assignment(assignment) => { - let variables: Vec<_> = assignment.variables().iter().collect(); - let exprs: Vec<_> = assignment.expressions().iter().collect(); - - for (i, var) in variables.iter().enumerate() { - if let Some(expr) = exprs.get(i) { - self.compile_expr(expr)?; - } else { - self.emit_instr(InstrKind::PushNull, self.get_span(var)); - } - - // Resolver variável - self.emit_assignment(var)?; - } - } - Stmt::FunctionCall(call) => { - self.compile_call(call)?; - self.emit_instr(InstrKind::Pop, self.get_span(call)); // Descartar retorno se usado como statement - } - Stmt::If(if_stmt) => { - let end_label = Label(self.new_label("if_end")); - - // if block - let next_block_label = Label(self.new_label("next_block")); - self.compile_expr(if_stmt.condition())?; - self.emit_instr(InstrKind::JmpIfFalse(next_block_label.clone()), self.get_span(if_stmt.condition())); - - self.enter_scope(); - for s in if_stmt.block().stmts() { - self.compile_stmt(s)?; - } - self.exit_scope(); - - self.emit_instr(InstrKind::Jmp(end_label.clone()), self.get_span(if_stmt)); - self.emit_instr(InstrKind::Label(next_block_label), None); - - // else_if blocks - if let Some(else_ifs) = if_stmt.else_if() { - for else_if in else_ifs { - let next_elseif_label = Label(self.new_label("next_elseif")); - self.compile_expr(else_if.condition())?; - self.emit_instr(InstrKind::JmpIfFalse(next_elseif_label.clone()), self.get_span(else_if.condition())); - - self.enter_scope(); - for s in else_if.block().stmts() { - self.compile_stmt(s)?; - } - self.exit_scope(); - - self.emit_instr(InstrKind::Jmp(end_label.clone()), self.get_span(else_if)); - self.emit_instr(InstrKind::Label(next_elseif_label), None); - } - } - - if let Some(else_block) = if_stmt.else_block() { - self.enter_scope(); - for s in else_block.stmts() { - self.compile_stmt(s)?; - } - self.exit_scope(); - } - - self.emit_instr(InstrKind::Label(end_label), None); - } - Stmt::While(while_stmt) => { - let start_label = Label(self.new_label("while_start")); - let end_label = Label(self.new_label("while_end")); - - self.emit_instr(InstrKind::Label(start_label.clone()), None); - self.compile_expr(while_stmt.condition())?; - self.emit_instr(InstrKind::JmpIfFalse(end_label.clone()), self.get_span(while_stmt.condition())); - - self.enter_scope(); - for s in while_stmt.block().stmts() { - self.compile_stmt(s)?; - } - self.exit_scope(); - - self.emit_instr(InstrKind::Jmp(start_label), self.get_span(while_stmt)); - self.emit_instr(InstrKind::Label(end_label), None); - } - Stmt::Do(do_stmt) => { - self.enter_scope(); - for s in do_stmt.block().stmts() { - self.compile_stmt(s)?; - } - if let Some(last) = do_stmt.block().last_stmt() { - self.compile_last_stmt(last)?; - } - self.exit_scope(); - } - _ => {} - } - Ok(()) - } - - fn compile_last_stmt(&mut self, last_stmt: &full_moon::ast::LastStmt) -> Result<(), DiagnosticBundle> { - match last_stmt { - full_moon::ast::LastStmt::Return(ret) => { - if let Some(expr) = ret.returns().iter().next() { - self.compile_expr(expr)?; - } else { - self.emit_instr(InstrKind::PushNull, self.get_span(ret)); - } - - if self.is_function { - self.emit_instr(InstrKind::Ret, self.get_span(ret)); - } else if let Some(exit_label) = self.module_exit_label.clone() { - self.emit_instr(InstrKind::Pop, self.get_span(ret)); - self.emit_instr(InstrKind::Jmp(Label(exit_label)), self.get_span(ret)); - } else { - self.emit_instr(InstrKind::Pop, self.get_span(ret)); - } - } - _ => {} - } - Ok(()) - } - - fn compile_expr(&mut self, expr: &Expression) -> Result<(), DiagnosticBundle> { - let span = self.get_span(expr); - match expr { - Expression::Number(token) => { - let s = token.to_string().trim().to_string(); - if let Ok(i) = s.parse::() { - self.emit_instr(InstrKind::PushInt(i), span); - } else if let Ok(f) = s.parse::() { - self.emit_instr(InstrKind::PushFloat(f), span); - } - } - Expression::Symbol(token) => { - let s = token.to_string().trim().to_string(); - if s == "true" || s == "false" { - self.emit_instr(InstrKind::PushBool(s == "true"), span); - } else if s == "nil" { - self.emit_instr(InstrKind::PushNull, span); - } else { - if let Some(symbol) = self.resolve_local(&s) { - let kind = match symbol { - Symbol::Local(s) => InstrKind::GetLocal(s), - Symbol::Global(s) => InstrKind::GetGlobal(s), - }; - self.emit_instr(kind, span); - } else { - let slot = self.get_or_create_global(&s); - self.emit_instr(InstrKind::GetGlobal(slot), span); - } - } - } - Expression::String(token) => { - let s = token.to_string(); - let s = s.trim().trim_matches(|c| c == '"' || c == '\''); - self.emit_instr(InstrKind::PushString(s.to_string()), span); - } - Expression::Var(var) => { - self.compile_var(var)?; - } - Expression::BinaryOperator { lhs, binop, rhs } => { - self.compile_expr(lhs)?; - self.compile_expr(rhs)?; - let kind = match binop { - BinOp::Plus(_) => InstrKind::Add, - BinOp::Minus(_) => InstrKind::Sub, - BinOp::Star(_) => InstrKind::Mul, - BinOp::Slash(_) => InstrKind::Div, - BinOp::TwoEqual(_) => InstrKind::Eq, - BinOp::TildeEqual(_) => InstrKind::Neq, - BinOp::LessThan(_) => InstrKind::Lt, - BinOp::LessThanEqual(_) => InstrKind::Lte, - BinOp::GreaterThan(_) => InstrKind::Gt, - BinOp::GreaterThanEqual(_) => InstrKind::Gte, - BinOp::And(_) => InstrKind::And, - BinOp::Or(_) => InstrKind::Or, - BinOp::TwoDots(_) => InstrKind::Concat, - _ => return Err(DiagnosticBundle::error(format!("Binary operator {:?} not supported", binop), span)), - }; - self.emit_instr(kind, span); - } - Expression::UnaryOperator { unop, expression } => { - self.compile_expr(expression)?; - let kind = match unop { - UnOp::Minus(_) => InstrKind::Neg, - UnOp::Not(_) => InstrKind::Not, - _ => return Err(DiagnosticBundle::error(format!("Unary operator {:?} not supported", unop), span)), - }; - self.emit_instr(kind, span); - } - Expression::FunctionCall(call) => { - self.compile_call(call)?; - } - Expression::TableConstructor(_) => { - // Return a null for now if we don't support tables yet, but don't fail compilation - // so we can see other errors. - // Actually color-square uses tables. - self.emit_instr(InstrKind::PushNull, span); - } - _ => return Err(DiagnosticBundle::error(format!("Expression type not supported: {:?}", expr), span)), - } - Ok(()) - } - - fn compile_var(&mut self, var: &Var) -> Result<(), DiagnosticBundle> { - let span = self.get_span(var); - match var { - Var::Name(token) => { - let name = token.to_string().trim().to_string(); - if name.to_lowercase().starts_with("color.") { - self.emit_color_constant(&name, span)?; - } else if name.to_lowercase().starts_with("pad.") || name.to_lowercase().starts_with("touch.") { - self.emit_input_mapping(&name, span)?; - } else if let Some(symbol) = self.resolve_local(&name) { - let kind = match symbol { - Symbol::Local(s) => InstrKind::GetLocal(s), - Symbol::Global(s) => InstrKind::GetGlobal(s), - }; - self.emit_instr(kind, span); - } else { - let slot = self.get_or_create_global(&name); - self.emit_instr(InstrKind::GetGlobal(slot), span); - } - } - Var::Expression(var_expr) => { - let prefix = var_expr.prefix(); - let suffixes: Vec<_> = var_expr.suffixes().collect(); - - if suffixes.is_empty() { - if let Prefix::Name(token) = prefix { - let name = token.to_string().trim().to_string(); - if name.to_lowercase().starts_with("color.") { - self.emit_color_constant(&name, span)?; - } else if name.to_lowercase().starts_with("pad.") || name.to_lowercase().starts_with("touch.") { - self.emit_input_mapping(&name, span)?; - } else if let Some(symbol) = self.resolve_local(&name) { - let kind = match symbol { - Symbol::Local(s) => InstrKind::GetLocal(s), - Symbol::Global(s) => InstrKind::GetGlobal(s), - }; - self.emit_instr(kind, span); - } else { - let slot = self.get_or_create_global(&name); - self.emit_instr(InstrKind::GetGlobal(slot), span); - } - } - } else { - let mut name = match prefix { - Prefix::Name(token) => token.to_string().trim().to_string(), - _ => return Err(DiagnosticBundle::error("Complex prefixes not supported".into(), span)), - }; - - for suffix in suffixes { - match suffix { - Suffix::Index(Index::Dot { name: dot_name, .. }) => { - name.push('.'); - name.push_str(&dot_name.to_string().trim()); - } - _ => return Err(DiagnosticBundle::error("Complex suffixes not supported".into(), span)), - } - } - - if name.to_lowercase().starts_with("color.") { - self.emit_color_constant(&name, span)?; - } else if name.to_lowercase().starts_with("pad.") || name.to_lowercase().starts_with("touch.") { - self.emit_input_mapping(&name, span)?; - } else { - let slot = self.get_or_create_global(&name); - self.emit_instr(InstrKind::GetGlobal(slot), span); - } - } - } - _ => return Err(DiagnosticBundle::error("Var type not supported".into(), span)), - } - Ok(()) - } - - fn emit_input_mapping(&mut self, name: &str, span: Option) -> Result<(), DiagnosticBundle> { - let name_lower = name.to_lowercase(); - if name_lower.starts_with("pad.") { - let parts: Vec<&str> = name_lower.split('.').collect(); - if parts.len() == 3 { - let btn_name = parts[1]; - let state_name = parts[2]; - let btn_id = input_map::map_btn_name(btn_name).map_err(|e| DiagnosticBundle::error(e.to_string(), span))?; - let syscall_name = input_map::map_pad_state(state_name).map_err(|e| DiagnosticBundle::error(e.to_string(), span))?; - let syscall_id = syscall_map::map_syscall(syscall_name).ok_or_else(|| DiagnosticBundle::error(format!("Syscall not found: {}", syscall_name), span))?; - - self.emit_instr(InstrKind::PushInt(btn_id as i64), span); - self.emit_instr(InstrKind::Syscall(syscall_id), span); - return Ok(()); - } - } else if name_lower.starts_with("touch.") { - let parts: Vec<&str> = name_lower.split('.').collect(); - match parts.len() { - 2 => { - let prop = parts[1]; - let syscall_name = input_map::map_touch_prop(prop).map_err(|e| DiagnosticBundle::error(e.to_string(), span))?; - let syscall_id = syscall_map::map_syscall(syscall_name).ok_or_else(|| DiagnosticBundle::error(format!("Touch syscall not found: {}", syscall_name), span))?; - self.emit_instr(InstrKind::Syscall(syscall_id), span); - return Ok(()); - } - 3 if parts[1] == "button" => { - let state_name = parts[2]; - let syscall_name = input_map::map_touch_button_state(state_name).map_err(|e| DiagnosticBundle::error(e.to_string(), span))?; - let syscall_id = syscall_map::map_syscall(syscall_name).ok_or_else(|| DiagnosticBundle::error(format!("Touch syscall not found: {}", syscall_name), span))?; - self.emit_instr(InstrKind::Syscall(syscall_id), span); - return Ok(()); - } - _ => {} - } - } - - Err(DiagnosticBundle::error(format!("Invalid input mapping: {}", name), span)) - } - - fn emit_color_constant(&mut self, name: &str, span: Option) -> Result<(), DiagnosticBundle> { - let val = match name.to_lowercase().as_str() { - "color.black" => Color::BLACK.raw(), - "color.white" => Color::WHITE.raw(), - "color.red" => Color::RED.raw(), - "color.green" => Color::GREEN.raw(), - "color.blue" => Color::BLUE.raw(), - "color.yellow" => Color::YELLOW.raw(), - "color.cyan" => Color::CYAN.raw(), - "color.gray" | "color.grey" => Color::GRAY.raw(), - "color.orange" => Color::ORANGE.raw(), - "color.indigo" => Color::INDIGO.raw(), - "color.magenta" => Color::MAGENTA.raw(), - "color.colorkey" | "color.color_key" => Color::COLOR_KEY.raw(), - _ => return Err(DiagnosticBundle::error(format!("Unsupported color constant: {}", name), span)), - }; - self.emit_instr(InstrKind::PushInt(val as i64), span); - Ok(()) - } - - fn compile_call(&mut self, call: &full_moon::ast::FunctionCall) -> Result<(), DiagnosticBundle> { - let span = self.get_span(call); - let prefix = call.prefix(); - let suffixes: Vec<_> = call.suffixes().collect(); - - let mut full_name = match prefix { - Prefix::Name(token) => token.to_string().trim().to_string(), - _ => return Err(DiagnosticBundle::error("Complex call prefixes not supported".into(), span)), - }; - - let mut args_store = None; - let mut is_method = false; - - for (i, suffix) in suffixes.iter().enumerate() { - match suffix { - Suffix::Index(Index::Dot { name: dot_name, .. }) => { - full_name.push('.'); - full_name.push_str(&dot_name.to_string().trim()); - } - Suffix::Call(full_moon::ast::Call::AnonymousCall(args)) => { - if i == suffixes.len() - 1 { - args_store = Some(args.clone()); - } else { - return Err(DiagnosticBundle::error("Chained calls not supported".into(), span)); - } - } - Suffix::Call(full_moon::ast::Call::MethodCall(method)) => { - if i == suffixes.len() - 1 { - full_name.push('.'); - full_name.push_str(&method.name().to_string().trim()); - args_store = Some(method.args().clone()); - is_method = true; - } else { - return Err(DiagnosticBundle::error("Chained method calls not supported".into(), span)); - } - } - _ => return Err(DiagnosticBundle::error("Unsupported suffix in call".into(), span)), - } - } - - let args = args_store.ok_or_else(|| DiagnosticBundle::error("Function call without args?".into(), span))?; - - // Special case for color.rgb - if full_name.to_lowercase() == "color.rgb" { - if let FunctionArgs::Parentheses { arguments, .. } = args { - let args_vec: Vec<_> = arguments.iter().collect(); - if args_vec.len() != 3 { - return Err(DiagnosticBundle::error("color.rgb expects 3 arguments".into(), span)); - } - - // r - self.compile_expr(args_vec[0])?; - self.emit_instr(InstrKind::PushInt(3), span); - self.emit_instr(InstrKind::Shr, span); - self.emit_instr(InstrKind::PushInt(11), span); - self.emit_instr(InstrKind::Shl, span); - - // g - self.compile_expr(args_vec[1])?; - self.emit_instr(InstrKind::PushInt(2), span); - self.emit_instr(InstrKind::Shr, span); - self.emit_instr(InstrKind::PushInt(5), span); - self.emit_instr(InstrKind::Shl, span); - - self.emit_instr(InstrKind::BitOr, span); - - // b - self.compile_expr(args_vec[2])?; - self.emit_instr(InstrKind::PushInt(3), span); - self.emit_instr(InstrKind::Shr, span); - - self.emit_instr(InstrKind::BitOr, span); - return Ok(()); - } - } - - // Special case for require - if full_name.to_lowercase() == "require" { - // In this subset, require is a compile-time hint. - // We push null as the return value. - self.emit_instr(InstrKind::PushNull, span); - return Ok(()); - } - - let mut arg_count = 0; - - if is_method { - // Push 'self' (o prefixo do call) - // Para simplificar o subset, se for algo como gfx:clear, não pushamos self se for syscall - // Mas se for um objeto real (que o subset não suporta muito bem ainda), pushamos. - if !syscall_map::map_syscall(&full_name).is_some() { - match prefix { - Prefix::Name(token) => { - let name = token.to_string().trim().to_string(); - if let Some(symbol) = self.resolve_local(&name) { - let kind = match symbol { - Symbol::Local(s) => InstrKind::GetLocal(s), - Symbol::Global(s) => InstrKind::GetGlobal(s), - }; - self.emit_instr(kind, span); - } else { - let slot = self.get_or_create_global(&name); - self.emit_instr(InstrKind::GetGlobal(slot), span); - } - arg_count += 1; - } - _ => {} - } - } - } - - match args { - FunctionArgs::Parentheses { arguments, .. } => { - for arg in arguments { - self.compile_expr(&arg)?; - arg_count += 1; - } - } - _ => return Err(DiagnosticBundle::error("Only parentheses arguments supported".into(), span)), - } - - // Verificar se é syscall - if let Some(syscall_id) = syscall_map::map_syscall(&full_name) { - self.emit_instr(InstrKind::Syscall(syscall_id), span); - } else { - self.emit_instr(InstrKind::Call { name: full_name, arg_count }, span); - } - - Ok(()) - } - - fn emit_assignment(&mut self, var: &Var) -> Result<(), DiagnosticBundle> { - let span = self.get_span(var); - match var { - Var::Name(token) => { - let name = token.to_string().trim().to_string(); - if let Some(symbol) = self.resolve_local(&name) { - let kind = match symbol { - Symbol::Local(s) => InstrKind::SetLocal(s), - Symbol::Global(s) => InstrKind::SetGlobal(s), - }; - self.emit_instr(kind, span); - } else { - let slot = self.get_or_create_global(&name); - self.emit_instr(InstrKind::SetGlobal(slot), span); - } - } - Var::Expression(var_expr) => { - let prefix = var_expr.prefix(); - let suffixes: Vec<_> = var_expr.suffixes().collect(); - - if suffixes.is_empty() { - if let Prefix::Name(token) = prefix { - let name = token.to_string().trim().to_string(); - if let Some(symbol) = self.resolve_local(&name) { - let kind = match symbol { - Symbol::Local(s) => InstrKind::SetLocal(s), - Symbol::Global(s) => InstrKind::SetGlobal(s), - }; - self.emit_instr(kind, span); - } else { - let slot = self.get_or_create_global(&name); - self.emit_instr(InstrKind::SetGlobal(slot), span); - } - } - } else { - let mut name = match prefix { - Prefix::Name(token) => token.to_string().trim().to_string(), - _ => return Err(DiagnosticBundle::error("Complex prefixes not supported".into(), span)), - }; - - for suffix in suffixes { - match suffix { - Suffix::Index(Index::Dot { name: dot_name, .. }) => { - name.push('.'); - name.push_str(&dot_name.to_string().trim()); - } - _ => return Err(DiagnosticBundle::error("Complex suffixes not supported in assignment".into(), span)), - } - } - let slot = self.get_or_create_global(&name); - self.emit_instr(InstrKind::SetGlobal(slot), span); - } - } - _ => return Err(DiagnosticBundle::error("Var type not supported in assignment".into(), span)), - } - Ok(()) - } - - fn declare_local(&mut self, name: &str) -> Symbol { - let symbol = if self.locals.len() == 1 { - let s = self.get_or_create_global(name); - Symbol::Global(s) - } else { - let s = self.next_local_slot; - self.next_local_slot += 1; - Symbol::Local(s) - }; - self.locals.last_mut().unwrap().insert(name.to_string(), symbol.clone()); - symbol - } - - fn resolve_local(&self, name: &str) -> Option { - for scope in self.locals.iter().rev() { - if let Some(symbol) = scope.get(name) { - return Some(symbol.clone()); - } - } - None - } - - fn get_or_create_global(&mut self, name: &str) -> u32 { - let mut full_name = name.to_string(); - - // Don't prefix standard namespaces - if !name.starts_with("gfx.") && !name.starts_with("pad.") && !name.starts_with("color.") && !name.starts_with("touch.") && !name.starts_with("audio.") && !name.starts_with("fs.") && !name.starts_with("log.") && !name.starts_with("asset.") && !name.starts_with("input.") { - if let Some(prefix) = &self.module_prefix { - if !name.contains('.') { - full_name = format!("{}.{}", prefix, name); - } - } - } - - let mut shared = self.shared_globals.lock().unwrap(); - if let Some(slot) = shared.map.get(&full_name) { - return *slot; - } - let slot = shared.next_slot; - shared.next_slot += 1; - shared.map.insert(full_name, slot); - slot - } - - fn enter_scope(&mut self) { - self.locals.push(HashMap::new()); - } - - fn exit_scope(&mut self) { - self.locals.pop(); - } - - fn new_label(&mut self, prefix: &str) -> String { - let label = if let Some(p) = &self.module_prefix { - format!("{}_{}_{}", p, prefix, self.label_counter) - } else { - format!("file{}_{}_{}", self.file_id, prefix, self.label_counter) - }; - self.label_counter += 1; - label - } - - fn emit_instr(&mut self, kind: InstrKind, span: Option) { - self.instructions.push(Instruction::new(kind, span)); - } -} diff --git a/crates/prometeu-compiler/src/frontends/lua/validate.rs b/crates/prometeu-compiler/src/frontends/lua/validate.rs deleted file mode 100644 index 2fb14197..00000000 --- a/crates/prometeu-compiler/src/frontends/lua/validate.rs +++ /dev/null @@ -1,184 +0,0 @@ -use full_moon::ast::{Ast, Stmt, LastStmt, Expression}; -use full_moon::node::Node; -use crate::common::diagnostics::{DiagnosticBundle, Diagnostic, DiagnosticLevel}; -use crate::common::spans::Span; - -pub fn validate_ast(ast: &Ast, file_id: usize) -> Result<(), DiagnosticBundle> { - let mut bundle = DiagnosticBundle::new(); - - let block = ast.nodes(); - for stmt in block.stmts() { - validate_stmt(stmt, &mut bundle, file_id); - } - - if let Some(last_stmt) = block.last_stmt() { - validate_last_stmt(last_stmt, &mut bundle, file_id); - } - - if bundle.diagnostics.is_empty() { - Ok(()) - } else { - Err(bundle) - } -} - -fn get_span(node: impl Node, file_id: usize) -> Option { - node.tokens().next().and_then(|t| { - let start = t.token().start_position(); - node.tokens().last().map(|last_t| { - let end = last_t.token().end_position(); - Span::new(file_id, start.bytes() as u32, end.bytes() as u32) - }) - }) -} - -fn validate_stmt(stmt: &Stmt, bundle: &mut DiagnosticBundle, file_id: usize) { - match stmt { - Stmt::LocalAssignment(assignment) => { - if assignment.expressions().len() == 0 { - bundle.push(Diagnostic { - message: "Local declaration must have an initializer in this subset.".into(), - level: DiagnosticLevel::Error, - span: get_span(assignment, file_id), - }); - } - // Check for multiple assignments (not supported in subset) - if assignment.names().len() > 1 || assignment.expressions().len() > 1 { - bundle.push(Diagnostic { - message: "Multiple assignments are not supported in this subset.".into(), - level: DiagnosticLevel::Error, - span: get_span(assignment, file_id), - }); - } - for expr in assignment.expressions() { - validate_expr(expr, bundle, file_id); - } - } - Stmt::Assignment(assignment) => { - if assignment.variables().len() > 1 || assignment.expressions().len() > 1 { - bundle.push(Diagnostic { - message: "Multiple assignments are not supported in this subset.".into(), - level: DiagnosticLevel::Error, - span: get_span(assignment, file_id), - }); - } - for expr in assignment.expressions() { - validate_expr(expr, bundle, file_id); - } - } - Stmt::FunctionCall(call) => { - validate_expr(&Expression::FunctionCall(call.clone()), bundle, file_id); - } - Stmt::If(if_stmt) => { - validate_expr(if_stmt.condition(), bundle, file_id); - for s in if_stmt.block().stmts() { - validate_stmt(s, bundle, file_id); - } - if let Some(else_ifs) = if_stmt.else_if() { - for else_if in else_ifs { - validate_expr(else_if.condition(), bundle, file_id); - for s in else_if.block().stmts() { - validate_stmt(s, bundle, file_id); - } - } - } - if let Some(else_block) = if_stmt.else_block() { - for s in else_block.stmts() { - validate_stmt(s, bundle, file_id); - } - } - } - Stmt::While(while_stmt) => { - validate_expr(while_stmt.condition(), bundle, file_id); - for s in while_stmt.block().stmts() { - validate_stmt(s, bundle, file_id); - } - } - Stmt::Do(do_stmt) => { - for s in do_stmt.block().stmts() { - validate_stmt(s, bundle, file_id); - } - if let Some(last) = do_stmt.block().last_stmt() { - validate_last_stmt(last, bundle, file_id); - } - } - Stmt::FunctionDeclaration(f) => { - // Check for varargs - if f.body().parameters().iter().any(|p| matches!(p, full_moon::ast::Parameter::Ellipsis(_))) { - bundle.push(Diagnostic { - message: "Varargs (...) are not supported in this subset.".into(), - level: DiagnosticLevel::Error, - span: get_span(f, file_id), - }); - } - for s in f.body().block().stmts() { - validate_stmt(s, bundle, file_id); - } - if let Some(last) = f.body().block().last_stmt() { - validate_last_stmt(last, bundle, file_id); - } - } - _ => { - // For color-square-lua, we might need to support more statement types if used. - // Let's see what's failing. - bundle.push(Diagnostic { - message: format!("Statement type not supported in this subset: {:?}", stmt), - level: DiagnosticLevel::Error, - span: get_span(stmt, file_id), - }); - } - } -} - -fn validate_expr(expr: &Expression, bundle: &mut DiagnosticBundle, file_id: usize) { - match expr { - Expression::BinaryOperator { lhs, rhs, .. } => { - validate_expr(lhs, bundle, file_id); - validate_expr(rhs, bundle, file_id); - } - Expression::UnaryOperator { expression, .. } => { - validate_expr(expression, bundle, file_id); - } - Expression::Number(_) | Expression::String(_) | Expression::Symbol(_) => {} - Expression::Var(_) => { - } - Expression::FunctionCall(_) => { - } - Expression::TableConstructor(_) => { - } - Expression::Function(_) => { - bundle.push(Diagnostic { - message: "Anonymous functions/closures are not supported in this subset.".into(), - level: DiagnosticLevel::Error, - span: get_span(expr, file_id), - }); - } - _ => { - bundle.push(Diagnostic { - message: "Expression type not supported in this subset.".into(), - level: DiagnosticLevel::Error, - span: get_span(expr, file_id), - }); - } - } -} - -fn validate_last_stmt(last_stmt: &LastStmt, bundle: &mut DiagnosticBundle, file_id: usize) { - match last_stmt { - LastStmt::Return(ret) => { - if ret.returns().len() > 1 { - bundle.push(Diagnostic { - message: "Multiple return values are not supported in this subset.".into(), - level: DiagnosticLevel::Error, - span: get_span(ret, file_id), - }); - } - for expr in ret.returns() { - validate_expr(expr, bundle, file_id); - } - } - LastStmt::Break(_) => { - } - _ => {} - } -} diff --git a/crates/prometeu-compiler/src/frontends/mod.rs b/crates/prometeu-compiler/src/frontends/mod.rs index 0afe5723..154c1142 100644 --- a/crates/prometeu-compiler/src/frontends/mod.rs +++ b/crates/prometeu-compiler/src/frontends/mod.rs @@ -2,8 +2,6 @@ use crate::common::diagnostics::DiagnosticBundle; use crate::ir; use std::path::Path; -pub mod ts; - use crate::common::files::FileManager; pub trait Frontend { diff --git a/crates/prometeu-compiler/src/frontends/ts/ast_util.rs b/crates/prometeu-compiler/src/frontends/ts/ast_util.rs deleted file mode 100644 index 0d6ba5f0..00000000 --- a/crates/prometeu-compiler/src/frontends/ts/ast_util.rs +++ /dev/null @@ -1,34 +0,0 @@ -use anyhow::{anyhow, Result}; -use oxc_ast::ast::*; - -/// Extracts the name of the function being called from a CallExpression. -pub fn get_callee_name(expr: &Expression) -> Result { - get_member_expr_name(expr) -} - -/// Recursively extracts a full name from an identifier or a member expression. -/// -/// Example: -/// - `foo` -> "foo" -/// - `Color.RED` -> "Color.RED" -/// - `PGfx.drawRect` -> "Gfx.drawRect" (Stripping 'P' prefix if appropriate) -pub fn get_member_expr_name(expr: &Expression) -> Result { - match expr { - Expression::Identifier(ident) => { - let name = ident.name.to_string(); - // Prometeu SDK uses 'P' prefix for some internal names to avoid conflicts - // with standard JS identifiers. We strip it here to match the ISA names. - if name.len() > 1 && name.starts_with('P') && name.chars().nth(1).unwrap().is_uppercase() { - Ok(name[1..].to_string()) - } else { - Ok(name) - } - } - Expression::StaticMemberExpression(member) => { - let obj = get_member_expr_name(&member.object)?; - let prop = member.property.name.to_string(); - Ok(format!("{}.{}", obj, prop)) - } - _ => Err(anyhow!("Unsupported expression for name resolution")), - } -} diff --git a/crates/prometeu-compiler/src/frontends/ts/input_map.rs b/crates/prometeu-compiler/src/frontends/ts/input_map.rs deleted file mode 100644 index ad2b5e43..00000000 --- a/crates/prometeu-compiler/src/frontends/ts/input_map.rs +++ /dev/null @@ -1,68 +0,0 @@ -use anyhow::anyhow; -use prometeu_core::model::ButtonId; - -/// Mapping of physical button names to their virtual Button ID. -/// -/// These constants match the `ButtonId` enum in `prometeu-core`. -pub const BTN_UP: u32 = ButtonId::Up as u32; -pub const BTN_DOWN: u32 = ButtonId::Down as u32; -pub const BTN_LEFT: u32 = ButtonId::Left as u32; -pub const BTN_RIGHT: u32 = ButtonId::Right as u32; -pub const BTN_A: u32 = ButtonId::A as u32; -pub const BTN_B: u32 = ButtonId::B as u32; -pub const BTN_X: u32 = ButtonId::X as u32; -pub const BTN_Y: u32 = ButtonId::Y as u32; -pub const BTN_L: u32 = ButtonId::L as u32; -pub const BTN_R: u32 = ButtonId::R as u32; -pub const BTN_START: u32 = ButtonId::Start as u32; -pub const BTN_SELECT: u32 = ButtonId::Select as u32; - -/// Translates a string identifier (e.g., "up") into a numeric Button ID. -pub fn map_btn_name(btn_name: &str) -> anyhow::Result { - match btn_name.to_lowercase().as_str() { - "up" => Ok(BTN_UP), - "down" => Ok(BTN_DOWN), - "left" => Ok(BTN_LEFT), - "right" => Ok(BTN_RIGHT), - "a" => Ok(BTN_A), - "b" => Ok(BTN_B), - "x" => Ok(BTN_X), - "y" => Ok(BTN_Y), - "l" => Ok(BTN_L), - "r" => Ok(BTN_R), - "start" => Ok(BTN_START), - "select" => Ok(BTN_SELECT), - _ => Err(anyhow!("Unsupported button: {}. Expected one of: up, down, left, right, a, b, x, y, l, r, start, select.", btn_name)), - } -} - -/// Translates a pad state name (e.g., "pressed") into the corresponding syscall name. -pub fn map_pad_state(state_name: &str) -> anyhow::Result<&'static str> { - match state_name { - "down" => Ok("input.getPad"), - "pressed" => Ok("input.getPadPressed"), - "released" => Ok("input.getPadReleased"), - "holdFrames" => Ok("input.getPadHold"), - _ => Err(anyhow!("Unsupported button state: {}. Expected one of: down, pressed, released, holdFrames.", state_name)), - } -} - -/// Translates a touch property name (e.g., "x") into the corresponding syscall name. -pub fn map_touch_prop(prop_name: &str) -> anyhow::Result<&'static str> { - match prop_name { - "x" => Ok("touch.getX"), - "y" => Ok("touch.getY"), - _ => Err(anyhow!("Unsupported touch property: {}. Expected one of: x, y.", prop_name)), - } -} - -/// Translates a touch button state name (e.g., "down") into the corresponding syscall name. -pub fn map_touch_button_state(state_name: &str) -> anyhow::Result<&'static str> { - match state_name { - "down" => Ok("touch.isDown"), - "pressed" => Ok("touch.isPressed"), - "released" => Ok("touch.isReleased"), - "holdFrames" => Ok("touch.getHold"), - _ => Err(anyhow!("Unsupported touch button state: {}. Expected one of: down, pressed, released, holdFrames.", state_name)), - } -} \ No newline at end of file diff --git a/crates/prometeu-compiler/src/frontends/ts/mod.rs b/crates/prometeu-compiler/src/frontends/ts/mod.rs deleted file mode 100644 index 52be255c..00000000 --- a/crates/prometeu-compiler/src/frontends/ts/mod.rs +++ /dev/null @@ -1,112 +0,0 @@ -//! # TypeScript Frontend -//! -//! This module implements the TypeScript/JavaScript frontend for the Prometeu Compiler. -//! It is responsible for: -//! 1. Parsing `.ts` files into an Abstract Syntax Tree (AST). -//! 2. Resolving imports and building a dependency graph. -//! 3. Validating that the source code uses only supported VM features. -//! 4. Lowering the high-level AST into the generic Intermediate Representation (IR). - -pub mod parse; -pub mod resolve; -pub mod validate; -pub mod to_ir; -pub mod ast_util; -pub mod input_map; -pub mod syscall_map; - -use crate::common::diagnostics::DiagnosticBundle; -use crate::frontends::Frontend; -use crate::ir; -use oxc_allocator::Allocator; -use std::collections::{HashMap, VecDeque}; -use std::fs; -use std::path::Path; - -/// The main entry point for the TypeScript compiler frontend. -pub struct TypescriptFrontend; - -impl Frontend for TypescriptFrontend { - fn language(&self) -> &'static str { - "typescript" - } - - /// Compiles a TypeScript entry file (and all its dependencies) into an IR Module. - /// - /// # The Compilation Pipeline: - /// - /// 1. **Discovery & Parsing**: - /// Starting from the `entry` file, it reads, parses, and discovers imports recursively. - /// It uses the `oxc` parser for high-performance AST generation. - /// - /// 2. **Semantic Validation**: - /// Each parsed module is checked against the `Validator`. Since Prometeu is a - /// specialized VM, it doesn't support the full range of TS/JS features (e.g., no `try/catch` yet). - /// - /// 3. **IR Lowering**: - /// Once all modules are parsed and validated, the `ToIR` engine traverses the ASTs - /// and emits equivalent IR instructions. - fn compile_to_ir( - &self, - entry: &Path, - file_manager: &mut crate::common::files::FileManager, - ) -> Result { - let allocator = Allocator::default(); - let mut modules = HashMap::new(); - let mut queue = VecDeque::new(); - - // Start with the entry point - let entry_abs = entry.canonicalize().map_err(|e| DiagnosticBundle::error(format!("Failed to canonicalize entry path: {}", e), None))?; - queue.push_back(entry_abs.clone()); - - // --- PHASE 1: Dependency Resolution and Parsing --- - // We traverse the dependency graph breadth-first to ensure all files are loaded. - while let Some(path) = queue.pop_front() { - let path_str: String = path.to_string_lossy().into_owned(); - if modules.contains_key(&path_str) { - continue; - } - - let source_text = fs::read_to_string(&path).map_err(|e| DiagnosticBundle::error(format!("Failed to read file: {}", e), None))?; - let file_id = file_manager.add(path.clone(), source_text.clone()); - let source_text_ptr = allocator.alloc_str(&source_text); - - // Parse the source into an AST - let program = parse::parse_file(&allocator, &path).map_err(|e| DiagnosticBundle::error(format!("Failed to parse module: {}", e), None))?; - - // --- PHASE 2: Individual Module Validation --- - // Ensure the code adheres to Prometeu's restricted subset of TypeScript. - validate::Validator::validate(&program).map_err(|e| DiagnosticBundle::error(format!("Validation error: {}", e), None))?; - - // Discover new imports and add them to the queue - for item in &program.body { - if let oxc_ast::ast::Statement::ImportDeclaration(decl) = item { - let import_path = decl.source.value.as_str(); - let resolved = resolve::resolve_import(&path, import_path).map_err(|e| DiagnosticBundle::error(format!("Resolve error: {}", e), None))?; - queue.push_back(resolved); - } - } - - modules.insert(path_str, (file_id, source_text_ptr, program)); - } - - // --- PHASE 3: To IR --- - // Collect all parsed modules and feed them into the IR generator. - let entry_str = entry_abs.to_string_lossy().to_string(); - let mut program_list = Vec::new(); - - let entry_data = modules.get(&entry_str).ok_or_else(|| DiagnosticBundle::error("Entry module not found".into(), None))?; - program_list.push((entry_data.0, entry_str.clone(), entry_data.1.to_string(), &entry_data.2)); - - for (path, (file_id, source, program)) in &modules { - if path != &entry_str { - program_list.push((*file_id, path.clone(), source.to_string(), program)); - } - } - - let mut to_ir_engine = to_ir::ToIR::new(entry_str.clone(), entry_data.1.to_string()); - let module = to_ir_engine.compile_to_ir(program_list).map_err(|e| DiagnosticBundle::error(format!("IR Generation error: {}", e), None))?; - - Ok(module) - } -} diff --git a/crates/prometeu-compiler/src/frontends/ts/parse.rs b/crates/prometeu-compiler/src/frontends/ts/parse.rs deleted file mode 100644 index 862429ae..00000000 --- a/crates/prometeu-compiler/src/frontends/ts/parse.rs +++ /dev/null @@ -1,25 +0,0 @@ -use anyhow::{anyhow, Context, Result}; -use oxc_allocator::Allocator; -use oxc_ast::ast::Program; -use oxc_parser::Parser; -use oxc_span::SourceType; -use std::fs; -use std::path::Path; - -pub fn parse_file<'a>(allocator: &'a Allocator, path: &Path) -> Result> { - let source_text = fs::read_to_string(path) - .with_context(|| format!("Failed to read file: {:?}", path))?; - - let source_text_ptr = allocator.alloc_str(&source_text); - let source_type = SourceType::from_path(path).unwrap_or_default(); - let parser_ret = Parser::new(allocator, source_text_ptr, source_type).parse(); - - if !parser_ret.errors.is_empty() { - for error in parser_ret.errors { - eprintln!("{:?}", error); - } - return Err(anyhow!("Failed to parse module: {:?}", path)); - } - - Ok(parser_ret.program) -} diff --git a/crates/prometeu-compiler/src/frontends/ts/resolve.rs b/crates/prometeu-compiler/src/frontends/ts/resolve.rs deleted file mode 100644 index fbbf645f..00000000 --- a/crates/prometeu-compiler/src/frontends/ts/resolve.rs +++ /dev/null @@ -1,22 +0,0 @@ -use anyhow::{anyhow, Result}; -use std::path::{Path, PathBuf}; - -/// Helper to resolve import paths (e.g., converting './utils' to './utils.ts'). -pub fn resolve_import(base_path: &Path, import_str: &str) -> Result { - let mut path = base_path.parent().unwrap().join(import_str); - - // Auto-append extensions if missing - if !path.exists() { - if path.with_extension("ts").exists() { - path.set_extension("ts"); - } else if path.with_extension("js").exists() { - path.set_extension("js"); - } - } - - if !path.exists() { - return Err(anyhow!("Cannot resolve import '{}' from {:?}", import_str, base_path)); - } - - Ok(path.canonicalize()?) -} diff --git a/crates/prometeu-compiler/src/frontends/ts/syscall_map.rs b/crates/prometeu-compiler/src/frontends/ts/syscall_map.rs deleted file mode 100644 index d10bf65e..00000000 --- a/crates/prometeu-compiler/src/frontends/ts/syscall_map.rs +++ /dev/null @@ -1,56 +0,0 @@ -use prometeu_core::hardware::Syscall; - -/// Maps a high-level function name to its corresponding Syscall ID in the Virtual Machine. -/// -/// This logic resides in the TypeScript frontend because string name resolution -/// from the source code is dependent on the language and the provided SDK. -pub fn map_syscall(name: &str) -> Option { - // Check if the name corresponds to a standard syscall defined in the core firmware - from_name(name) -} - -/// Converts a textual name (e.g., "gfx.fillRect") to the numeric ID of the syscall. -fn from_name(name: &str) -> Option { - let id = match name.to_lowercase().as_str() { - "system.hascart" | "system.has_cart" => Syscall::SystemHasCart, - "system.runcart" | "system.run_cart" => Syscall::SystemRunCart, - "gfx.clear" => Syscall::GfxClear, - "gfx.fillrect" | "gfx.draw_rect" => Syscall::GfxFillRect, - "gfx.drawline" | "gfx.draw_line" => Syscall::GfxDrawLine, - "gfx.drawcircle" | "gfx.draw_circle" => Syscall::GfxDrawCircle, - "gfx.drawdisc" | "gfx.draw_disc" => Syscall::GfxDrawDisc, - "gfx.drawsquare" | "gfx.draw_square" => Syscall::GfxDrawSquare, - "gfx.setsprite" | "gfx.set_sprite" => Syscall::GfxSetSprite, - "gfx.drawtext" | "gfx.draw_text" => Syscall::GfxDrawText, - "input.getpad" | "input.get_pad" => Syscall::InputGetPad, - "input.getpadpressed" | "input.get_pad_pressed" => Syscall::InputGetPadPressed, - "input.getpadreleased" | "input.get_pad_released" => Syscall::InputGetPadReleased, - "input.getpadhold" | "input.get_pad_hold" => Syscall::InputGetPadHold, - "touch.getx" | "touch.get_x" => Syscall::TouchGetX, - "touch.gety" | "touch.get_y" => Syscall::TouchGetY, - "touch.isdown" | "touch.is_down" => Syscall::TouchIsDown, - "touch.ispressed" | "touch.is_pressed" => Syscall::TouchIsPressed, - "touch.isreleased" | "touch.is_released" => Syscall::TouchIsReleased, - "touch.gethold" | "touch.get_hold" => Syscall::TouchGetHold, - "audio.playsample" | "audio.play_sample" => Syscall::AudioPlaySample, - "audio.play" => Syscall::AudioPlay, - "fs.open" => Syscall::FsOpen, - "fs.read" => Syscall::FsRead, - "fs.write" => Syscall::FsWrite, - "fs.close" => Syscall::FsClose, - "fs.listdir" | "fs.list_dir" => Syscall::FsListDir, - "fs.exists" => Syscall::FsExists, - "fs.delete" => Syscall::FsDelete, - "log.write" => Syscall::LogWrite, - "log.writetag" | "log.write_tag" => Syscall::LogWriteTag, - "asset.load" => Syscall::AssetLoad, - "asset.status" => Syscall::AssetStatus, - "asset.commit" => Syscall::AssetCommit, - "asset.cancel" => Syscall::AssetCancel, - "bank.info" => Syscall::BankInfo, - "bank.slotinfo" | "bank.slot_info" => Syscall::BankSlotInfo, - _ => return None, - }; - Some(id as u32) -} - diff --git a/crates/prometeu-compiler/src/frontends/ts/to_ir.rs b/crates/prometeu-compiler/src/frontends/ts/to_ir.rs deleted file mode 100644 index 7be2c9bf..00000000 --- a/crates/prometeu-compiler/src/frontends/ts/to_ir.rs +++ /dev/null @@ -1,803 +0,0 @@ -//! # To IR Lowering -//! -//! This module implements the core translation logic from the TypeScript AST (Abstract Syntax Tree) -//! into the Prometeu Intermediate Representation (IR). -//! -//! ## How it works: -//! -//! The `ToIR` engine traverses the AST and for each node (expression, statement, etc.), -//! it emits equivalent stack-based IR instructions. -//! -//! ### Symbol Resolution -//! It maintains a scoped symbol table to track local variables and their assigned slots. -//! It also manages global variables, which are mapped to permanent memory slots in the VM. -//! -//! ### Control Flow -//! High-level control flow (like `if`, `while`, `for`) is lowered into jumps and labels. -//! Unique labels are generated for each branch to avoid collisions. -//! -//! ### Example Translation: -//! **TS Source:** -//! ```typescript -//! let x = 10 + 20; -//! ``` -//! **Generated IR:** -//! ```text -//! PushInt(10) -//! PushInt(20) -//! Add -//! SetLocal(0) -//! ``` - -use crate::frontends::ts::syscall_map; -use crate::common::spans::Span as IRSpan; -use crate::frontends::ts::ast_util; -use crate::frontends::ts::input_map; -use crate::ir; -use crate::ir::instr::{InstrKind, Instruction as IRInstruction, Label as IRLabel}; -use anyhow::{anyhow, Result}; -use oxc_allocator::Vec as OXCVec; -use oxc_ast::ast::*; -use oxc_ast_visit::{walk, Visit}; -use oxc_span::{GetSpan, Span}; -use oxc_syntax::scope::ScopeFlags; -use prometeu_core::model::Color; -use std::collections::HashMap; - -/// Helper to count local variables and hoisted functions in a function body. -/// -/// This is used to determine how many slots need to be reserved on the stack frame -/// before executing a function. -struct LocalCounter { - count: u32, -} - -impl<'a> Visit<'a> for LocalCounter { - /// Discovers function declarations which also occupy a slot in the current frame. - fn visit_statement(&mut self, stmt: &Statement<'a>) { - match stmt { - Statement::FunctionDeclaration(f) => { - self.visit_function(f, ScopeFlags::empty()); - } - _ => walk::walk_statement(self, stmt), - } - } - - /// Discovers variable declarations (let/const/var) and increments the local count. - fn visit_variable_declaration(&mut self, decl: &VariableDeclaration<'a>) { - self.count += decl.declarations.len() as u32; - walk::walk_variable_declaration(self, decl); - } - - fn visit_function(&mut self, f: &Function<'a>, _flags: ScopeFlags) { - if f.id.is_some() { - self.count += 1; - } - // Stop recursion: nested functions have their own frames and locals. - } -} - -/// Metadata for a symbol (variable or function) in the symbol table. -struct SymbolEntry { - /// The unique index assigned to this variable in the current frame or global memory. - slot_index: u32, - /// Whether the variable was declared with `const`. - is_const: bool, - /// Tracks if the variable has been assigned a value (used for Temporal Dead Zone checks). - is_initialized: bool, -} - -/// The TS AST to Prometeu IR translator. -pub struct ToIR { - /// Name of the file being compiled (used for debug symbols). - file_name: String, - /// Full source code of the file (used for position lookup). - source_text: String, - /// ID of the file being compiled. - current_file_id: usize, - /// The stream of generated IR instructions. - pub instructions: Vec, - /// Scoped symbol table. Each element is a scope level containing a map of symbols. - /// This allows for variable shadowing and block scope resolution. - symbol_table: Vec>, - /// Current depth of the scope (0 is global/function top-level). - scope_depth: usize, - /// Mapping of global variable names to their slots in the VM's global memory. - globals: HashMap, - /// Counter for the next available local variable ID in the current function. - next_local: u32, - /// Counter for the next available global variable ID. - next_global: u32, - /// Counter for generating unique labels (e.g., for 'if' or 'while' blocks). - label_count: u32, -} - -impl ToIR { - /// Creates a new ToIR instance for a specific file. - pub fn new(file_name: String, source_text: String) -> Self { - Self { - file_name, - source_text, - current_file_id: 0, - instructions: Vec::new(), - symbol_table: Vec::new(), - scope_depth: 0, - globals: HashMap::new(), - next_local: 0, - next_global: 0, - label_count: 0, - } - } - - /// Enters a new scope level. - fn enter_scope(&mut self) { - self.symbol_table.push(HashMap::new()); - self.scope_depth += 1; - } - - /// Exits the current scope level. - fn exit_scope(&mut self) { - self.symbol_table.pop(); - self.scope_depth -= 1; - } - - /// Declares a new symbol in the current scope. - fn declare_symbol(&mut self, name: String, is_const: bool, is_initialized: bool, span: Span) -> Result<&mut SymbolEntry> { - let current_scope = self.symbol_table.last_mut().ok_or_else(|| anyhow!("No active scope"))?; - - if current_scope.contains_key(&name) { - return Err(anyhow!("Variable '{}' already declared in this scope at {:?}", name, span)); - } - - let slot_index = self.next_local; - self.next_local += 1; - - let entry = SymbolEntry { - slot_index, - is_const, - is_initialized, - }; - - current_scope.insert(name.clone(), entry); - Ok(self.symbol_table.last_mut().unwrap().get_mut(&name).unwrap()) - } - - /// Marks a symbol as initialized. - fn initialize_symbol(&mut self, name: &str) { - for scope in self.symbol_table.iter_mut().rev() { - if let Some(entry) = scope.get_mut(name) { - entry.is_initialized = true; - return; - } - } - } - - /// Resolves a symbol name to its entry, searching from inner to outer scopes. - fn resolve_symbol(&self, name: &str) -> Option<&SymbolEntry> { - for scope in self.symbol_table.iter().rev() { - if let Some(entry) = scope.get(name) { - return Some(entry); - } - } - None - } - - /// Discovers all function declarations in a program, including nested ones. - fn discover_functions<'a>( - &self, - file_id: usize, - file: String, - source: String, - program: &'a Program<'a>, - all_functions: &mut Vec<(usize, String, String, &'a Function<'a>)>, - ) { - struct Collector<'a, 'b> { - file_id: usize, - file: String, - source: String, - functions: &'b mut Vec<(usize, String, String, &'a Function<'a>)>, - } - - impl<'a, 'b> Visit<'a> for Collector<'a, 'b> { - fn visit_function(&mut self, f: &Function<'a>, flags: ScopeFlags) { - // Safety: The program AST lives long enough as it's owned by the caller - // of compile_programs and outlives the compilation process. - let f_ref = unsafe { std::mem::transmute::<&Function<'a>, &'a Function<'a>>(f) }; - self.functions.push((self.file_id, self.file.clone(), self.source.clone(), f_ref)); - walk::walk_function(self, f, flags); - } - } - - let mut collector = Collector { - file_id, - file, - source, - functions: all_functions, - }; - collector.visit_program(program); - } - - - - /// Compiles multiple programs (files) into a single Prometeu IR Module. - pub fn compile_to_ir(&mut self, programs: Vec<(usize, String, String, &Program)>) -> Result { - // --- FIRST PASS: Global Functions and Variables Collection --- - let mut all_functions_ast = Vec::new(); - - for (file_id, file, source, program) in &programs { - for item in &program.body { - match item { - Statement::ExportNamedDeclaration(decl) => { - if let Some(Declaration::VariableDeclaration(var)) = &decl.declaration { - self.export_global_variable_declarations(&var); - } - } - Statement::VariableDeclaration(var) => { - self.export_global_variable_declarations(&var); - } - _ => {} - } - } - - self.discover_functions(*file_id, file.clone(), source.clone(), program, &mut all_functions_ast); - } - - // --- ENTRY POINT VERIFICATION --- - let mut frame_fn_name = None; - if let Some((_, _, _, entry_program)) = programs.first() { - for item in &entry_program.body { - let f_opt = match item { - Statement::FunctionDeclaration(f) => Some(f.as_ref()), - Statement::ExportNamedDeclaration(decl) => { - if let Some(Declaration::FunctionDeclaration(f)) = &decl.declaration { - Some(f.as_ref()) - } else { - None - } - } - _ => None, - }; - - if let Some(f) = f_opt { - if let Some(ident) = &f.id { - if ident.name == "frame" { - frame_fn_name = Some(ident.name.to_string()); - break; - } - } - } - } - } - - let frame_fn_name = frame_fn_name.ok_or_else(|| anyhow!("export function frame() not found in entry file"))?; - - let mut module = ir::Module::new("main".to_string()); - - // Populate globals in IR - for (name, slot) in &self.globals { - module.globals.push(ir::module::Global { - name: name.clone(), - r#type: ir::types::Type::Any, - slot: *slot, - }); - } - - // --- GLOBAL INITIALIZATION AND MAIN LOOP (__init) --- - self.instructions.clear(); - for (file_id, file, source, program) in &programs { - self.file_name = file.clone(); - self.source_text = source.clone(); - self.current_file_id = *file_id; - for item in &program.body { - let var_opt = match item { - Statement::VariableDeclaration(var) => Some(var.as_ref()), - Statement::ExportNamedDeclaration(decl) => { - if let Some(Declaration::VariableDeclaration(var)) = &decl.declaration { - Some(var.as_ref()) - } else { - None - } - } - _ => None, - }; - - if let Some(var) = var_opt { - for decl in &var.declarations { - if let BindingPattern::BindingIdentifier(ident) = &decl.id { - let name = ident.name.to_string(); - let id = *self.globals.get(&name).unwrap(); - if let Some(init) = &decl.init { - self.compile_expr(init)?; - } else { - self.emit_instr(InstrKind::PushInt(0), decl.span); - } - self.emit_instr(InstrKind::SetGlobal(id), decl.span); - } - } - } - } - } - - self.emit_label("entry".to_string()); - self.emit_instr(InstrKind::Call { name: frame_fn_name, arg_count: 0 }, Span::default()); - self.emit_instr(InstrKind::Pop, Span::default()); - self.emit_instr(InstrKind::FrameSync, Span::default()); - self.emit_instr(InstrKind::Jmp(IRLabel("entry".to_string())), Span::default()); - - module.functions.push(ir::module::Function { - name: "__init".to_string(), - params: Vec::new(), - return_type: ir::types::Type::Void, - body: self.instructions.clone(), - }); - - // --- FUNCTION COMPILATION --- - for (file_id, file, source, f) in all_functions_ast { - self.instructions.clear(); - self.file_name = file; - self.source_text = source; - self.current_file_id = file_id; - if let Some(ident) = &f.id { - let name = ident.name.to_string(); - self.compile_function(f)?; - self.emit_instr(InstrKind::Ret, Span::default()); - - module.functions.push(ir::module::Function { - name, - params: Vec::new(), // TODO: map parameters to IR - return_type: ir::types::Type::Any, - body: self.instructions.clone(), - }); - } - } - - Ok(module) - } - - /// Registers a global variable in the symbol table. - /// Global variables are accessible from any function and persist between frames. - fn export_global_variable_declarations(&mut self, var: &VariableDeclaration) { - for decl in &var.declarations { - if let BindingPattern::BindingIdentifier(ident) = &decl.id { - let name = ident.name.to_string(); - if !self.globals.contains_key(&name) { - let id = self.next_global; - self.globals.insert(name, id); - self.next_global += 1; - } - } - } - } - - /// Compiles a function declaration. - /// - /// Functions in Prometeu follow the ABI: - /// 1. Parameters are mapped to the first `n` local slots. - /// 2. `PushScope` is called to protect the caller's environment. - /// 3. The body is compiled sequentially. - /// 4. `PopScope` and `Push Null` are executed before `Ret` to ensure the stack rule. - fn compile_function(&mut self, f: &Function) -> Result<()> { - self.symbol_table.clear(); - self.scope_depth = 0; - self.next_local = 0; - - // Start scope for parameters and local variables - self.enter_scope(); - self.emit_instr(InstrKind::PushScope, f.span); - - // Map parameters to locals (they are pushed by the caller before the Call instruction) - for param in &f.params.items { - if let BindingPattern::BindingIdentifier(ident) = ¶m.pattern { - let name = ident.name.to_string(); - // Parameters are considered initialized - self.declare_symbol(name, false, true, ident.span)?; - } - } - - if let Some(body) = &f.body { - // Reserve slots for all local variables and hoisted functions - let locals_to_reserve = self.count_locals(&body.statements); - for _ in 0..locals_to_reserve { - // Initializing with I32 0 as it's the safest default for Prometeu VM - self.emit_instr(InstrKind::PushInt(0), f.span); - } - - // Function and Variable hoisting within the function scope - self.hoist_functions(&body.statements)?; - self.hoist_variables(&body.statements)?; - - for stmt in &body.statements { - self.compile_stmt(stmt)?; - } - } - - // ABI Rule: Every function MUST leave exactly one value on the stack before RET. - // If the function doesn't have a return statement, we push Null. - self.emit_instr(InstrKind::PopScope, Span::default()); - self.emit_instr(InstrKind::PushNull, Span::default()); - Ok(()) - } - - /// Counts the total number of local variable and function declarations in a function body. - fn count_locals(&self, statements: &OXCVec) -> u32 { - let mut counter = LocalCounter { count: 0 }; - for stmt in statements { - counter.visit_statement(stmt); - } - counter.count - } - - /// Hoists function declarations to the top of the current scope. - fn hoist_functions(&mut self, statements: &OXCVec) -> Result<()> { - for stmt in statements { - if let Statement::FunctionDeclaration(f) = stmt { - if let Some(ident) = &f.id { - let name = ident.name.to_string(); - // Functions are hoisted and already considered initialized - self.declare_symbol(name, false, true, ident.span)?; - } - } - } - Ok(()) - } - - /// Hoists variable declarations (let/const) to the top of the current scope. - fn hoist_variables(&mut self, statements: &OXCVec) -> Result<()> { - for stmt in statements { - if let Statement::VariableDeclaration(var) = stmt { - let is_const = var.kind == VariableDeclarationKind::Const; - for decl in &var.declarations { - if let BindingPattern::BindingIdentifier(ident) = &decl.id { - let name = ident.name.to_string(); - // Register as uninitialized for TDZ - self.declare_symbol(name, is_const, false, ident.span)?; - } - } - } - } - Ok(()) - } - - /// Translates a Statement into bytecode. - fn compile_stmt(&mut self, stmt: &Statement) -> Result<()> { - match stmt { - // var x = 10; - Statement::VariableDeclaration(var) => { - let is_const = var.kind == VariableDeclarationKind::Const; - for decl in &var.declarations { - if let BindingPattern::BindingIdentifier(ident) = &decl.id { - let name = ident.name.to_string(); - - // Variable should already be in the symbol table due to hoisting - let entry = self.resolve_symbol(&name) - .ok_or_else(|| anyhow!("Internal compiler error: symbol '{}' not hoisted at {:?}", name, ident.span))?; - - let slot_index = entry.slot_index; - - if let Some(init) = &decl.init { - self.compile_expr(init)?; - self.emit_instr(InstrKind::SetLocal(slot_index), decl.span); - self.initialize_symbol(&name); - } else { - if is_const { - return Err(anyhow!("Missing initializer in const declaration at {:?}", decl.span)); - } - // Default initialization to 0 - self.emit_instr(InstrKind::PushInt(0), decl.span); - self.emit_instr(InstrKind::SetLocal(slot_index), decl.span); - self.initialize_symbol(&name); - } - } - } - } - // console.log("hello"); - Statement::ExpressionStatement(expr_stmt) => { - self.compile_expr(&expr_stmt.expression)?; - // ABI requires us to Pop unused return values from the stack to prevent leaks - self.emit_instr(InstrKind::Pop, expr_stmt.span); - } - // if (a == b) { ... } else { ... } - Statement::IfStatement(if_stmt) => { - let else_label = self.new_label("else"); - let end_label = self.new_label("end_if"); - - self.compile_expr(&if_stmt.test)?; - self.emit_instr(InstrKind::JmpIfFalse(IRLabel(else_label.clone())), if_stmt.span); - - self.compile_stmt(&if_stmt.consequent)?; - self.emit_instr(InstrKind::Jmp(IRLabel(end_label.clone())), Span::default()); - - self.emit_label(else_label); - if let Some(alt) = &if_stmt.alternate { - self.compile_stmt(alt)?; - } - - self.emit_label(end_label); - } - // { let x = 1; } - Statement::BlockStatement(block) => { - self.enter_scope(); - self.emit_instr(InstrKind::PushScope, block.span); - - // Hoist functions and variables in the block - self.hoist_functions(&block.body)?; - self.hoist_variables(&block.body)?; - - for stmt in &block.body { - self.compile_stmt(stmt)?; - } - - self.emit_instr(InstrKind::PopScope, block.span); - self.exit_scope(); - } - // Function declarations are handled by hoisting and compiled separately - Statement::FunctionDeclaration(_) => {} - _ => return Err(anyhow!("Unsupported statement type at {:?}", stmt.span())), - } - Ok(()) - } - - /// Translates an Expression into bytecode. - /// Expressions always leave exactly one value at the top of the stack. - fn compile_expr(&mut self, expr: &Expression) -> Result<()> { - match expr { - // Literals: push the value directly onto the stack - Expression::NumericLiteral(n) => { - let val = n.value; - if val.fract() == 0.0 && val >= i32::MIN as f64 && val <= i32::MAX as f64 { - self.emit_instr(InstrKind::PushInt(val as i64), n.span); - } else { - self.emit_instr(InstrKind::PushFloat(val), n.span); - } - } - Expression::BooleanLiteral(b) => { - self.emit_instr(InstrKind::PushBool(b.value), b.span); - } - Expression::StringLiteral(s) => { - self.emit_instr(InstrKind::PushString(s.value.to_string()), s.span); - } - Expression::NullLiteral(n) => { - self.emit_instr(InstrKind::PushNull, n.span); - } - // Variable access: resolve to GetLocal or GetGlobal - Expression::Identifier(ident) => { - let name = ident.name.to_string(); - if let Some(entry) = self.resolve_symbol(&name) { - if !entry.is_initialized { - return Err(anyhow!("TDZ Violation: Variable '{}' accessed before initialization at {:?}", name, ident.span)); - } - self.emit_instr(InstrKind::GetLocal(entry.slot_index), ident.span); - } else if let Some(&id) = self.globals.get(&name) { - self.emit_instr(InstrKind::GetGlobal(id), ident.span); - } else { - return Err(anyhow!("Undefined variable: {} at {:?}", name, ident.span)); - } - } - // Assignment: evaluate RHS and store result in LHS slot - Expression::AssignmentExpression(assign) => { - if let AssignmentTarget::AssignmentTargetIdentifier(ident) = &assign.left { - let name = ident.name.to_string(); - - if let Some(entry) = self.resolve_symbol(&name) { - if entry.is_const { - return Err(anyhow!("Assignment to constant variable '{}' at {:?}", name, assign.span)); - } - if !entry.is_initialized { - return Err(anyhow!("TDZ Violation: Variable '{}' accessed before initialization at {:?}", name, assign.span)); - } - let slot_index = entry.slot_index; - self.compile_expr(&assign.right)?; - self.emit_instr(InstrKind::SetLocal(slot_index), assign.span); - self.emit_instr(InstrKind::GetLocal(slot_index), assign.span); // Assignment returns the value - } else if let Some(&id) = self.globals.get(&name) { - self.compile_expr(&assign.right)?; - self.emit_instr(InstrKind::SetGlobal(id), assign.span); - self.emit_instr(InstrKind::GetGlobal(id), assign.span); - } else { - return Err(anyhow!("Undefined variable: {} at {:?}", name, ident.span)); - } - } else { - return Err(anyhow!("Unsupported assignment target at {:?}", assign.span)); - } - } - // Binary operations: evaluate both sides and apply the opcode - Expression::BinaryExpression(bin) => { - self.compile_expr(&bin.left)?; - self.compile_expr(&bin.right)?; - let kind = match bin.operator { - BinaryOperator::Addition => InstrKind::Add, - BinaryOperator::Subtraction => InstrKind::Sub, - BinaryOperator::Multiplication => InstrKind::Mul, - BinaryOperator::Division => InstrKind::Div, - BinaryOperator::Equality => InstrKind::Eq, - BinaryOperator::Inequality => InstrKind::Neq, - BinaryOperator::LessThan => InstrKind::Lt, - BinaryOperator::GreaterThan => InstrKind::Gt, - BinaryOperator::LessEqualThan => InstrKind::Lte, - BinaryOperator::GreaterEqualThan => InstrKind::Gte, - _ => return Err(anyhow!("Unsupported binary operator {:?} at {:?}", bin.operator, bin.span)), - }; - self.emit_instr(kind, bin.span); - } - // Logical operations: evaluate both sides and apply the opcode - Expression::LogicalExpression(log) => { - self.compile_expr(&log.left)?; - self.compile_expr(&log.right)?; - let kind = match log.operator { - LogicalOperator::And => InstrKind::And, - LogicalOperator::Or => InstrKind::Or, - _ => return Err(anyhow!("Unsupported logical operator {:?} at {:?}", log.operator, log.span)), - }; - self.emit_instr(kind, log.span); - } - // Unary operations: evaluate argument and apply the opcode - Expression::UnaryExpression(unary) => { - self.compile_expr(&unary.argument)?; - let kind = match unary.operator { - UnaryOperator::UnaryNegation => InstrKind::Neg, - UnaryOperator::UnaryPlus => return Ok(()), - UnaryOperator::LogicalNot => InstrKind::Not, - _ => return Err(anyhow!("Unsupported unary operator {:?} at {:?}", unary.operator, unary.span)), - }; - self.emit_instr(kind, unary.span); - } - // Function calls: resolve to Syscall or Call - Expression::CallExpression(call) => { - let name = ast_util::get_callee_name(&call.callee)?; - let name_lower = name.to_lowercase(); - - if name_lower == "color.rgb" { - // Special case for Color.rgb(r, g, b) - // It's compiled to a sequence of bitwise operations for performance - if call.arguments.len() != 3 { - return Err(anyhow!("Color.rgb expects 3 arguments at {:?}", call.span)); - } - - // Argument 0: r (shift right 3, shift left 11) - if let Some(expr) = call.arguments[0].as_expression() { - self.compile_expr(expr)?; - self.emit_instr(InstrKind::PushInt(3), call.span); - self.emit_instr(InstrKind::Shr, call.span); - self.emit_instr(InstrKind::PushInt(11), call.span); - self.emit_instr(InstrKind::Shl, call.span); - } - - // Argument 1: g (shift right 2, shift left 5) - if let Some(expr) = call.arguments[1].as_expression() { - self.compile_expr(expr)?; - self.emit_instr(InstrKind::PushInt(2), call.span); - self.emit_instr(InstrKind::Shr, call.span); - self.emit_instr(InstrKind::PushInt(5), call.span); - self.emit_instr(InstrKind::Shl, call.span); - } - - self.emit_instr(InstrKind::BitOr, call.span); - - // Argument 2: b (shift right 3) - if let Some(expr) = call.arguments[2].as_expression() { - self.compile_expr(expr)?; - self.emit_instr(InstrKind::PushInt(3), call.span); - self.emit_instr(InstrKind::Shr, call.span); - } - - self.emit_instr(InstrKind::BitOr, call.span); - - } else if name_lower.starts_with("input.btn") || name_lower.starts_with("pinput.btn") { - // Special case for legacy Input.btnX() calls - // They map to input.getPad(BTN_X) - let btn_name = if name_lower.starts_with("pinput.btn") { - &name[10..] - } else { - &name[9..] - }; - - // Strip parentheses if any (though get_callee_name should handle just the callee) - let btn_name = btn_name.strip_suffix("()").unwrap_or(btn_name); - - let btn_id = input_map::map_btn_name(btn_name)?; - self.emit_instr(InstrKind::PushInt(btn_id as i64), call.span); - let pad_id = syscall_map::map_syscall("input.getPad").ok_or_else(|| anyhow!("input.getPad syscall not found"))?; - self.emit_instr(InstrKind::Syscall(pad_id), call.span); - - } else if let Some(syscall_id) = syscall_map::map_syscall(&name) { - // Standard System Call - for arg in &call.arguments { - if let Some(expr) = arg.as_expression() { - self.compile_expr(expr)?; - } - } - self.emit_instr(InstrKind::Syscall(syscall_id), call.span); - } else { - // Local function call (to a function defined in the project) - for arg in &call.arguments { - if let Some(expr) = arg.as_expression() { - self.compile_expr(expr)?; - } - } - self.emit_instr(InstrKind::Call { name, arg_count: call.arguments.len() as u32 }, call.span); - } - } - // Member access (e.g., Color.RED, Pad.A.down) - Expression::StaticMemberExpression(member) => { - let full_name = ast_util::get_member_expr_name(expr)?; - - if full_name.to_lowercase().starts_with("color.") { - // Resolved at compile-time to literal values - match full_name.to_lowercase().as_str() { - "color.black" => self.emit_instr(InstrKind::PushInt(Color::BLACK.raw() as i64), member.span), - "color.white" => self.emit_instr(InstrKind::PushInt(Color::WHITE.raw() as i64), member.span), - "color.red" => self.emit_instr(InstrKind::PushInt(Color::RED.raw() as i64), member.span), - "color.green" => self.emit_instr(InstrKind::PushInt(Color::GREEN.raw() as i64), member.span), - "color.blue" => self.emit_instr(InstrKind::PushInt(Color::BLUE.raw() as i64), member.span), - "color.yellow" => self.emit_instr(InstrKind::PushInt(Color::YELLOW.raw() as i64), member.span), - "color.cyan" => self.emit_instr(InstrKind::PushInt(Color::CYAN.raw() as i64), member.span), - "color.gray" | "color.grey" => self.emit_instr(InstrKind::PushInt(Color::GRAY.raw() as i64), member.span), - "color.orange" => self.emit_instr(InstrKind::PushInt(Color::ORANGE.raw() as i64), member.span), - "color.indigo" => self.emit_instr(InstrKind::PushInt(Color::INDIGO.raw() as i64), member.span), - "color.magenta" => self.emit_instr(InstrKind::PushInt(Color::MAGENTA.raw() as i64), member.span), - "color.colorKey" | "color.color_key" => self.emit_instr(InstrKind::PushInt(Color::COLOR_KEY.raw() as i64), member.span), - _ => return Err(anyhow!("Unsupported color constant: {} at {:?}", full_name, member.span)), - } - } else if full_name.to_lowercase().starts_with("pad.") { - // Re-mapped to specific input syscalls - let parts: Vec<&str> = full_name.split('.').collect(); - if parts.len() == 3 { - let btn_name = parts[1]; - let state_name = parts[2]; - let btn_id = input_map::map_btn_name(btn_name)?; - let syscall_name = input_map::map_pad_state(state_name)?; - let syscall_id = syscall_map::map_syscall(syscall_name).unwrap(); - self.emit_instr(InstrKind::PushInt(btn_id as i64), member.span); - self.emit_instr(InstrKind::Syscall(syscall_id), member.span); - } else { - return Err(anyhow!("Partial Pad access not supported: {} at {:?}", full_name, member.span)); - } - } else if full_name.to_lowercase().starts_with("touch.") { - // Re-mapped to specific touch syscalls - let parts: Vec<&str> = full_name.split('.').collect(); - match parts.len() { - 2 => { - let prop = parts[1]; - let syscall_name = input_map::map_touch_prop(prop)?; - let syscall_id = syscall_map::map_syscall(syscall_name).unwrap(); - self.emit_instr(InstrKind::Syscall(syscall_id), member.span); - } - 3 if parts[1] == "button" => { - let state_name = parts[2]; - let syscall_name = input_map::map_touch_button_state(state_name)?; - let syscall_id = syscall_map::map_syscall(syscall_name).unwrap(); - self.emit_instr(InstrKind::Syscall(syscall_id), member.span); - } - _ => return Err(anyhow!("Unsupported touch access: {} at {:?}", full_name, member.span)), - } - } else { - return Err(anyhow!("Member expression outside call not supported: {} at {:?}", full_name, member.span)); - } - } - _ => return Err(anyhow!("Unsupported expression type at {:?}", expr.span())), - } - Ok(()) - } - - /// Generates a new unique label name for control flow. - fn new_label(&mut self, prefix: &str) -> String { - let label = format!("{}_{}", prefix, self.label_count); - self.label_count += 1; - label - } - - /// Emits a label definition in the instruction stream. - fn emit_label(&mut self, name: String) { - self.instructions.push(IRInstruction::new(InstrKind::Label(IRLabel(name)), None)); - } - - /// Emits an IR instruction. - fn emit_instr(&mut self, kind: InstrKind, span: Span) { - let ir_span = if !span.is_unspanned() { - Some(IRSpan::new(0, span.start, span.end)) - } else { - None - }; - self.instructions.push(IRInstruction::new(kind, ir_span)); - } -} diff --git a/crates/prometeu-compiler/src/frontends/ts/validate.rs b/crates/prometeu-compiler/src/frontends/ts/validate.rs deleted file mode 100644 index bae9a9cd..00000000 --- a/crates/prometeu-compiler/src/frontends/ts/validate.rs +++ /dev/null @@ -1,183 +0,0 @@ -use crate::frontends::ts::syscall_map; -use crate::frontends::ts::ast_util; -use anyhow::{anyhow, Result}; -use oxc_ast::ast::*; -use oxc_ast_visit::{walk, Visit}; -use oxc_span::GetSpan; -use oxc_syntax::scope::ScopeFlags; - -/// AST Visitor that ensures the source code follows the Prometeu subset of JS/TS. -/// -/// Since the Prometeu Virtual Machine is highly optimized and focused on game logic, -/// it does not support the full ECMA-262 specification (e.g., no `async`, `class`, -/// `try/catch`, or `generators`). -pub struct Validator { - /// List of validation errors found during the pass. - errors: Vec, - /// Set of function names defined in the project, used to distinguish - /// local calls from invalid references. - local_functions: std::collections::HashSet, -} - -impl Validator { - /// Performs a full validation pass on a program. - /// - /// Returns `Ok(())` if the program is valid, or a combined error message - /// listing all violations. - pub fn validate(program: &Program) -> Result<()> { - let mut validator = Self { - errors: Vec::new(), - local_functions: std::collections::HashSet::new(), - }; - - // 1. Discovery Pass: Collect all function names and imports recursively - validator.discover_functions(program); - - // 2. Traversal Pass: Check every node for compatibility - validator.visit_program(program); - - if validator.errors.is_empty() { - Ok(()) - } else { - Err(anyhow!("Validation errors:\n{}", validator.errors.join("\n"))) - } - } - - /// Recursively discovers all function declarations in the program. - fn discover_functions(&mut self, program: &Program) { - struct FunctionDiscoverer<'a> { - functions: &'a mut std::collections::HashSet, - } - impl<'a, 'b> Visit<'b> for FunctionDiscoverer<'a> { - fn visit_function(&mut self, f: &Function<'b>, _flags: ScopeFlags) { - if let Some(ident) = &f.id { - self.functions.insert(ident.name.to_string()); - } - walk::walk_function(self, f, _flags); - } - fn visit_import_declaration(&mut self, decl: &ImportDeclaration<'b>) { - if let Some(specifiers) = &decl.specifiers { - for specifier in specifiers { - match specifier { - ImportDeclarationSpecifier::ImportSpecifier(s) => { - self.functions.insert(s.local.name.to_string()); - } - ImportDeclarationSpecifier::ImportDefaultSpecifier(s) => { - self.functions.insert(s.local.name.to_string()); - } - ImportDeclarationSpecifier::ImportNamespaceSpecifier(s) => { - self.functions.insert(s.local.name.to_string()); - } - } - } - } - } - } - let mut discoverer = FunctionDiscoverer { functions: &mut self.local_functions }; - discoverer.visit_program(program); - } -} - -impl<'a> Visit<'a> for Validator { - /// Validates that only supported expressions are used. - fn visit_expression(&mut self, expr: &Expression<'a>) { - match expr { - Expression::NumericLiteral(_) | - Expression::BooleanLiteral(_) | - Expression::StringLiteral(_) | - Expression::NullLiteral(_) | - Expression::Identifier(_) | - Expression::AssignmentExpression(_) | - Expression::BinaryExpression(_) | - Expression::LogicalExpression(_) | - Expression::UnaryExpression(_) | - Expression::CallExpression(_) | - Expression::StaticMemberExpression(_) => { - // Basic JS logic is supported. - walk::walk_expression(self, expr); - } - _ => { - self.errors.push(format!("Unsupported expression type at {:?}. Note: Closures, arrow functions, and object literals are not yet supported.", expr.span())); - } - } - } - - fn visit_call_expression(&mut self, expr: &CallExpression<'a>) { - if let Ok(name) = ast_util::get_callee_name(&expr.callee) { - let name_lower = name.to_lowercase(); - let is_intrinsic = name_lower == "color.rgb" || name_lower.starts_with("input.btn") || name_lower.starts_with("pinput.btn"); - - if !is_intrinsic && syscall_map::map_syscall(&name).is_none() && !self.local_functions.contains(&name) { - self.errors.push(format!("Unsupported function call: {} at {:?}", name, expr.span)); - } - } else { - self.errors.push(format!("Unsupported callee expression at {:?}", expr.callee.span())); - } - walk::walk_call_expression(self, expr); - } - - fn visit_unary_expression(&mut self, expr: &UnaryExpression<'a>) { - match expr.operator { - UnaryOperator::UnaryNegation | - UnaryOperator::UnaryPlus | - UnaryOperator::LogicalNot => { - walk::walk_unary_expression(self, expr); - } - _ => { - self.errors.push(format!("Unsupported unary operator {:?} at {:?}", expr.operator, expr.span)); - } - } - } - - fn visit_binary_expression(&mut self, expr: &BinaryExpression<'a>) { - match expr.operator { - BinaryOperator::Addition | - BinaryOperator::Subtraction | - BinaryOperator::Multiplication | - BinaryOperator::Division | - BinaryOperator::Equality | - BinaryOperator::Inequality | - BinaryOperator::LessThan | - BinaryOperator::GreaterThan | - BinaryOperator::LessEqualThan | - BinaryOperator::GreaterEqualThan => { - walk::walk_binary_expression(self, expr); - } - _ => { - self.errors.push(format!("Unsupported binary operator {:?} at {:?}", expr.operator, expr.span)); - } - } - } - - fn visit_logical_expression(&mut self, expr: &LogicalExpression<'a>) { - match expr.operator { - LogicalOperator::And | - LogicalOperator::Or => { - walk::walk_logical_expression(self, expr); - } - _ => { - self.errors.push(format!("Unsupported logical operator {:?} at {:?}", expr.operator, expr.span)); - } - } - } - - /// Validates that only supported statements are used. - fn visit_statement(&mut self, stmt: &Statement<'a>) { - match stmt { - Statement::VariableDeclaration(_) | - Statement::ExpressionStatement(_) | - Statement::IfStatement(_) | - Statement::BlockStatement(_) | - Statement::ExportNamedDeclaration(_) | - Statement::ImportDeclaration(_) | - Statement::FunctionDeclaration(_) | - Statement::ReturnStatement(_) => { - // These are the only statements the PVM handles currently. - walk::walk_statement(self, stmt); - } - _ => { - self.errors.push(format!("Unsupported statement type at {:?}. Note: Prometeu does not support while/for loops or classes yet.", stmt.span())); - } - } - } -} diff --git a/crates/prometeu-compiler/tests/golden/lua/complex.lua b/crates/prometeu-compiler/tests/golden/lua/complex.lua deleted file mode 100644 index 54af72e3..00000000 --- a/crates/prometeu-compiler/tests/golden/lua/complex.lua +++ /dev/null @@ -1,13 +0,0 @@ -local counter = 0 - -function frame() - while counter < 100 do - counter = counter + 1 - if counter == 50 then - log.write("Halfway there") - end - end - - local x = counter - gfx.fillRect(0, 0, x, x, color.indigo) -end diff --git a/crates/prometeu-compiler/tests/golden/lua/hello.lua b/crates/prometeu-compiler/tests/golden/lua/hello.lua deleted file mode 100644 index e64b9407..00000000 --- a/crates/prometeu-compiler/tests/golden/lua/hello.lua +++ /dev/null @@ -1,11 +0,0 @@ -function frame() - local x = 10 - local y = 20 - local result = x + y - - if result > 25 then - gfx.clear(color.green) - else - gfx.clear(color.red) - end -end diff --git a/crates/prometeu-compiler/tests/golden/lua/prometeu.json b/crates/prometeu-compiler/tests/golden/lua/prometeu.json deleted file mode 100644 index 756c3d33..00000000 --- a/crates/prometeu-compiler/tests/golden/lua/prometeu.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "script_fe": "lua", - "entry": "hello.lua", - "out": "program.pbc", - "emit_disasm": true, - "emit_symbols": true -} diff --git a/crates/prometeu-compiler/tests/golden/ts/main.ts b/crates/prometeu-compiler/tests/golden/ts/main.ts deleted file mode 100644 index 382604a5..00000000 --- a/crates/prometeu-compiler/tests/golden/ts/main.ts +++ /dev/null @@ -1,11 +0,0 @@ -export function frame(): void { - const x = 10; - const y = 20; - const result = x + y; - - if (result > 25) { - gfx.clear(color.green); - } else { - gfx.clear(color.red); - } -} diff --git a/crates/prometeu-compiler/tests/golden/ts/prometeu.json b/crates/prometeu-compiler/tests/golden/ts/prometeu.json deleted file mode 100644 index 1334869e..00000000 --- a/crates/prometeu-compiler/tests/golden/ts/prometeu.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "script_fe": "ts", - "entry": "main.ts", - "out": "program.pbc", - "emit_disasm": true, - "emit_symbols": true -} diff --git a/crates/prometeu-compiler/tests/lua_tests.rs b/crates/prometeu-compiler/tests/lua_tests.rs deleted file mode 100644 index 3950c556..00000000 --- a/crates/prometeu-compiler/tests/lua_tests.rs +++ /dev/null @@ -1,43 +0,0 @@ -use prometeu_compiler::compiler; -use prometeu_compiler::compiler::ProjectConfig; -use std::path::Path; - -#[test] -fn test_compile_lua_hello() { - let project_dir = Path::new("tests/golden/lua"); - let cfg = ProjectConfig { - script_fe: "lua".to_string(), - entry: "hello.lua".to_string(), - out: "out.pbc".to_string(), - emit_disasm: false, - emit_symbols: false, - }; - let result = compiler::compile(&cfg, project_dir); - - match result { - Ok(unit) => { - assert!(!unit.rom.is_empty()); - } - Err(e) => panic!("Compilation failed: {}", e), - } -} - -#[test] -fn test_compile_ts_regression() { - let cfg = ProjectConfig { - script_fe: "ts".to_string(), - entry: "main.ts".to_string(), - out: "out.pbc".to_string(), - emit_disasm: false, - emit_symbols: false, - }; - let project_dir = Path::new("tests/golden/ts"); - let result = compiler::compile(&cfg, project_dir); - - match result { - Ok(unit) => { - assert!(!unit.rom.is_empty()); - } - Err(e) => panic!("TS Compilation failed: {}", e), - } -}