removed cart lua

This commit is contained in:
bQUARKz 2026-01-23 17:34:55 +00:00
parent 06c8e95cc9
commit 5b9524f401
Signed by: bquarkz
SSH Key Fingerprint: SHA256:Z7dgqoglWwoK6j6u4QC87OveEq74WOhFN+gitsxtkf8
22 changed files with 1453 additions and 118 deletions

View 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)),
}
}

View 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)
}
}

View 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
})
}

View 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 {})
}

View 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)
}

View 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(&param_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));
}
}

View 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(_) => {
}
_ => {}
}
}

View 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

View 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

View File

@ -0,0 +1,7 @@
{
"script_fe": "lua",
"entry": "hello.lua",
"out": "program.pbc",
"emit_disasm": true,
"emit_symbols": true
}

View 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);
}
}

View File

@ -0,0 +1,7 @@
{
"script_fe": "ts",
"entry": "main.ts",
"out": "program.pbc",
"emit_disasm": true,
"emit_symbols": true
}

View 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),
}
}

View File

@ -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 }
]
}

View File

@ -1 +0,0 @@
../../dist-staging/stable/prometeu-aarch64-apple-darwin/

View File

@ -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)"

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -0,0 +1,7 @@
{
"script_fe": "ts",
"entry": "src/main.ts",
"out": "build/program.pbc",
"emit_disasm": true,
"emit_symbols": true
}