From 0496afc1920698c5f1f5e9aa4bd56e88c8b4c117 Mon Sep 17 00:00:00 2001 From: Nilton Constantino Date: Tue, 20 Jan 2026 12:52:23 +0000 Subject: [PATCH] prometeuc improvements --- build/program.disasm.txt | 17 +++ build/program.pbc | Bin 0 -> 82 bytes build/symbols.json | 74 ++++++++++++ crates/prometeu-bytecode/src/asm.rs | 20 ++-- crates/prometeuc/src/codegen/ast_util.rs | 22 ++++ crates/prometeuc/src/codegen/codegen.rs | 34 +----- crates/prometeuc/src/codegen/mod.rs | 2 + crates/prometeuc/src/codegen/validator.rs | 140 ++++++++++++++++++++++ crates/prometeuc/src/compiler.rs | 83 ++++++++----- crates/prometeuc/src/main.rs | 3 +- 10 files changed, 325 insertions(+), 70 deletions(-) create mode 100644 build/program.disasm.txt create mode 100644 build/program.pbc create mode 100644 build/symbols.json create mode 100644 crates/prometeuc/src/codegen/ast_util.rs create mode 100644 crates/prometeuc/src/codegen/validator.rs diff --git a/build/program.disasm.txt b/build/program.disasm.txt new file mode 100644 index 00000000..99dc1644 --- /dev/null +++ b/build/program.disasm.txt @@ -0,0 +1,17 @@ +00000000 Call U32(18) U32(0) +0000000A FrameSync +0000000C Jmp U32(0) +00000012 PushI32 U32(1) ; test_supported.ts:2 +00000018 SetLocal U32(0) ; test_supported.ts:2 +0000001E GetLocal U32(0) ; test_supported.ts:3 +00000024 PushI32 U32(10) ; test_supported.ts:3 +0000002A Lt ; test_supported.ts:3 +0000002C JmpIfFalse U32(80) ; test_supported.ts:3 +00000032 GetLocal U32(0) ; test_supported.ts:4 +00000038 PushI32 U32(1) ; test_supported.ts:4 +0000003E Add ; test_supported.ts:4 +00000040 Dup ; test_supported.ts:4 +00000042 SetLocal U32(0) ; test_supported.ts:4 +00000048 Pop ; test_supported.ts:4 +0000004A Jmp U32(80) +00000050 Ret diff --git a/build/program.pbc b/build/program.pbc new file mode 100644 index 0000000000000000000000000000000000000000..49db59a3734e6a160f1d665299b13ae9f3e0bf27 GIT binary patch literal 82 zcmWGw5MqFU1_mZDL!5yTNIQesPEZjpAZ^6J%n$&iVPYUv3P9Zum4ZOsAlX0$0F~?m As{jB1 literal 0 HcmV?d00001 diff --git a/build/symbols.json b/build/symbols.json new file mode 100644 index 00000000..9d066842 --- /dev/null +++ b/build/symbols.json @@ -0,0 +1,74 @@ +[ + { + "pc": 18, + "file": "test_supported.ts", + "line": 2, + "col": 13 + }, + { + "pc": 24, + "file": "test_supported.ts", + "line": 2, + "col": 9 + }, + { + "pc": 30, + "file": "test_supported.ts", + "line": 3, + "col": 9 + }, + { + "pc": 36, + "file": "test_supported.ts", + "line": 3, + "col": 13 + }, + { + "pc": 42, + "file": "test_supported.ts", + "line": 3, + "col": 9 + }, + { + "pc": 44, + "file": "test_supported.ts", + "line": 3, + "col": 5 + }, + { + "pc": 50, + "file": "test_supported.ts", + "line": 4, + "col": 13 + }, + { + "pc": 56, + "file": "test_supported.ts", + "line": 4, + "col": 17 + }, + { + "pc": 62, + "file": "test_supported.ts", + "line": 4, + "col": 13 + }, + { + "pc": 64, + "file": "test_supported.ts", + "line": 4, + "col": 9 + }, + { + "pc": 66, + "file": "test_supported.ts", + "line": 4, + "col": 9 + }, + { + "pc": 72, + "file": "test_supported.ts", + "line": 4, + "col": 9 + } +] \ No newline at end of file diff --git a/crates/prometeu-bytecode/src/asm.rs b/crates/prometeu-bytecode/src/asm.rs index 8079997f..0f7d2b45 100644 --- a/crates/prometeu-bytecode/src/asm.rs +++ b/crates/prometeu-bytecode/src/asm.rs @@ -18,6 +18,18 @@ pub enum Asm { Label(String), } +pub fn update_pc_by_operand(initial_pc: u32, operands: &Vec) -> u32 { + let mut pcp: u32 = initial_pc; + for operand in operands { + match operand { + Operand::U32(_) | Operand::I32(_) | Operand::Label(_) => pcp += 4, + Operand::I64(_) | Operand::F64(_) => pcp += 8, + Operand::Bool(_) => pcp += 1, + } + } + pcp +} + pub fn assemble(instructions: &[Asm]) -> Result, String> { let mut labels = HashMap::new(); let mut current_pc = 0u32; @@ -30,13 +42,7 @@ pub fn assemble(instructions: &[Asm]) -> Result, String> { } Asm::Op(_opcode, operands) => { current_pc += 2; // OpCode is u16 (2 bytes) - for operand in operands { - match operand { - Operand::U32(_) | Operand::I32(_) | Operand::Label(_) => current_pc += 4, - Operand::I64(_) | Operand::F64(_) => current_pc += 8, - Operand::Bool(_) => current_pc += 1, - } - } + current_pc = update_pc_by_operand(current_pc, operands); } } } diff --git a/crates/prometeuc/src/codegen/ast_util.rs b/crates/prometeuc/src/codegen/ast_util.rs new file mode 100644 index 00000000..0ef76ad3 --- /dev/null +++ b/crates/prometeuc/src/codegen/ast_util.rs @@ -0,0 +1,22 @@ +use oxc_ast::ast::*; +use anyhow::{Result, anyhow}; + +pub fn get_callee_name(expr: &Expression) -> Result { + match expr { + Expression::Identifier(ident) => Ok(ident.name.to_string()), + Expression::StaticMemberExpression(member) => { + let obj = get_callee_name_from_member_obj(&member.object)?; + let prop = member.property.name.to_string(); + Ok(format!("{}.{}", obj, prop)) + } + _ => Err(anyhow!("Unsupported callee expression")), + } +} + +pub fn get_callee_name_from_member_obj(expr: &Expression) -> Result { + if let Expression::Identifier(ident) = expr { + Ok(ident.name.to_string()) + } else { + Err(anyhow!("Unsupported member object expression")) + } +} diff --git a/crates/prometeuc/src/codegen/codegen.rs b/crates/prometeuc/src/codegen/codegen.rs index 1dddc283..3c5062af 100644 --- a/crates/prometeuc/src/codegen/codegen.rs +++ b/crates/prometeuc/src/codegen/codegen.rs @@ -5,7 +5,9 @@ use prometeu_bytecode::opcode::OpCode; use prometeu_bytecode::asm::{Asm, Operand, assemble}; use crate::compiler::Symbol; use crate::syscall_map; +use crate::codegen::ast_util; use std::collections::HashMap; +use prometeu_bytecode::asm; pub struct Codegen { file_name: String, @@ -203,7 +205,7 @@ impl Codegen { self.emit_op(op, vec![], unary.span); } Expression::CallExpression(call) => { - let name = self.get_callee_name(&call.callee)?; + let name = ast_util::get_callee_name(&call.callee)?; if let Some(syscall_id) = syscall_map::map_syscall(&name) { if name == "input.btnA" { self.emit_op(OpCode::PushI32, vec![Operand::I32(syscall_map::BTN_A as i32)], call.span); @@ -222,7 +224,7 @@ impl Codegen { } } Expression::StaticMemberExpression(member) => { - let obj = self.get_callee_name_from_member_obj(&member.object)?; + let obj = ast_util::get_callee_name_from_member_obj(&member.object)?; let prop = member.property.name.to_string(); let full_name = format!("{}.{}", obj, prop); // If it's used as a value (GetGlobal/GetLocal?), but for now we only support it in calls @@ -233,26 +235,6 @@ impl Codegen { Ok(()) } - fn get_callee_name(&self, expr: &Expression) -> Result { - match expr { - Expression::Identifier(ident) => Ok(ident.name.to_string()), - Expression::StaticMemberExpression(member) => { - let obj = self.get_callee_name_from_member_obj(&member.object)?; - let prop = member.property.name.to_string(); - Ok(format!("{}.{}", obj, prop)) - } - _ => Err(anyhow!("Unsupported callee expression at {:?}", expr.span())), - } - } - - fn get_callee_name_from_member_obj(&self, expr: &Expression) -> Result { - if let Expression::Identifier(ident) = expr { - Ok(ident.name.to_string()) - } else { - Err(anyhow!("Unsupported member object expression")) - } - } - fn new_label(&mut self, prefix: &str) -> String { let label = format!("{}_{}", prefix, self.label_count); self.label_count += 1; @@ -310,13 +292,7 @@ impl Codegen { } current_pc += 2; - for operand in operands { - match operand { - Operand::U32(_) | Operand::I32(_) | Operand::Label(_) => current_pc += 4, - Operand::I64(_) | Operand::F64(_) => current_pc += 8, - Operand::Bool(_) => current_pc += 1, - } - } + current_pc = asm::update_pc_by_operand(current_pc, operands); } } } diff --git a/crates/prometeuc/src/codegen/mod.rs b/crates/prometeuc/src/codegen/mod.rs index 484803d5..3d883e34 100644 --- a/crates/prometeuc/src/codegen/mod.rs +++ b/crates/prometeuc/src/codegen/mod.rs @@ -1,3 +1,5 @@ pub mod codegen; +pub mod validator; +pub mod ast_util; pub use codegen::Codegen; \ No newline at end of file diff --git a/crates/prometeuc/src/codegen/validator.rs b/crates/prometeuc/src/codegen/validator.rs new file mode 100644 index 00000000..6af5b2ff --- /dev/null +++ b/crates/prometeuc/src/codegen/validator.rs @@ -0,0 +1,140 @@ +use oxc_ast::ast::*; +use oxc_ast::visit::walk; +use oxc_ast::Visit; +use oxc_span::GetSpan; +use anyhow::{Result, anyhow}; +use crate::syscall_map; +use crate::codegen::ast_util; + +pub struct Validator { + errors: Vec, +} + +impl Validator { + pub fn validate(program: &Program) -> Result<()> { + let mut validator = Self { errors: Vec::new() }; + + // 1. Check for exported tick function + let mut tick_fn_found = false; + for item in &program.body { + if let Statement::ExportNamedDeclaration(decl) = item { + if let Some(Declaration::FunctionDeclaration(f)) = &decl.declaration { + if let Some(ident) = &f.id { + if ident.name == "tick" { + tick_fn_found = true; + break; + } + } + } + } + } + + if !tick_fn_found { + validator.errors.push("export function tick() not found".to_string()); + } + + // 2. Recursive validation of the AST + validator.visit_program(program); + + if validator.errors.is_empty() { + Ok(()) + } else { + Err(anyhow!("Validation errors:\n{}", validator.errors.join("\n"))) + } + } +} + +impl<'a> Visit<'a> for Validator { + fn visit_statement(&mut self, stmt: &Statement<'a>) { + match stmt { + Statement::VariableDeclaration(_) | + Statement::ExpressionStatement(_) | + Statement::IfStatement(_) | + Statement::BlockStatement(_) | + Statement::ExportNamedDeclaration(_) | + Statement::FunctionDeclaration(_) => { + // Supported + walk::walk_statement(self, stmt); + } + _ => { + self.errors.push(format!("Unsupported statement type at {:?}", stmt.span())); + } + } + } + + fn visit_expression(&mut self, expr: &Expression<'a>) { + match expr { + Expression::NumericLiteral(_) | + Expression::BooleanLiteral(_) | + Expression::Identifier(_) | + Expression::AssignmentExpression(_) | + Expression::BinaryExpression(_) | + Expression::LogicalExpression(_) | + Expression::UnaryExpression(_) | + Expression::CallExpression(_) | + Expression::StaticMemberExpression(_) => { + // Base types supported, detailed checks in specific visit methods if needed + walk::walk_expression(self, expr); + } + _ => { + self.errors.push(format!("Unsupported expression type at {:?}", expr.span())); + } + } + } + + fn visit_unary_expression(&mut self, expr: &UnaryExpression<'a>) { + match expr.operator { + UnaryOperator::UnaryNegation | + UnaryOperator::UnaryPlus | + UnaryOperator::LogicalNot => { + walk::walk_unary_expression(self, expr); + } + _ => { + self.errors.push(format!("Unsupported unary operator {:?} at {:?}", expr.operator, expr.span)); + } + } + } + + fn visit_binary_expression(&mut self, expr: &BinaryExpression<'a>) { + match expr.operator { + BinaryOperator::Addition | + BinaryOperator::Subtraction | + BinaryOperator::Multiplication | + BinaryOperator::Division | + BinaryOperator::Equality | + BinaryOperator::Inequality | + BinaryOperator::LessThan | + BinaryOperator::GreaterThan | + BinaryOperator::LessEqualThan | + BinaryOperator::GreaterEqualThan => { + walk::walk_binary_expression(self, expr); + } + _ => { + self.errors.push(format!("Unsupported binary operator {:?} at {:?}", expr.operator, expr.span)); + } + } + } + + fn visit_call_expression(&mut self, expr: &CallExpression<'a>) { + if let Ok(name) = ast_util::get_callee_name(&expr.callee) { + if syscall_map::map_syscall(&name).is_none() { + self.errors.push(format!("Unsupported function call: {} at {:?}", name, expr.span)); + } + } else { + self.errors.push(format!("Unsupported callee expression at {:?}", expr.callee.span())); + } + walk::walk_call_expression(self, expr); + } + + fn visit_logical_expression(&mut self, expr: &LogicalExpression<'a>) { + match expr.operator { + LogicalOperator::And | + LogicalOperator::Or => { + walk::walk_logical_expression(self, expr); + } + _ => { + self.errors.push(format!("Unsupported logical operator {:?} at {:?}", expr.operator, expr.span)); + } + } + } +} diff --git a/crates/prometeuc/src/compiler.rs b/crates/prometeuc/src/compiler.rs index cda88df4..7a31d65d 100644 --- a/crates/prometeuc/src/compiler.rs +++ b/crates/prometeuc/src/compiler.rs @@ -4,6 +4,7 @@ use oxc_allocator::Allocator; use oxc_parser::Parser; use oxc_span::SourceType; use crate::codegen::Codegen; +use crate::codegen::validator::Validator; use std::fs; use prometeu_bytecode::disasm::disasm; use serde::Serialize; @@ -16,7 +17,49 @@ pub struct Symbol { pub col: usize, } -pub fn compile(entry: &Path, out: &Path, emit_disasm: bool, emit_symbols: bool) -> Result<()> { +pub struct CompilationUnit { + pub rom: Vec, + pub symbols: Vec, +} + +impl CompilationUnit { + pub fn export(&self, out: &Path, emit_disasm: bool, emit_symbols: bool) -> Result<()> { + fs::write(out, &self.rom).with_context(|| format!("Failed to write PBC to {:?}", out))?; + + if emit_symbols { + let symbols_path = out.with_file_name("symbols.json"); + let symbols_json = serde_json::to_string_pretty(&self.symbols)?; + fs::write(&symbols_path, symbols_json)?; + } + + if emit_disasm { + let disasm_path = out.with_extension("disasm.txt"); + let instructions = disasm(&self.rom).map_err(|e| anyhow::anyhow!("Disassembly failed: {}", e))?; + + let mut disasm_text = String::new(); + for instr in instructions { + let symbol = self.symbols.iter().find(|s| s.pc == instr.pc); + let comment = if let Some(s) = symbol { + format!(" ; {}:{}", s.file, s.line) + } else { + "".to_string() + }; + + let operands_str = instr.operands.iter() + .map(|o| format!("{:?}", o)) + .collect::>() + .join(" "); + + disasm_text.push_str(&format!("{:08X} {:?} {}{}\n", instr.pc, instr.opcode, operands_str, comment)); + } + fs::write(disasm_path, disasm_text)?; + } + + Ok(()) + } +} + +pub fn compile(entry: &Path) -> Result { let source_text = fs::read_to_string(entry) .with_context(|| format!("Failed to read entry file: {:?}", entry))?; @@ -34,39 +77,13 @@ pub fn compile(entry: &Path, out: &Path, emit_disasm: bool, emit_symbols: bool) return Err(anyhow::anyhow!("Failed to parse module")); } + Validator::validate(&ret.program)?; + codegen.compile_program(&ret.program)? }; - fs::write(out, &rom).with_context(|| format!("Failed to write PBC to {:?}", out))?; - - if emit_symbols { - let symbols_path = out.with_file_name("symbols.json"); - let symbols_json = serde_json::to_string_pretty(&codegen.symbols)?; - fs::write(&symbols_path, symbols_json)?; - } - - if emit_disasm { - let disasm_path = out.with_extension("disasm.txt"); - let instructions = disasm(&rom).map_err(|e| anyhow::anyhow!("Disassembly failed: {}", e))?; - - let mut disasm_text = String::new(); - for instr in instructions { - let symbol = codegen.symbols.iter().find(|s| s.pc == instr.pc); - let comment = if let Some(s) = symbol { - format!(" ; {}:{}", s.file, s.line) - } else { - "".to_string() - }; - - let operands_str = instr.operands.iter() - .map(|o| format!("{:?}", o)) - .collect::>() - .join(" "); - - disasm_text.push_str(&format!("{:08X} {:?} {}{}\n", instr.pc, instr.opcode, operands_str, comment)); - } - fs::write(disasm_path, disasm_text)?; - } - - Ok(()) + Ok(CompilationUnit { + rom, + symbols: codegen.symbols, + }) } diff --git a/crates/prometeuc/src/main.rs b/crates/prometeuc/src/main.rs index b91734e5..f37017bc 100644 --- a/crates/prometeuc/src/main.rs +++ b/crates/prometeuc/src/main.rs @@ -29,7 +29,8 @@ fn main() -> Result<()> { println!("Entry: {:?}", entry); println!("Output: {:?}", out); - compiler::compile(&entry, &out, emit_disasm, emit_symbols)?; + let compilation_unit = compiler::compile(&entry)?; + compilation_unit.export(&out, emit_disasm, emit_symbols)?; } }