removed cart lua
This commit is contained in:
parent
06c8e95cc9
commit
5b9524f401
68
crates/prometeu-compiler/src/frontends/lua/input_map.rs
Normal file
68
crates/prometeu-compiler/src/frontends/lua/input_map.rs
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
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<u32> {
|
||||||
|
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)),
|
||||||
|
}
|
||||||
|
}
|
||||||
164
crates/prometeu-compiler/src/frontends/lua/mod.rs
Normal file
164
crates/prometeu-compiler/src/frontends/lua/mod.rs
Normal file
@ -0,0 +1,164 @@
|
|||||||
|
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<PathBuf> {
|
||||||
|
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<ir::Module, DiagnosticBundle> {
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
26
crates/prometeu-compiler/src/frontends/lua/parse.rs
Normal file
26
crates/prometeu-compiler/src/frontends/lua/parse.rs
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
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<full_moon::ast::Ast, DiagnosticBundle> {
|
||||||
|
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
|
||||||
|
})
|
||||||
|
}
|
||||||
25
crates/prometeu-compiler/src/frontends/lua/resolve.rs
Normal file
25
crates/prometeu-compiler/src/frontends/lua/resolve.rs
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
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<SymbolTable, DiagnosticBundle> {
|
||||||
|
// Por enquanto, retorna uma tabela vazia.
|
||||||
|
// A resolução real pode ser feita durante o ToIR ou aqui se anotarmos a AST.
|
||||||
|
Ok(SymbolTable {})
|
||||||
|
}
|
||||||
56
crates/prometeu-compiler/src/frontends/lua/syscall_map.rs
Normal file
56
crates/prometeu-compiler/src/frontends/lua/syscall_map.rs
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
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<u32> {
|
||||||
|
// 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<u32> {
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
831
crates/prometeu-compiler/src/frontends/lua/to_ir.rs
Normal file
831
crates/prometeu-compiler/src/frontends/lua/to_ir.rs
Normal file
@ -0,0 +1,831 @@
|
|||||||
|
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<String, u32>,
|
||||||
|
pub next_slot: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub enum Symbol {
|
||||||
|
Local(u32),
|
||||||
|
Global(u32),
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct ToIR {
|
||||||
|
instructions: Vec<Instruction>,
|
||||||
|
label_counter: u32,
|
||||||
|
locals: Vec<HashMap<String, Symbol>>,
|
||||||
|
next_local_slot: u32,
|
||||||
|
shared_globals: Arc<Mutex<SharedGlobalTable>>,
|
||||||
|
file_id: usize,
|
||||||
|
module_prefix: Option<String>,
|
||||||
|
is_function: bool,
|
||||||
|
module_exit_label: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ToIR {
|
||||||
|
pub fn new(file_id: usize, shared_globals: Arc<Mutex<SharedGlobalTable>>, module_prefix: Option<String>, 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<Span> {
|
||||||
|
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<Module, DiagnosticBundle> {
|
||||||
|
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<Function, DiagnosticBundle> {
|
||||||
|
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<full_moon::ast::Parameter>) -> Result<Function, DiagnosticBundle> {
|
||||||
|
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::<i64>() {
|
||||||
|
self.emit_instr(InstrKind::PushInt(i), span);
|
||||||
|
} else if let Ok(f) = s.parse::<f64>() {
|
||||||
|
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<Span>) -> 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<Span>) -> 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<Symbol> {
|
||||||
|
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<Span>) {
|
||||||
|
self.instructions.push(Instruction::new(kind, span));
|
||||||
|
}
|
||||||
|
}
|
||||||
184
crates/prometeu-compiler/src/frontends/lua/validate.rs
Normal file
184
crates/prometeu-compiler/src/frontends/lua/validate.rs
Normal file
@ -0,0 +1,184 @@
|
|||||||
|
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<Span> {
|
||||||
|
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(_) => {
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
13
crates/prometeu-compiler/tests/golden/lua/complex.lua
Normal file
13
crates/prometeu-compiler/tests/golden/lua/complex.lua
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
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
|
||||||
11
crates/prometeu-compiler/tests/golden/lua/hello.lua
Normal file
11
crates/prometeu-compiler/tests/golden/lua/hello.lua
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
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
|
||||||
7
crates/prometeu-compiler/tests/golden/lua/prometeu.json
Normal file
7
crates/prometeu-compiler/tests/golden/lua/prometeu.json
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
"script_fe": "lua",
|
||||||
|
"entry": "hello.lua",
|
||||||
|
"out": "program.pbc",
|
||||||
|
"emit_disasm": true,
|
||||||
|
"emit_symbols": true
|
||||||
|
}
|
||||||
11
crates/prometeu-compiler/tests/golden/ts/main.ts
Normal file
11
crates/prometeu-compiler/tests/golden/ts/main.ts
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
7
crates/prometeu-compiler/tests/golden/ts/prometeu.json
Normal file
7
crates/prometeu-compiler/tests/golden/ts/prometeu.json
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
"script_fe": "ts",
|
||||||
|
"entry": "main.ts",
|
||||||
|
"out": "program.pbc",
|
||||||
|
"emit_disasm": true,
|
||||||
|
"emit_symbols": true
|
||||||
|
}
|
||||||
43
crates/prometeu-compiler/tests/lua_tests.rs
Normal file
43
crates/prometeu-compiler/tests/lua_tests.rs
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
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),
|
||||||
|
}
|
||||||
|
}
|
||||||
Binary file not shown.
@ -1,41 +0,0 @@
|
|||||||
{
|
|
||||||
"magic": "PMTU",
|
|
||||||
"cartridge_version": 1,
|
|
||||||
"app_id": 1,
|
|
||||||
"title": "Color Square",
|
|
||||||
"app_version": "0.1.0",
|
|
||||||
"app_mode": "Game",
|
|
||||||
"entrypoint": "0",
|
|
||||||
"asset_table": [
|
|
||||||
{
|
|
||||||
"asset_id": 0,
|
|
||||||
"asset_name": "bgm_music",
|
|
||||||
"bank_type": "SOUNDS",
|
|
||||||
"offset": 0,
|
|
||||||
"size": 88200,
|
|
||||||
"decoded_size": 88200,
|
|
||||||
"codec": "RAW",
|
|
||||||
"metadata": {
|
|
||||||
"sample_rate": 44100
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"asset_id": 1,
|
|
||||||
"asset_name": "mouse_cursor",
|
|
||||||
"bank_type": "TILES",
|
|
||||||
"offset": 88200,
|
|
||||||
"size": 2304,
|
|
||||||
"decoded_size": 2304,
|
|
||||||
"codec": "RAW",
|
|
||||||
"metadata": {
|
|
||||||
"tile_size": 16,
|
|
||||||
"width": 16,
|
|
||||||
"height": 16
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"preload": [
|
|
||||||
{ "asset_name": "bgm_music", "slot": 0 },
|
|
||||||
{ "asset_name": "mouse_cursor", "slot": 1 }
|
|
||||||
]
|
|
||||||
}
|
|
||||||
@ -1 +0,0 @@
|
|||||||
../../dist-staging/stable/prometeu-aarch64-apple-darwin/
|
|
||||||
@ -1,7 +0,0 @@
|
|||||||
#!/usr/bin/env bash
|
|
||||||
set -euo pipefail
|
|
||||||
|
|
||||||
prometeu build . --lang lua
|
|
||||||
prometeu run cartridge
|
|
||||||
|
|
||||||
echo "Lua project scaffold ok. (Build step depends on Lua frontend in prometeu-compiler)"
|
|
||||||
@ -1,16 +0,0 @@
|
|||||||
local my_gfx = require("my_gfx")
|
|
||||||
local my_input = require("my_input")
|
|
||||||
local my_fs = require("my_fs")
|
|
||||||
|
|
||||||
function frame()
|
|
||||||
my_gfx.do_init_gfx()
|
|
||||||
my_input.do_pad()
|
|
||||||
my_input.do_touch()
|
|
||||||
my_fs.do_fs()
|
|
||||||
my_gfx.print_orange()
|
|
||||||
|
|
||||||
do
|
|
||||||
local x = 10
|
|
||||||
gfx.drawText(120, 100, "1. value of " .. x, color.white)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@ -1,15 +0,0 @@
|
|||||||
local M = {}
|
|
||||||
|
|
||||||
function M.do_fs()
|
|
||||||
local h = fs.open("test.txt")
|
|
||||||
if h >= 0 then
|
|
||||||
fs.write(h, "Hello Prometeu!")
|
|
||||||
local content = fs.read(h)
|
|
||||||
if content and content ~= "" then
|
|
||||||
log.writeTag(2, 101, content)
|
|
||||||
end
|
|
||||||
fs.close(h)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
return M
|
|
||||||
@ -1,17 +0,0 @@
|
|||||||
local M = {}
|
|
||||||
|
|
||||||
function M.do_init_gfx()
|
|
||||||
gfx.clear(color.indigo)
|
|
||||||
gfx.fillRect(10, 10, 50, 50, color.red)
|
|
||||||
gfx.drawLine(0, 0, 128, 128, color.white)
|
|
||||||
gfx.drawCircle(64, 64, 20, color.blue)
|
|
||||||
gfx.drawDisc(100, 100, 10, color.green, color.yellow)
|
|
||||||
gfx.drawSquare(20, 100, 30, 30, color.cyan, color.color_key)
|
|
||||||
end
|
|
||||||
|
|
||||||
function M.print_orange()
|
|
||||||
local c = color.rgb(255, 128, 0)
|
|
||||||
gfx.fillRect(0, 0, 5, 5, c)
|
|
||||||
end
|
|
||||||
|
|
||||||
return M
|
|
||||||
@ -1,21 +0,0 @@
|
|||||||
local M = {}
|
|
||||||
|
|
||||||
function M.do_pad()
|
|
||||||
if pad.up.down then
|
|
||||||
log.write(2, "Up is down")
|
|
||||||
end
|
|
||||||
|
|
||||||
if pad.a.pressed then
|
|
||||||
audio.play("bgm_music", 0, 0, 128, 127, 1.0, 1)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
function M.do_touch()
|
|
||||||
gfx.setSprite("mouse_cursor", 0, touch.x, touch.y, 0, 0, true, false, false, 4)
|
|
||||||
|
|
||||||
if touch.button.down then
|
|
||||||
gfx.drawCircle(touch.x, touch.y, 10, color.white)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
return M
|
|
||||||
7
test-cartridges/color-square-ts/prometeu.json
Normal file
7
test-cartridges/color-square-ts/prometeu.json
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
"script_fe": "ts",
|
||||||
|
"entry": "src/main.ts",
|
||||||
|
"out": "build/program.pbc",
|
||||||
|
"emit_disasm": true,
|
||||||
|
"emit_symbols": true
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user