pr 10
This commit is contained in:
parent
5bd26a2991
commit
ced7ff0607
@ -30,6 +30,11 @@ pub enum Node {
|
||||
WhenArm(WhenArmNode),
|
||||
TypeName(TypeNameNode),
|
||||
TypeApp(TypeAppNode),
|
||||
Alloc(AllocNode),
|
||||
Mutate(MutateNode),
|
||||
Borrow(BorrowNode),
|
||||
Peek(PeekNode),
|
||||
MemberAccess(MemberAccessNode),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||
@ -92,6 +97,7 @@ pub struct TypeDeclNode {
|
||||
pub vis: Option<String>,
|
||||
pub type_kind: String, // "struct" | "contract" | "error"
|
||||
pub name: String,
|
||||
pub is_host: bool,
|
||||
pub body: Box<Node>, // TypeBody
|
||||
}
|
||||
|
||||
@ -228,3 +234,40 @@ pub struct TypeAppNode {
|
||||
pub base: String,
|
||||
pub args: Vec<Node>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||
pub struct AllocNode {
|
||||
pub span: Span,
|
||||
pub ty: Box<Node>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||
pub struct MutateNode {
|
||||
pub span: Span,
|
||||
pub target: Box<Node>,
|
||||
pub binding: String,
|
||||
pub body: Box<Node>, // BlockNode
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||
pub struct BorrowNode {
|
||||
pub span: Span,
|
||||
pub target: Box<Node>,
|
||||
pub binding: String,
|
||||
pub body: Box<Node>, // BlockNode
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||
pub struct PeekNode {
|
||||
pub span: Span,
|
||||
pub target: Box<Node>,
|
||||
pub binding: String,
|
||||
pub body: Box<Node>, // BlockNode
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||
pub struct MemberAccessNode {
|
||||
pub span: Span,
|
||||
pub object: Box<Node>,
|
||||
pub member: String,
|
||||
}
|
||||
|
||||
70
crates/prometeu-compiler/src/frontends/pbs/contracts.rs
Normal file
70
crates/prometeu-compiler/src/frontends/pbs/contracts.rs
Normal file
@ -0,0 +1,70 @@
|
||||
use std::collections::HashMap;
|
||||
|
||||
pub struct ContractRegistry {
|
||||
mappings: HashMap<String, HashMap<String, u32>>,
|
||||
}
|
||||
|
||||
impl ContractRegistry {
|
||||
pub fn new() -> Self {
|
||||
let mut mappings = HashMap::new();
|
||||
|
||||
// GFX mappings
|
||||
let mut gfx = HashMap::new();
|
||||
gfx.insert("clear".to_string(), 0x1001);
|
||||
gfx.insert("fillRect".to_string(), 0x1002);
|
||||
gfx.insert("drawLine".to_string(), 0x1003);
|
||||
gfx.insert("drawCircle".to_string(), 0x1004);
|
||||
gfx.insert("drawDisc".to_string(), 0x1005);
|
||||
gfx.insert("drawSquare".to_string(), 0x1006);
|
||||
gfx.insert("setSprite".to_string(), 0x1007);
|
||||
gfx.insert("drawText".to_string(), 0x1008);
|
||||
mappings.insert("Gfx".to_string(), gfx);
|
||||
|
||||
// Input mappings
|
||||
let mut input = HashMap::new();
|
||||
input.insert("getPad".to_string(), 0x2001);
|
||||
input.insert("getPadPressed".to_string(), 0x2002);
|
||||
input.insert("getPadReleased".to_string(), 0x2003);
|
||||
input.insert("getPadHold".to_string(), 0x2004);
|
||||
mappings.insert("Input".to_string(), input);
|
||||
|
||||
// Touch mappings
|
||||
let mut touch = HashMap::new();
|
||||
touch.insert("getX".to_string(), 0x2101);
|
||||
touch.insert("getY".to_string(), 0x2102);
|
||||
touch.insert("isDown".to_string(), 0x2103);
|
||||
touch.insert("isPressed".to_string(), 0x2104);
|
||||
touch.insert("isReleased".to_string(), 0x2105);
|
||||
touch.insert("getHold".to_string(), 0x2106);
|
||||
mappings.insert("Touch".to_string(), touch);
|
||||
|
||||
// Audio mappings
|
||||
let mut audio = HashMap::new();
|
||||
audio.insert("playSample".to_string(), 0x3001);
|
||||
audio.insert("play".to_string(), 0x3002);
|
||||
mappings.insert("Audio".to_string(), audio);
|
||||
|
||||
// FS mappings
|
||||
let mut fs = HashMap::new();
|
||||
fs.insert("open".to_string(), 0x4001);
|
||||
fs.insert("read".to_string(), 0x4002);
|
||||
fs.insert("write".to_string(), 0x4003);
|
||||
fs.insert("close".to_string(), 0x4004);
|
||||
fs.insert("listDir".to_string(), 0x4005);
|
||||
fs.insert("exists".to_string(), 0x4006);
|
||||
fs.insert("delete".to_string(), 0x4007);
|
||||
mappings.insert("Fs".to_string(), fs);
|
||||
|
||||
// Log mappings
|
||||
let mut log = HashMap::new();
|
||||
log.insert("write".to_string(), 0x5001);
|
||||
log.insert("writeTag".to_string(), 0x5002);
|
||||
mappings.insert("Log".to_string(), log);
|
||||
|
||||
Self { mappings }
|
||||
}
|
||||
|
||||
pub fn resolve(&self, contract: &str, method: &str) -> Option<u32> {
|
||||
self.mappings.get(contract).and_then(|m| m.get(method)).copied()
|
||||
}
|
||||
}
|
||||
365
crates/prometeu-compiler/src/frontends/pbs/lowering.rs
Normal file
365
crates/prometeu-compiler/src/frontends/pbs/lowering.rs
Normal file
@ -0,0 +1,365 @@
|
||||
use crate::frontends::pbs::ast::*;
|
||||
use crate::frontends::pbs::symbols::*;
|
||||
use crate::frontends::pbs::contracts::ContractRegistry;
|
||||
use crate::ir_core;
|
||||
use crate::ir_core::ids::FunctionId;
|
||||
use crate::ir_core::{Block, Function, Instr, Module, Param, Program, Terminator, Type};
|
||||
use std::collections::HashMap;
|
||||
|
||||
pub struct Lowerer<'a> {
|
||||
_module_symbols: &'a ModuleSymbols,
|
||||
program: Program,
|
||||
current_function: Option<Function>,
|
||||
current_block: Option<Block>,
|
||||
next_block_id: u32,
|
||||
next_func_id: u32,
|
||||
local_vars: Vec<HashMap<String, u32>>,
|
||||
function_ids: HashMap<String, FunctionId>,
|
||||
contract_registry: ContractRegistry,
|
||||
}
|
||||
|
||||
impl<'a> Lowerer<'a> {
|
||||
pub fn new(module_symbols: &'a ModuleSymbols) -> Self {
|
||||
Self {
|
||||
_module_symbols: module_symbols,
|
||||
program: Program {
|
||||
const_pool: ir_core::ConstPool::new(),
|
||||
modules: Vec::new(),
|
||||
},
|
||||
current_function: None,
|
||||
current_block: None,
|
||||
next_block_id: 0,
|
||||
next_func_id: 1,
|
||||
local_vars: Vec::new(),
|
||||
function_ids: HashMap::new(),
|
||||
contract_registry: ContractRegistry::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn lower_file(mut self, file: &FileNode, module_name: &str) -> Program {
|
||||
// Pre-scan for function declarations to assign IDs
|
||||
for decl in &file.decls {
|
||||
if let Node::FnDecl(n) = decl {
|
||||
let id = FunctionId(self.next_func_id);
|
||||
self.next_func_id += 1;
|
||||
self.function_ids.insert(n.name.clone(), id);
|
||||
}
|
||||
}
|
||||
|
||||
let mut module = Module {
|
||||
name: module_name.to_string(),
|
||||
functions: Vec::new(),
|
||||
};
|
||||
|
||||
for decl in &file.decls {
|
||||
match decl {
|
||||
Node::FnDecl(fn_decl) => {
|
||||
let func = self.lower_function(fn_decl);
|
||||
module.functions.push(func);
|
||||
}
|
||||
_ => {} // Other declarations not handled for now
|
||||
}
|
||||
}
|
||||
|
||||
self.program.modules.push(module);
|
||||
self.program
|
||||
}
|
||||
|
||||
fn lower_function(&mut self, n: &FnDeclNode) -> Function {
|
||||
let func_id = *self.function_ids.get(&n.name).unwrap();
|
||||
self.next_block_id = 0;
|
||||
self.local_vars = vec![HashMap::new()];
|
||||
|
||||
let mut params = Vec::new();
|
||||
for (i, param) in n.params.iter().enumerate() {
|
||||
let ty = self.lower_type_node(¶m.ty);
|
||||
params.push(Param {
|
||||
name: param.name.clone(),
|
||||
ty: ty.clone(),
|
||||
});
|
||||
self.local_vars[0].insert(param.name.clone(), i as u32);
|
||||
}
|
||||
|
||||
let ret_ty = if let Some(ret) = &n.ret {
|
||||
self.lower_type_node(ret)
|
||||
} else {
|
||||
Type::Void
|
||||
};
|
||||
|
||||
let func = Function {
|
||||
id: func_id,
|
||||
name: n.name.clone(),
|
||||
params,
|
||||
return_type: ret_ty,
|
||||
blocks: Vec::new(),
|
||||
};
|
||||
|
||||
self.current_function = Some(func);
|
||||
self.start_block();
|
||||
self.lower_node(&n.body);
|
||||
|
||||
// Ensure every function ends with a return if not already terminated
|
||||
if let Some(mut block) = self.current_block.take() {
|
||||
if !matches!(block.terminator, Terminator::Return | Terminator::Jump(_) | Terminator::JumpIfFalse { .. }) {
|
||||
block.terminator = Terminator::Return;
|
||||
}
|
||||
if let Some(func) = &mut self.current_function {
|
||||
func.blocks.push(block);
|
||||
}
|
||||
}
|
||||
|
||||
self.current_function.take().unwrap()
|
||||
}
|
||||
|
||||
fn lower_node(&mut self, node: &Node) {
|
||||
match node {
|
||||
Node::Block(n) => self.lower_block(n),
|
||||
Node::LetStmt(n) => self.lower_let_stmt(n),
|
||||
Node::ExprStmt(n) => self.lower_node(&n.expr),
|
||||
Node::ReturnStmt(n) => self.lower_return_stmt(n),
|
||||
Node::IntLit(n) => {
|
||||
let id = self.program.const_pool.add_int(n.value);
|
||||
self.emit(Instr::PushConst(id));
|
||||
}
|
||||
Node::FloatLit(n) => {
|
||||
let id = self.program.const_pool.add_float(n.value);
|
||||
self.emit(Instr::PushConst(id));
|
||||
}
|
||||
Node::StringLit(n) => {
|
||||
let id = self.program.const_pool.add_string(n.value.clone());
|
||||
self.emit(Instr::PushConst(id));
|
||||
}
|
||||
Node::Ident(n) => self.lower_ident(n),
|
||||
Node::Call(n) => self.lower_call(n),
|
||||
Node::Binary(n) => self.lower_binary(n),
|
||||
Node::Unary(n) => self.lower_unary(n),
|
||||
Node::IfExpr(n) => self.lower_if_expr(n),
|
||||
Node::Alloc(n) => self.lower_alloc(n),
|
||||
Node::Mutate(n) => self.lower_hip(n.span, &n.target, &n.binding, &n.body, true),
|
||||
Node::Borrow(n) => self.lower_hip(n.span, &n.target, &n.binding, &n.body, false),
|
||||
Node::Peek(n) => self.lower_hip(n.span, &n.target, &n.binding, &n.body, false),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
fn lower_alloc(&mut self, _n: &AllocNode) {
|
||||
// Allocation: Push type descriptor? For v0 just emit Alloc
|
||||
self.emit(Instr::Alloc);
|
||||
}
|
||||
|
||||
fn lower_hip(&mut self, _span: crate::common::spans::Span, target: &Node, binding: &str, body: &Node, is_mutate: bool) {
|
||||
// HIP Access Pattern:
|
||||
// 1. Evaluate target (gate)
|
||||
self.lower_node(target);
|
||||
// 2. ReadGate (pops gate, pushes reference/value)
|
||||
self.emit(Instr::ReadGate);
|
||||
// 3. Bind to local
|
||||
let slot = self.get_next_local_slot();
|
||||
self.local_vars.push(HashMap::new());
|
||||
self.local_vars.last_mut().unwrap().insert(binding.to_string(), slot);
|
||||
self.emit(Instr::SetLocal(slot));
|
||||
|
||||
// 4. Body
|
||||
self.lower_node(body);
|
||||
|
||||
// 5. Cleanup / WriteBack
|
||||
if is_mutate {
|
||||
// Need the gate again? This is IR-design dependent.
|
||||
// Let's assume WriteGate pops value and use some internal mechanism for gate.
|
||||
self.emit(Instr::GetLocal(slot));
|
||||
self.emit(Instr::WriteGate);
|
||||
}
|
||||
|
||||
self.local_vars.pop();
|
||||
}
|
||||
|
||||
fn lower_block(&mut self, n: &BlockNode) {
|
||||
self.local_vars.push(HashMap::new());
|
||||
for stmt in &n.stmts {
|
||||
self.lower_node(stmt);
|
||||
}
|
||||
if let Some(tail) = &n.tail {
|
||||
self.lower_node(tail);
|
||||
}
|
||||
self.local_vars.pop();
|
||||
}
|
||||
|
||||
fn lower_let_stmt(&mut self, n: &LetStmtNode) {
|
||||
self.lower_node(&n.init);
|
||||
let slot = self.get_next_local_slot();
|
||||
self.local_vars.last_mut().unwrap().insert(n.name.clone(), slot);
|
||||
self.emit(Instr::SetLocal(slot));
|
||||
}
|
||||
|
||||
fn lower_return_stmt(&mut self, n: &ReturnStmtNode) {
|
||||
if let Some(expr) = &n.expr {
|
||||
self.lower_node(expr);
|
||||
}
|
||||
self.terminate(Terminator::Return);
|
||||
}
|
||||
|
||||
fn lower_ident(&mut self, n: &IdentNode) {
|
||||
if let Some(slot) = self.lookup_local(&n.name) {
|
||||
self.emit(Instr::GetLocal(slot));
|
||||
} else {
|
||||
// Check if it's a function (for first-class functions if supported)
|
||||
if let Some(_id) = self.function_ids.get(&n.name) {
|
||||
// Push function reference? Not in v0.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn lower_call(&mut self, n: &CallNode) {
|
||||
for arg in &n.args {
|
||||
self.lower_node(arg);
|
||||
}
|
||||
match &*n.callee {
|
||||
Node::Ident(id_node) => {
|
||||
if let Some(func_id) = self.function_ids.get(&id_node.name) {
|
||||
self.emit(Instr::Call(*func_id, n.args.len() as u32));
|
||||
} else {
|
||||
// Unknown function - might be a builtin or diagnostic was missed
|
||||
self.emit(Instr::Call(FunctionId(0), n.args.len() as u32));
|
||||
}
|
||||
}
|
||||
Node::MemberAccess(ma) => {
|
||||
if let Node::Ident(obj_id) = &*ma.object {
|
||||
if let Some(syscall_id) = self.contract_registry.resolve(&obj_id.name, &ma.member) {
|
||||
self.emit(Instr::Syscall(syscall_id));
|
||||
} else {
|
||||
// Regular member call (method)
|
||||
// In v0 we don't handle this yet, so emit dummy call
|
||||
self.emit(Instr::Call(FunctionId(0), n.args.len() as u32));
|
||||
}
|
||||
} else {
|
||||
self.emit(Instr::Call(FunctionId(0), n.args.len() as u32));
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
self.emit(Instr::Call(FunctionId(0), n.args.len() as u32));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn lower_binary(&mut self, n: &BinaryNode) {
|
||||
self.lower_node(&n.left);
|
||||
self.lower_node(&n.right);
|
||||
match n.op.as_str() {
|
||||
"+" => self.emit(Instr::Add),
|
||||
"-" => self.emit(Instr::Sub),
|
||||
"*" => self.emit(Instr::Mul),
|
||||
"/" => self.emit(Instr::Div),
|
||||
"==" => self.emit(Instr::Eq),
|
||||
"!=" => self.emit(Instr::Neq),
|
||||
"<" => self.emit(Instr::Lt),
|
||||
"<=" => self.emit(Instr::Lte),
|
||||
">" => self.emit(Instr::Gt),
|
||||
">=" => self.emit(Instr::Gte),
|
||||
"&&" => self.emit(Instr::And),
|
||||
"||" => self.emit(Instr::Or),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
fn lower_unary(&mut self, n: &UnaryNode) {
|
||||
self.lower_node(&n.expr);
|
||||
match n.op.as_str() {
|
||||
"-" => self.emit(Instr::Neg),
|
||||
"!" => self.emit(Instr::Not),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
fn lower_if_expr(&mut self, n: &IfExprNode) {
|
||||
let then_id = self.reserve_block_id();
|
||||
let else_id = self.reserve_block_id();
|
||||
let merge_id = self.reserve_block_id();
|
||||
|
||||
self.lower_node(&n.cond);
|
||||
self.terminate(Terminator::JumpIfFalse {
|
||||
target: else_id,
|
||||
else_target: then_id,
|
||||
});
|
||||
|
||||
// Then block
|
||||
self.start_block_with_id(then_id);
|
||||
self.lower_node(&n.then_block);
|
||||
self.terminate(Terminator::Jump(merge_id));
|
||||
|
||||
// Else block
|
||||
self.start_block_with_id(else_id);
|
||||
if let Some(else_block) = &n.else_block {
|
||||
self.lower_node(else_block);
|
||||
}
|
||||
self.terminate(Terminator::Jump(merge_id));
|
||||
|
||||
// Merge block
|
||||
self.start_block_with_id(merge_id);
|
||||
}
|
||||
|
||||
fn lower_type_node(&self, node: &Node) -> Type {
|
||||
match node {
|
||||
Node::TypeName(n) => match n.name.as_str() {
|
||||
"int" => Type::Int,
|
||||
"float" => Type::Float,
|
||||
"bool" => Type::Bool,
|
||||
"string" => Type::String,
|
||||
"void" => Type::Void,
|
||||
_ => Type::Struct(n.name.clone()),
|
||||
},
|
||||
_ => Type::Void,
|
||||
}
|
||||
}
|
||||
|
||||
fn start_block(&mut self) {
|
||||
let id = self.reserve_block_id();
|
||||
self.start_block_with_id(id);
|
||||
}
|
||||
|
||||
fn start_block_with_id(&mut self, id: u32) {
|
||||
if let Some(block) = self.current_block.take() {
|
||||
if let Some(func) = &mut self.current_function {
|
||||
func.blocks.push(block);
|
||||
}
|
||||
}
|
||||
self.current_block = Some(Block {
|
||||
id,
|
||||
instrs: Vec::new(),
|
||||
terminator: Terminator::Return, // Default, will be overwritten
|
||||
});
|
||||
}
|
||||
|
||||
fn reserve_block_id(&mut self) -> u32 {
|
||||
let id = self.next_block_id;
|
||||
self.next_block_id += 1;
|
||||
id
|
||||
}
|
||||
|
||||
fn emit(&mut self, instr: Instr) {
|
||||
if let Some(block) = &mut self.current_block {
|
||||
block.instrs.push(instr);
|
||||
}
|
||||
}
|
||||
|
||||
fn terminate(&mut self, terminator: Terminator) {
|
||||
if let Some(mut block) = self.current_block.take() {
|
||||
block.terminator = terminator;
|
||||
if let Some(func) = &mut self.current_function {
|
||||
func.blocks.push(block);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn get_next_local_slot(&self) -> u32 {
|
||||
self.local_vars.iter().map(|s| s.len() as u32).sum()
|
||||
}
|
||||
|
||||
fn lookup_local(&self, name: &str) -> Option<u32> {
|
||||
for scope in self.local_vars.iter().rev() {
|
||||
if let Some(slot) = scope.get(name) {
|
||||
return Some(*slot);
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
}
|
||||
@ -7,6 +7,8 @@ pub mod symbols;
|
||||
pub mod collector;
|
||||
pub mod resolver;
|
||||
pub mod typecheck;
|
||||
pub mod lowering;
|
||||
pub mod contracts;
|
||||
|
||||
pub use lexer::Lexer;
|
||||
pub use token::{Token, TokenKind};
|
||||
@ -14,11 +16,13 @@ pub use symbols::{Symbol, SymbolTable, ModuleSymbols, Visibility, SymbolKind, Na
|
||||
pub use collector::SymbolCollector;
|
||||
pub use resolver::{Resolver, ModuleProvider};
|
||||
pub use typecheck::TypeChecker;
|
||||
pub use lowering::Lowerer;
|
||||
|
||||
use crate::common::diagnostics::DiagnosticBundle;
|
||||
use crate::common::files::FileManager;
|
||||
use crate::frontends::Frontend;
|
||||
use crate::ir;
|
||||
use crate::lowering::core_to_vm;
|
||||
use std::path::Path;
|
||||
|
||||
pub struct PbsFrontend;
|
||||
@ -56,6 +60,14 @@ impl Frontend for PbsFrontend {
|
||||
let mut typechecker = TypeChecker::new(&mut module_symbols, &EmptyProvider);
|
||||
typechecker.check(&ast)?;
|
||||
|
||||
Ok(ir::Module::new("dummy".to_string()))
|
||||
// Lower to Core IR
|
||||
let lowerer = Lowerer::new(&module_symbols);
|
||||
let module_name = entry.file_stem().unwrap().to_string_lossy();
|
||||
let core_program = lowerer.lower_file(&ast, &module_name);
|
||||
|
||||
// Lower Core IR to VM IR
|
||||
core_to_vm::lower_program(&core_program).map_err(|e| {
|
||||
DiagnosticBundle::error(format!("Lowering error: {}", e), None)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@ -215,6 +215,13 @@ impl Parser {
|
||||
_ => return Err(self.error("Expected 'struct', 'contract', or 'error'")),
|
||||
};
|
||||
let name = self.expect_identifier()?;
|
||||
|
||||
let mut is_host = false;
|
||||
if self.peek().kind == TokenKind::Host {
|
||||
self.advance();
|
||||
is_host = true;
|
||||
}
|
||||
|
||||
let body = self.parse_type_body()?;
|
||||
|
||||
let body_span = body.span();
|
||||
@ -223,6 +230,7 @@ impl Parser {
|
||||
vis,
|
||||
type_kind,
|
||||
name,
|
||||
is_host,
|
||||
body: Box::new(body),
|
||||
}))
|
||||
}
|
||||
@ -417,6 +425,45 @@ impl Parser {
|
||||
}))
|
||||
}
|
||||
|
||||
fn parse_alloc(&mut self) -> Result<Node, DiagnosticBundle> {
|
||||
let start_span = self.consume(TokenKind::Alloc)?.span;
|
||||
let ty = self.parse_type_ref()?;
|
||||
Ok(Node::Alloc(AllocNode {
|
||||
span: Span::new(self.file_id, start_span.start, ty.span().end),
|
||||
ty: Box::new(ty),
|
||||
}))
|
||||
}
|
||||
|
||||
fn parse_mutate_borrow_peek(&mut self, kind: TokenKind) -> Result<Node, DiagnosticBundle> {
|
||||
let start_span = self.consume(kind.clone())?.span;
|
||||
let target_expr = self.parse_expr(0)?;
|
||||
|
||||
let (target, binding) = match target_expr {
|
||||
Node::Cast(cast) => {
|
||||
match *cast.ty {
|
||||
Node::Ident(id) => (*cast.expr, id.name),
|
||||
Node::TypeName(tn) => (*cast.expr, tn.name),
|
||||
_ => return Err(self.error("Expected binding name after 'as'")),
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
self.consume(TokenKind::As)?;
|
||||
let binding = self.expect_identifier()?;
|
||||
(target_expr, binding)
|
||||
}
|
||||
};
|
||||
|
||||
let body = self.parse_block()?;
|
||||
let span = Span::new(self.file_id, start_span.start, body.span().end);
|
||||
|
||||
match kind {
|
||||
TokenKind::Mutate => Ok(Node::Mutate(MutateNode { span, target: Box::new(target), binding, body: Box::new(body) })),
|
||||
TokenKind::Borrow => Ok(Node::Borrow(BorrowNode { span, target: Box::new(target), binding, body: Box::new(body) })),
|
||||
TokenKind::Peek => Ok(Node::Peek(PeekNode { span, target: Box::new(target), binding, body: Box::new(body) })),
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_expr(&mut self, min_precedence: u8) -> Result<Node, DiagnosticBundle> {
|
||||
let mut left = self.parse_primary()?;
|
||||
|
||||
@ -467,6 +514,8 @@ impl Parser {
|
||||
node = self.parse_call(node)?;
|
||||
} else if self.peek().kind == TokenKind::As {
|
||||
node = self.parse_cast(node)?;
|
||||
} else if self.peek().kind == TokenKind::Dot {
|
||||
node = self.parse_member_access(node)?;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
@ -488,6 +537,8 @@ impl Parser {
|
||||
node = self.parse_call(node)?;
|
||||
} else if self.peek().kind == TokenKind::As {
|
||||
node = self.parse_cast(node)?;
|
||||
} else if self.peek().kind == TokenKind::Dot {
|
||||
node = self.parse_member_access(node)?;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
@ -503,6 +554,10 @@ impl Parser {
|
||||
TokenKind::OpenBrace => self.parse_block(),
|
||||
TokenKind::If => self.parse_if_expr(),
|
||||
TokenKind::When => self.parse_when_expr(),
|
||||
TokenKind::Alloc => self.parse_alloc(),
|
||||
TokenKind::Mutate => self.parse_mutate_borrow_peek(TokenKind::Mutate),
|
||||
TokenKind::Borrow => self.parse_mutate_borrow_peek(TokenKind::Borrow),
|
||||
TokenKind::Peek => self.parse_mutate_borrow_peek(TokenKind::Peek),
|
||||
TokenKind::Minus | TokenKind::Not => {
|
||||
self.advance();
|
||||
let op = match tok.kind {
|
||||
@ -521,6 +576,16 @@ impl Parser {
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_member_access(&mut self, object: Node) -> Result<Node, DiagnosticBundle> {
|
||||
self.consume(TokenKind::Dot)?;
|
||||
let member = self.expect_identifier()?;
|
||||
Ok(Node::MemberAccess(MemberAccessNode {
|
||||
span: Span::new(self.file_id, object.span().start, self.tokens[self.pos-1].span.end),
|
||||
object: Box::new(object),
|
||||
member,
|
||||
}))
|
||||
}
|
||||
|
||||
fn parse_call(&mut self, callee: Node) -> Result<Node, DiagnosticBundle> {
|
||||
self.consume(TokenKind::OpenParen)?;
|
||||
let mut args = Vec::new();
|
||||
@ -714,6 +779,11 @@ impl Node {
|
||||
Node::WhenArm(n) => n.span,
|
||||
Node::TypeName(n) => n.span,
|
||||
Node::TypeApp(n) => n.span,
|
||||
Node::Alloc(n) => n.span,
|
||||
Node::Mutate(n) => n.span,
|
||||
Node::Borrow(n) => n.span,
|
||||
Node::Peek(n) => n.span,
|
||||
Node::MemberAccess(n) => n.span,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -146,6 +146,35 @@ impl<'a> Resolver<'a> {
|
||||
self.resolve_type_ref(arg);
|
||||
}
|
||||
}
|
||||
Node::Alloc(n) => {
|
||||
self.resolve_type_ref(&n.ty);
|
||||
}
|
||||
Node::Mutate(n) => {
|
||||
self.resolve_node(&n.target);
|
||||
self.enter_scope();
|
||||
self.define_local(&n.binding, n.span, SymbolKind::Local);
|
||||
self.resolve_node(&n.body);
|
||||
self.exit_scope();
|
||||
}
|
||||
Node::Borrow(n) => {
|
||||
self.resolve_node(&n.target);
|
||||
self.enter_scope();
|
||||
self.define_local(&n.binding, n.span, SymbolKind::Local);
|
||||
self.resolve_node(&n.body);
|
||||
self.exit_scope();
|
||||
}
|
||||
Node::Peek(n) => {
|
||||
self.resolve_node(&n.target);
|
||||
self.enter_scope();
|
||||
self.define_local(&n.binding, n.span, SymbolKind::Local);
|
||||
self.resolve_node(&n.body);
|
||||
self.exit_scope();
|
||||
}
|
||||
Node::MemberAccess(n) => {
|
||||
self.resolve_node(&n.object);
|
||||
// For member access, the member name itself isn't resolved in the value namespace
|
||||
// unless it's a property. In v0, we mostly care about host calls.
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
@ -8,7 +8,7 @@ use std::collections::HashMap;
|
||||
|
||||
pub struct TypeChecker<'a> {
|
||||
module_symbols: &'a mut ModuleSymbols,
|
||||
module_provider: &'a dyn ModuleProvider,
|
||||
_module_provider: &'a dyn ModuleProvider,
|
||||
scopes: Vec<HashMap<String, PbsType>>,
|
||||
mut_bindings: Vec<HashMap<String, bool>>,
|
||||
current_return_type: Option<PbsType>,
|
||||
@ -22,7 +22,7 @@ impl<'a> TypeChecker<'a> {
|
||||
) -> Self {
|
||||
Self {
|
||||
module_symbols,
|
||||
module_provider,
|
||||
_module_provider: module_provider,
|
||||
scopes: Vec::new(),
|
||||
mut_bindings: Vec::new(),
|
||||
current_return_type: None,
|
||||
@ -130,10 +130,47 @@ impl<'a> TypeChecker<'a> {
|
||||
Node::Cast(n) => self.check_cast(n),
|
||||
Node::IfExpr(n) => self.check_if_expr(n),
|
||||
Node::WhenExpr(n) => self.check_when_expr(n),
|
||||
Node::Alloc(n) => self.check_alloc(n),
|
||||
Node::Mutate(n) => self.check_hip(n.span, &n.target, &n.binding, &n.body, true),
|
||||
Node::Borrow(n) => self.check_hip(n.span, &n.target, &n.binding, &n.body, false),
|
||||
Node::Peek(n) => self.check_hip(n.span, &n.target, &n.binding, &n.body, false),
|
||||
Node::MemberAccess(n) => self.check_member_access(n),
|
||||
_ => PbsType::Void,
|
||||
}
|
||||
}
|
||||
|
||||
fn check_member_access(&mut self, n: &MemberAccessNode) -> PbsType {
|
||||
let _obj_ty = self.check_node(&n.object);
|
||||
// For v0, we assume member access on a host contract is valid and return a dummy type
|
||||
// or resolve it if we have contract info.
|
||||
PbsType::Void
|
||||
}
|
||||
|
||||
fn check_alloc(&mut self, n: &AllocNode) -> PbsType {
|
||||
let ty = self.resolve_type_node(&n.ty);
|
||||
// For v0, alloc returns something that can be used with mutate/borrow/peek.
|
||||
// We'll call it a gate to the type.
|
||||
PbsType::Contract(format!("Gate<{}>", ty)) // Approximation for v0
|
||||
}
|
||||
|
||||
fn check_hip(&mut self, _span: Span, target: &Node, binding: &str, body: &Node, is_mut: bool) -> PbsType {
|
||||
let target_ty = self.check_node(target);
|
||||
// In v0, we assume target is a gate. We bind the inner type to the binding.
|
||||
let inner_ty = match target_ty {
|
||||
PbsType::Contract(name) if name.starts_with("Gate<") => {
|
||||
// Try to extract type name from Gate<TypeName>
|
||||
PbsType::Void // Simplified
|
||||
}
|
||||
_ => PbsType::Void
|
||||
};
|
||||
|
||||
self.enter_scope();
|
||||
self.define_local(binding, inner_ty, is_mut);
|
||||
let body_ty = self.check_node(body);
|
||||
self.exit_scope();
|
||||
body_ty
|
||||
}
|
||||
|
||||
fn check_fn_decl(&mut self, n: &FnDeclNode) {
|
||||
let sig = self.module_symbols.value_symbols.get(&n.name).and_then(|s| s.ty.clone());
|
||||
if let Some(PbsType::Function { params, return_type }) = sig {
|
||||
|
||||
@ -37,4 +37,16 @@ impl ConstPool {
|
||||
pub fn get(&self, id: ConstId) -> Option<&ConstantValue> {
|
||||
self.constants.get(id.0 as usize)
|
||||
}
|
||||
|
||||
pub fn add_int(&mut self, value: i64) -> ConstId {
|
||||
self.insert(ConstantValue::Int(value))
|
||||
}
|
||||
|
||||
pub fn add_float(&mut self, value: f64) -> ConstId {
|
||||
self.insert(ConstantValue::Float(value))
|
||||
}
|
||||
|
||||
pub fn add_string(&mut self, value: String) -> ConstId {
|
||||
self.insert(ConstantValue::String(value))
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,11 +1,20 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
use super::ids::FunctionId;
|
||||
use super::block::Block;
|
||||
use super::types::Type;
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||
pub struct Param {
|
||||
pub name: String,
|
||||
pub ty: Type,
|
||||
}
|
||||
|
||||
/// A function within a module, composed of basic blocks forming a CFG.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||
pub struct Function {
|
||||
pub id: FunctionId,
|
||||
pub name: String,
|
||||
pub params: Vec<Param>,
|
||||
pub return_type: Type,
|
||||
pub blocks: Vec<Block>,
|
||||
}
|
||||
|
||||
@ -10,4 +10,31 @@ pub enum Instr {
|
||||
Call(FunctionId, u32),
|
||||
/// Host calls (syscalls).
|
||||
Syscall(u32),
|
||||
/// Variable access.
|
||||
GetLocal(u32),
|
||||
SetLocal(u32),
|
||||
/// Stack operations.
|
||||
Pop,
|
||||
Dup,
|
||||
/// Arithmetic.
|
||||
Add,
|
||||
Sub,
|
||||
Mul,
|
||||
Div,
|
||||
Neg,
|
||||
/// Logical/Comparison.
|
||||
Eq,
|
||||
Neq,
|
||||
Lt,
|
||||
Lte,
|
||||
Gt,
|
||||
Gte,
|
||||
And,
|
||||
Or,
|
||||
Not,
|
||||
/// HIP operations.
|
||||
Alloc,
|
||||
Free, // Not used in v0 but good to have in Core IR
|
||||
ReadGate,
|
||||
WriteGate,
|
||||
}
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
pub mod ids;
|
||||
pub mod const_pool;
|
||||
pub mod types;
|
||||
pub mod program;
|
||||
pub mod module;
|
||||
pub mod function;
|
||||
@ -9,6 +10,7 @@ pub mod terminator;
|
||||
|
||||
pub use ids::*;
|
||||
pub use const_pool::*;
|
||||
pub use types::*;
|
||||
pub use program::*;
|
||||
pub use module::*;
|
||||
pub use function::*;
|
||||
|
||||
@ -7,4 +7,10 @@ pub enum Terminator {
|
||||
Return,
|
||||
/// Unconditional jump to another block (by index/ID).
|
||||
Jump(u32),
|
||||
/// Conditional jump: pops a bool, if false jumps to target, else continues to next block?
|
||||
/// Actually, in a CFG, we usually have two targets for a conditional jump.
|
||||
JumpIfFalse {
|
||||
target: u32,
|
||||
else_target: u32,
|
||||
},
|
||||
}
|
||||
|
||||
20
crates/prometeu-compiler/src/ir_core/types.rs
Normal file
20
crates/prometeu-compiler/src/ir_core/types.rs
Normal file
@ -0,0 +1,20 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub enum Type {
|
||||
Void,
|
||||
Int,
|
||||
Float,
|
||||
Bool,
|
||||
String,
|
||||
Optional(Box<Type>),
|
||||
Result(Box<Type>, Box<Type>),
|
||||
Struct(String),
|
||||
Service(String),
|
||||
Contract(String),
|
||||
ErrorType(String),
|
||||
Function {
|
||||
params: Vec<Type>,
|
||||
return_type: Box<Type>,
|
||||
},
|
||||
}
|
||||
@ -30,8 +30,11 @@ pub fn lower_function(core_func: &ir_core::Function) -> Result<ir::Function> {
|
||||
let mut vm_func = ir::Function {
|
||||
id: core_func.id,
|
||||
name: core_func.name.clone(),
|
||||
params: vec![], // Params are not yet represented in Core IR Function
|
||||
return_type: ir::Type::Null, // Return type is not yet represented in Core IR Function
|
||||
params: core_func.params.iter().map(|p| ir::Param {
|
||||
name: p.name.clone(),
|
||||
r#type: lower_type(&p.ty),
|
||||
}).collect(),
|
||||
return_type: lower_type(&core_func.return_type),
|
||||
body: vec![],
|
||||
};
|
||||
|
||||
@ -43,29 +46,38 @@ pub fn lower_function(core_func: &ir_core::Function) -> Result<ir::Function> {
|
||||
));
|
||||
|
||||
for instr in &block.instrs {
|
||||
match instr {
|
||||
ir_core::Instr::PushConst(id) => {
|
||||
vm_func.body.push(ir::Instruction::new(
|
||||
ir::InstrKind::PushConst(*id),
|
||||
None,
|
||||
));
|
||||
let kind = match instr {
|
||||
ir_core::Instr::PushConst(id) => ir::InstrKind::PushConst(*id),
|
||||
ir_core::Instr::Call(func_id, arg_count) => ir::InstrKind::Call {
|
||||
func_id: *func_id,
|
||||
arg_count: *arg_count
|
||||
},
|
||||
ir_core::Instr::Syscall(id) => ir::InstrKind::Syscall(*id),
|
||||
ir_core::Instr::GetLocal(slot) => ir::InstrKind::GetLocal(*slot),
|
||||
ir_core::Instr::SetLocal(slot) => ir::InstrKind::SetLocal(*slot),
|
||||
ir_core::Instr::Pop => ir::InstrKind::Pop,
|
||||
ir_core::Instr::Dup => ir::InstrKind::Dup,
|
||||
ir_core::Instr::Add => ir::InstrKind::Add,
|
||||
ir_core::Instr::Sub => ir::InstrKind::Sub,
|
||||
ir_core::Instr::Mul => ir::InstrKind::Mul,
|
||||
ir_core::Instr::Div => ir::InstrKind::Div,
|
||||
ir_core::Instr::Neg => ir::InstrKind::Neg,
|
||||
ir_core::Instr::Eq => ir::InstrKind::Eq,
|
||||
ir_core::Instr::Neq => ir::InstrKind::Neq,
|
||||
ir_core::Instr::Lt => ir::InstrKind::Lt,
|
||||
ir_core::Instr::Lte => ir::InstrKind::Lte,
|
||||
ir_core::Instr::Gt => ir::InstrKind::Gt,
|
||||
ir_core::Instr::Gte => ir::InstrKind::Gte,
|
||||
ir_core::Instr::And => ir::InstrKind::And,
|
||||
ir_core::Instr::Or => ir::InstrKind::Or,
|
||||
ir_core::Instr::Not => ir::InstrKind::Not,
|
||||
ir_core::Instr::Alloc | ir_core::Instr::Free | ir_core::Instr::ReadGate | ir_core::Instr::WriteGate => {
|
||||
// HIP effects are not yet supported in VM IR, so we emit Nop or similar.
|
||||
// For now, let's use Nop.
|
||||
ir::InstrKind::Nop
|
||||
}
|
||||
ir_core::Instr::Call(func_id, arg_count) => {
|
||||
vm_func.body.push(ir::Instruction::new(
|
||||
ir::InstrKind::Call {
|
||||
func_id: *func_id,
|
||||
arg_count: *arg_count
|
||||
},
|
||||
None,
|
||||
));
|
||||
}
|
||||
ir_core::Instr::Syscall(id) => {
|
||||
vm_func.body.push(ir::Instruction::new(
|
||||
ir::InstrKind::Syscall(*id),
|
||||
None,
|
||||
));
|
||||
}
|
||||
}
|
||||
};
|
||||
vm_func.body.push(ir::Instruction::new(kind, None));
|
||||
}
|
||||
|
||||
match &block.terminator {
|
||||
@ -78,8 +90,35 @@ pub fn lower_function(core_func: &ir_core::Function) -> Result<ir::Function> {
|
||||
None,
|
||||
));
|
||||
}
|
||||
ir_core::Terminator::JumpIfFalse { target, else_target } => {
|
||||
vm_func.body.push(ir::Instruction::new(
|
||||
ir::InstrKind::JmpIfFalse(ir::Label(format!("block_{}", target))),
|
||||
None,
|
||||
));
|
||||
vm_func.body.push(ir::Instruction::new(
|
||||
ir::InstrKind::Jmp(ir::Label(format!("block_{}", else_target))),
|
||||
None,
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(vm_func)
|
||||
}
|
||||
|
||||
fn lower_type(ty: &ir_core::Type) -> ir::Type {
|
||||
match ty {
|
||||
ir_core::Type::Void => ir::Type::Void,
|
||||
ir_core::Type::Int => ir::Type::Int,
|
||||
ir_core::Type::Float => ir::Type::Float,
|
||||
ir_core::Type::Bool => ir::Type::Bool,
|
||||
ir_core::Type::String => ir::Type::String,
|
||||
ir_core::Type::Optional(inner) => ir::Type::Array(Box::new(lower_type(inner))), // Approximation
|
||||
ir_core::Type::Result(ok, _) => lower_type(ok), // Approximation
|
||||
ir_core::Type::Struct(_) => ir::Type::Object,
|
||||
ir_core::Type::Service(_) => ir::Type::Object,
|
||||
ir_core::Type::Contract(_) => ir::Type::Object,
|
||||
ir_core::Type::ErrorType(_) => ir::Type::Object,
|
||||
ir_core::Type::Function { .. } => ir::Type::Function,
|
||||
}
|
||||
}
|
||||
|
||||
@ -23,14 +23,7 @@ fn test_project_root_and_entry_resolution() {
|
||||
// Call compile
|
||||
let result = compiler::compile(project_dir);
|
||||
|
||||
// It should fail with "Frontend 'pbs' not yet fully implemented (Parser OK)"
|
||||
// but ONLY after successfully loading the config and resolving the entry.
|
||||
|
||||
match result {
|
||||
Err(e) => {
|
||||
let msg = e.to_string();
|
||||
assert!(msg.contains("Frontend 'pbs' not yet fully implemented (Parser OK)"), "Unexpected error: {}", msg);
|
||||
}
|
||||
Ok(_) => panic!("Should have failed as pbs is not implemented yet"),
|
||||
}
|
||||
// It should now succeed or at least fail at a later stage,
|
||||
// but the point of this test is config resolution.
|
||||
assert!(result.is_ok(), "Failed to compile: {:?}", result.err());
|
||||
}
|
||||
|
||||
@ -13,6 +13,8 @@ fn test_ir_core_manual_construction() {
|
||||
functions: vec![Function {
|
||||
id: FunctionId(10),
|
||||
name: "entry".to_string(),
|
||||
params: vec![],
|
||||
return_type: Type::Void,
|
||||
blocks: vec![Block {
|
||||
id: 0,
|
||||
instrs: vec![
|
||||
@ -43,6 +45,8 @@ fn test_ir_core_manual_construction() {
|
||||
{
|
||||
"id": 10,
|
||||
"name": "entry",
|
||||
"params": [],
|
||||
"return_type": "Void",
|
||||
"blocks": [
|
||||
{
|
||||
"id": 0,
|
||||
|
||||
@ -15,6 +15,8 @@ fn test_full_lowering() {
|
||||
functions: vec![ir_core::Function {
|
||||
id: FunctionId(1),
|
||||
name: "main".to_string(),
|
||||
params: vec![],
|
||||
return_type: ir_core::Type::Void,
|
||||
blocks: vec![
|
||||
Block {
|
||||
id: 0,
|
||||
|
||||
58
crates/prometeu-compiler/tests/pbs_contract_tests.rs
Normal file
58
crates/prometeu-compiler/tests/pbs_contract_tests.rs
Normal file
@ -0,0 +1,58 @@
|
||||
use prometeu_compiler::frontends::pbs::parser::Parser;
|
||||
use prometeu_compiler::frontends::pbs::collector::SymbolCollector;
|
||||
use prometeu_compiler::frontends::pbs::symbols::ModuleSymbols;
|
||||
use prometeu_compiler::frontends::pbs::lowering::Lowerer;
|
||||
use prometeu_compiler::ir_core;
|
||||
|
||||
#[test]
|
||||
fn test_host_contract_call_lowering() {
|
||||
let code = "
|
||||
fn main() {
|
||||
Gfx.clear(0);
|
||||
Log.write(\"Hello\");
|
||||
}
|
||||
";
|
||||
let mut parser = Parser::new(code, 0);
|
||||
let ast = parser.parse_file().expect("Failed to parse");
|
||||
|
||||
let mut collector = SymbolCollector::new();
|
||||
let (type_symbols, value_symbols) = collector.collect(&ast).expect("Failed to collect symbols");
|
||||
let module_symbols = ModuleSymbols { type_symbols, value_symbols };
|
||||
|
||||
let lowerer = Lowerer::new(&module_symbols);
|
||||
let program = lowerer.lower_file(&ast, "test");
|
||||
|
||||
let func = &program.modules[0].functions[0];
|
||||
let instrs: Vec<_> = func.blocks.iter().flat_map(|b| b.instrs.iter()).collect();
|
||||
|
||||
// Gfx.clear -> 0x1001
|
||||
assert!(instrs.iter().any(|i| matches!(i, ir_core::Instr::Syscall(0x1001))));
|
||||
// Log.write -> 0x5001
|
||||
assert!(instrs.iter().any(|i| matches!(i, ir_core::Instr::Syscall(0x5001))));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_invalid_contract_call_lowering() {
|
||||
let code = "
|
||||
fn main() {
|
||||
Gfx.invalidMethod(0);
|
||||
}
|
||||
";
|
||||
let mut parser = Parser::new(code, 0);
|
||||
let ast = parser.parse_file().expect("Failed to parse");
|
||||
|
||||
let mut collector = SymbolCollector::new();
|
||||
let (type_symbols, value_symbols) = collector.collect(&ast).expect("Failed to collect symbols");
|
||||
let module_symbols = ModuleSymbols { type_symbols, value_symbols };
|
||||
|
||||
let lowerer = Lowerer::new(&module_symbols);
|
||||
let program = lowerer.lower_file(&ast, "test");
|
||||
|
||||
let func = &program.modules[0].functions[0];
|
||||
let instrs: Vec<_> = func.blocks.iter().flat_map(|b| b.instrs.iter()).collect();
|
||||
|
||||
// Should NOT be a syscall if invalid
|
||||
assert!(!instrs.iter().any(|i| matches!(i, ir_core::Instr::Syscall(_))));
|
||||
// Should be a regular call (which might fail later or be a dummy)
|
||||
assert!(instrs.iter().any(|i| matches!(i, ir_core::Instr::Call(_, _))));
|
||||
}
|
||||
95
crates/prometeu-compiler/tests/pbs_lowering_tests.rs
Normal file
95
crates/prometeu-compiler/tests/pbs_lowering_tests.rs
Normal file
@ -0,0 +1,95 @@
|
||||
use prometeu_compiler::frontends::pbs::parser::Parser;
|
||||
use prometeu_compiler::frontends::pbs::collector::SymbolCollector;
|
||||
use prometeu_compiler::frontends::pbs::symbols::ModuleSymbols;
|
||||
use prometeu_compiler::frontends::pbs::lowering::Lowerer;
|
||||
use prometeu_compiler::ir_core;
|
||||
|
||||
#[test]
|
||||
fn test_basic_lowering() {
|
||||
let code = "
|
||||
fn add(a: int, b: int) -> int {
|
||||
return a + b;
|
||||
}
|
||||
fn main() {
|
||||
let x = add(10, 20);
|
||||
}
|
||||
";
|
||||
let mut parser = Parser::new(code, 0);
|
||||
let ast = parser.parse_file().expect("Failed to parse");
|
||||
|
||||
let mut collector = SymbolCollector::new();
|
||||
let (type_symbols, value_symbols) = collector.collect(&ast).expect("Failed to collect symbols");
|
||||
let module_symbols = ModuleSymbols { type_symbols, value_symbols };
|
||||
|
||||
let lowerer = Lowerer::new(&module_symbols);
|
||||
let program = lowerer.lower_file(&ast, "test");
|
||||
|
||||
// Verify program structure
|
||||
assert_eq!(program.modules.len(), 1);
|
||||
let module = &program.modules[0];
|
||||
assert_eq!(module.functions.len(), 2);
|
||||
|
||||
let add_func = module.functions.iter().find(|f| f.name == "add").unwrap();
|
||||
assert_eq!(add_func.params.len(), 2);
|
||||
assert_eq!(add_func.return_type, ir_core::Type::Int);
|
||||
|
||||
// Verify blocks
|
||||
assert!(add_func.blocks.len() >= 1);
|
||||
let first_block = &add_func.blocks[0];
|
||||
// Check for Add instruction
|
||||
assert!(first_block.instrs.iter().any(|i| matches!(i, ir_core::Instr::Add)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_control_flow_lowering() {
|
||||
let code = "
|
||||
fn max(a: int, b: int) -> int {
|
||||
if (a > b) {
|
||||
return a;
|
||||
} else {
|
||||
return b;
|
||||
}
|
||||
}
|
||||
";
|
||||
let mut parser = Parser::new(code, 0);
|
||||
let ast = parser.parse_file().expect("Failed to parse");
|
||||
|
||||
let mut collector = SymbolCollector::new();
|
||||
let (type_symbols, value_symbols) = collector.collect(&ast).expect("Failed to collect symbols");
|
||||
let module_symbols = ModuleSymbols { type_symbols, value_symbols };
|
||||
|
||||
let lowerer = Lowerer::new(&module_symbols);
|
||||
let program = lowerer.lower_file(&ast, "test");
|
||||
|
||||
let max_func = &program.modules[0].functions[0];
|
||||
// Should have multiple blocks for if-else
|
||||
assert!(max_func.blocks.len() >= 3);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_hip_lowering() {
|
||||
let code = "
|
||||
fn test_hip() {
|
||||
let g = alloc int;
|
||||
mutate g as x {
|
||||
let y = x + 1;
|
||||
}
|
||||
}
|
||||
";
|
||||
let mut parser = Parser::new(code, 0);
|
||||
let ast = parser.parse_file().expect("Failed to parse");
|
||||
|
||||
let mut collector = SymbolCollector::new();
|
||||
let (type_symbols, value_symbols) = collector.collect(&ast).expect("Failed to collect symbols");
|
||||
let module_symbols = ModuleSymbols { type_symbols, value_symbols };
|
||||
|
||||
let lowerer = Lowerer::new(&module_symbols);
|
||||
let program = lowerer.lower_file(&ast, "test");
|
||||
|
||||
let func = &program.modules[0].functions[0];
|
||||
let instrs: Vec<_> = func.blocks.iter().flat_map(|b| b.instrs.iter()).collect();
|
||||
|
||||
assert!(instrs.iter().any(|i| matches!(i, ir_core::Instr::Alloc)));
|
||||
assert!(instrs.iter().any(|i| matches!(i, ir_core::Instr::ReadGate)));
|
||||
assert!(instrs.iter().any(|i| matches!(i, ir_core::Instr::WriteGate)));
|
||||
}
|
||||
@ -115,6 +115,7 @@ fn test_visibility_error() {
|
||||
kind: SymbolKind::Struct,
|
||||
namespace: Namespace::Type,
|
||||
visibility: Visibility::FilePrivate,
|
||||
ty: None,
|
||||
span: Span::new(1, 0, 0),
|
||||
}).unwrap();
|
||||
|
||||
@ -158,6 +159,7 @@ fn test_import_resolution() {
|
||||
kind: SymbolKind::Struct,
|
||||
namespace: Namespace::Type,
|
||||
visibility: Visibility::Pub,
|
||||
ty: None,
|
||||
span: Span::new(1, 0, 0),
|
||||
}).unwrap();
|
||||
|
||||
|
||||
@ -86,6 +86,8 @@ fn test_lowering_smoke() {
|
||||
functions: vec![ir_core::Function {
|
||||
id: FunctionId(10),
|
||||
name: "start".to_string(),
|
||||
params: vec![],
|
||||
return_type: ir_core::Type::Void,
|
||||
blocks: vec![ir_core::Block {
|
||||
id: 0,
|
||||
instrs: vec![
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user