prometeuc improvements
This commit is contained in:
parent
73353d864d
commit
0496afc192
17
build/program.disasm.txt
Normal file
17
build/program.disasm.txt
Normal file
@ -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
|
||||
BIN
build/program.pbc
Normal file
BIN
build/program.pbc
Normal file
Binary file not shown.
74
build/symbols.json
Normal file
74
build/symbols.json
Normal file
@ -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
|
||||
}
|
||||
]
|
||||
@ -18,6 +18,18 @@ pub enum Asm {
|
||||
Label(String),
|
||||
}
|
||||
|
||||
pub fn update_pc_by_operand(initial_pc: u32, operands: &Vec<Operand>) -> 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<Vec<u8>, String> {
|
||||
let mut labels = HashMap::new();
|
||||
let mut current_pc = 0u32;
|
||||
@ -30,13 +42,7 @@ pub fn assemble(instructions: &[Asm]) -> Result<Vec<u8>, 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
22
crates/prometeuc/src/codegen/ast_util.rs
Normal file
22
crates/prometeuc/src/codegen/ast_util.rs
Normal file
@ -0,0 +1,22 @@
|
||||
use oxc_ast::ast::*;
|
||||
use anyhow::{Result, anyhow};
|
||||
|
||||
pub fn get_callee_name(expr: &Expression) -> Result<String> {
|
||||
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<String> {
|
||||
if let Expression::Identifier(ident) = expr {
|
||||
Ok(ident.name.to_string())
|
||||
} else {
|
||||
Err(anyhow!("Unsupported member object expression"))
|
||||
}
|
||||
}
|
||||
@ -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<String> {
|
||||
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<String> {
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,3 +1,5 @@
|
||||
pub mod codegen;
|
||||
pub mod validator;
|
||||
pub mod ast_util;
|
||||
|
||||
pub use codegen::Codegen;
|
||||
140
crates/prometeuc/src/codegen/validator.rs
Normal file
140
crates/prometeuc/src/codegen/validator.rs
Normal file
@ -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<String>,
|
||||
}
|
||||
|
||||
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));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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,42 +17,28 @@ pub struct Symbol {
|
||||
pub col: usize,
|
||||
}
|
||||
|
||||
pub fn compile(entry: &Path, out: &Path, emit_disasm: bool, emit_symbols: bool) -> Result<()> {
|
||||
let source_text = fs::read_to_string(entry)
|
||||
.with_context(|| format!("Failed to read entry file: {:?}", entry))?;
|
||||
pub struct CompilationUnit {
|
||||
pub rom: Vec<u8>,
|
||||
pub symbols: Vec<Symbol>,
|
||||
}
|
||||
|
||||
let allocator = Allocator::default();
|
||||
let source_type = SourceType::from_path(entry).unwrap_or_default();
|
||||
|
||||
let mut codegen = Codegen::new(entry.to_string_lossy().to_string(), source_text.clone());
|
||||
let rom = {
|
||||
let ret = Parser::new(&allocator, &source_text, source_type).parse();
|
||||
|
||||
if !ret.errors.is_empty() {
|
||||
for error in ret.errors {
|
||||
eprintln!("{:?}", error);
|
||||
}
|
||||
return Err(anyhow::anyhow!("Failed to parse module"));
|
||||
}
|
||||
|
||||
codegen.compile_program(&ret.program)?
|
||||
};
|
||||
|
||||
fs::write(out, &rom).with_context(|| format!("Failed to write PBC to {:?}", out))?;
|
||||
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(&codegen.symbols)?;
|
||||
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(&rom).map_err(|e| anyhow::anyhow!("Disassembly failed: {}", e))?;
|
||||
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 = codegen.symbols.iter().find(|s| s.pc == instr.pc);
|
||||
let symbol = self.symbols.iter().find(|s| s.pc == instr.pc);
|
||||
let comment = if let Some(s) = symbol {
|
||||
format!(" ; {}:{}", s.file, s.line)
|
||||
} else {
|
||||
@ -69,4 +56,34 @@ pub fn compile(entry: &Path, out: &Path, emit_disasm: bool, emit_symbols: bool)
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub fn compile(entry: &Path) -> Result<CompilationUnit> {
|
||||
let source_text = fs::read_to_string(entry)
|
||||
.with_context(|| format!("Failed to read entry file: {:?}", entry))?;
|
||||
|
||||
let allocator = Allocator::default();
|
||||
let source_type = SourceType::from_path(entry).unwrap_or_default();
|
||||
|
||||
let mut codegen = Codegen::new(entry.to_string_lossy().to_string(), source_text.clone());
|
||||
let rom = {
|
||||
let ret = Parser::new(&allocator, &source_text, source_type).parse();
|
||||
|
||||
if !ret.errors.is_empty() {
|
||||
for error in ret.errors {
|
||||
eprintln!("{:?}", error);
|
||||
}
|
||||
return Err(anyhow::anyhow!("Failed to parse module"));
|
||||
}
|
||||
|
||||
Validator::validate(&ret.program)?;
|
||||
|
||||
codegen.compile_program(&ret.program)?
|
||||
};
|
||||
|
||||
Ok(CompilationUnit {
|
||||
rom,
|
||||
symbols: codegen.symbols,
|
||||
})
|
||||
}
|
||||
|
||||
@ -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)?;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user