diff --git a/crates/prometeu-compiler/src/frontends/pbs/ast.rs b/crates/prometeu-compiler/src/frontends/pbs/ast.rs index 3fbb07ff..d65e44d2 100644 --- a/crates/prometeu-compiler/src/frontends/pbs/ast.rs +++ b/crates/prometeu-compiler/src/frontends/pbs/ast.rs @@ -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, pub type_kind: String, // "struct" | "contract" | "error" pub name: String, + pub is_host: bool, pub body: Box, // TypeBody } @@ -228,3 +234,40 @@ pub struct TypeAppNode { pub base: String, pub args: Vec, } + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +pub struct AllocNode { + pub span: Span, + pub ty: Box, +} + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +pub struct MutateNode { + pub span: Span, + pub target: Box, + pub binding: String, + pub body: Box, // BlockNode +} + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +pub struct BorrowNode { + pub span: Span, + pub target: Box, + pub binding: String, + pub body: Box, // BlockNode +} + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +pub struct PeekNode { + pub span: Span, + pub target: Box, + pub binding: String, + pub body: Box, // BlockNode +} + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +pub struct MemberAccessNode { + pub span: Span, + pub object: Box, + pub member: String, +} diff --git a/crates/prometeu-compiler/src/frontends/pbs/contracts.rs b/crates/prometeu-compiler/src/frontends/pbs/contracts.rs new file mode 100644 index 00000000..542a3971 --- /dev/null +++ b/crates/prometeu-compiler/src/frontends/pbs/contracts.rs @@ -0,0 +1,70 @@ +use std::collections::HashMap; + +pub struct ContractRegistry { + mappings: HashMap>, +} + +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 { + self.mappings.get(contract).and_then(|m| m.get(method)).copied() + } +} diff --git a/crates/prometeu-compiler/src/frontends/pbs/lowering.rs b/crates/prometeu-compiler/src/frontends/pbs/lowering.rs new file mode 100644 index 00000000..b3982730 --- /dev/null +++ b/crates/prometeu-compiler/src/frontends/pbs/lowering.rs @@ -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, + current_block: Option, + next_block_id: u32, + next_func_id: u32, + local_vars: Vec>, + function_ids: HashMap, + 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 { + for scope in self.local_vars.iter().rev() { + if let Some(slot) = scope.get(name) { + return Some(*slot); + } + } + None + } +} diff --git a/crates/prometeu-compiler/src/frontends/pbs/mod.rs b/crates/prometeu-compiler/src/frontends/pbs/mod.rs index cfae1be5..877384c1 100644 --- a/crates/prometeu-compiler/src/frontends/pbs/mod.rs +++ b/crates/prometeu-compiler/src/frontends/pbs/mod.rs @@ -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) + }) } } diff --git a/crates/prometeu-compiler/src/frontends/pbs/parser.rs b/crates/prometeu-compiler/src/frontends/pbs/parser.rs index 23ebb674..96d33f2f 100644 --- a/crates/prometeu-compiler/src/frontends/pbs/parser.rs +++ b/crates/prometeu-compiler/src/frontends/pbs/parser.rs @@ -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 { + 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 { + 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 { 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 { + 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 { 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, } } } diff --git a/crates/prometeu-compiler/src/frontends/pbs/resolver.rs b/crates/prometeu-compiler/src/frontends/pbs/resolver.rs index 0aab601e..c0b931e8 100644 --- a/crates/prometeu-compiler/src/frontends/pbs/resolver.rs +++ b/crates/prometeu-compiler/src/frontends/pbs/resolver.rs @@ -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. + } _ => {} } } diff --git a/crates/prometeu-compiler/src/frontends/pbs/typecheck.rs b/crates/prometeu-compiler/src/frontends/pbs/typecheck.rs index 7863d3a3..bf847704 100644 --- a/crates/prometeu-compiler/src/frontends/pbs/typecheck.rs +++ b/crates/prometeu-compiler/src/frontends/pbs/typecheck.rs @@ -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>, mut_bindings: Vec>, current_return_type: Option, @@ -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 + 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 { diff --git a/crates/prometeu-compiler/src/ir_core/const_pool.rs b/crates/prometeu-compiler/src/ir_core/const_pool.rs index 2e438712..59773690 100644 --- a/crates/prometeu-compiler/src/ir_core/const_pool.rs +++ b/crates/prometeu-compiler/src/ir_core/const_pool.rs @@ -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)) + } } diff --git a/crates/prometeu-compiler/src/ir_core/function.rs b/crates/prometeu-compiler/src/ir_core/function.rs index 96e79640..04e13a86 100644 --- a/crates/prometeu-compiler/src/ir_core/function.rs +++ b/crates/prometeu-compiler/src/ir_core/function.rs @@ -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, + pub return_type: Type, pub blocks: Vec, } diff --git a/crates/prometeu-compiler/src/ir_core/instr.rs b/crates/prometeu-compiler/src/ir_core/instr.rs index 810fed12..50c28f65 100644 --- a/crates/prometeu-compiler/src/ir_core/instr.rs +++ b/crates/prometeu-compiler/src/ir_core/instr.rs @@ -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, } diff --git a/crates/prometeu-compiler/src/ir_core/mod.rs b/crates/prometeu-compiler/src/ir_core/mod.rs index 86af5cdd..12b888d8 100644 --- a/crates/prometeu-compiler/src/ir_core/mod.rs +++ b/crates/prometeu-compiler/src/ir_core/mod.rs @@ -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::*; diff --git a/crates/prometeu-compiler/src/ir_core/terminator.rs b/crates/prometeu-compiler/src/ir_core/terminator.rs index 965986dc..1dcbe342 100644 --- a/crates/prometeu-compiler/src/ir_core/terminator.rs +++ b/crates/prometeu-compiler/src/ir_core/terminator.rs @@ -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, + }, } diff --git a/crates/prometeu-compiler/src/ir_core/types.rs b/crates/prometeu-compiler/src/ir_core/types.rs new file mode 100644 index 00000000..f8a7d8d7 --- /dev/null +++ b/crates/prometeu-compiler/src/ir_core/types.rs @@ -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), + Result(Box, Box), + Struct(String), + Service(String), + Contract(String), + ErrorType(String), + Function { + params: Vec, + return_type: Box, + }, +} diff --git a/crates/prometeu-compiler/src/lowering/core_to_vm.rs b/crates/prometeu-compiler/src/lowering/core_to_vm.rs index 9c2df4f8..1a23a8e8 100644 --- a/crates/prometeu-compiler/src/lowering/core_to_vm.rs +++ b/crates/prometeu-compiler/src/lowering/core_to_vm.rs @@ -30,8 +30,11 @@ pub fn lower_function(core_func: &ir_core::Function) -> Result { 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 { )); 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 { 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, + } +} diff --git a/crates/prometeu-compiler/tests/config_integration.rs b/crates/prometeu-compiler/tests/config_integration.rs index 096c5457..4b40c540 100644 --- a/crates/prometeu-compiler/tests/config_integration.rs +++ b/crates/prometeu-compiler/tests/config_integration.rs @@ -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()); } diff --git a/crates/prometeu-compiler/tests/ir_core_tests.rs b/crates/prometeu-compiler/tests/ir_core_tests.rs index aeaf1aaa..c2ba92a3 100644 --- a/crates/prometeu-compiler/tests/ir_core_tests.rs +++ b/crates/prometeu-compiler/tests/ir_core_tests.rs @@ -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, diff --git a/crates/prometeu-compiler/tests/lowering_tests.rs b/crates/prometeu-compiler/tests/lowering_tests.rs index 88b98897..93db307e 100644 --- a/crates/prometeu-compiler/tests/lowering_tests.rs +++ b/crates/prometeu-compiler/tests/lowering_tests.rs @@ -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, diff --git a/crates/prometeu-compiler/tests/pbs_contract_tests.rs b/crates/prometeu-compiler/tests/pbs_contract_tests.rs new file mode 100644 index 00000000..73a51d6e --- /dev/null +++ b/crates/prometeu-compiler/tests/pbs_contract_tests.rs @@ -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(_, _)))); +} diff --git a/crates/prometeu-compiler/tests/pbs_lowering_tests.rs b/crates/prometeu-compiler/tests/pbs_lowering_tests.rs new file mode 100644 index 00000000..8d5643a1 --- /dev/null +++ b/crates/prometeu-compiler/tests/pbs_lowering_tests.rs @@ -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))); +} diff --git a/crates/prometeu-compiler/tests/pbs_resolver_tests.rs b/crates/prometeu-compiler/tests/pbs_resolver_tests.rs index 347ce42d..468dcde3 100644 --- a/crates/prometeu-compiler/tests/pbs_resolver_tests.rs +++ b/crates/prometeu-compiler/tests/pbs_resolver_tests.rs @@ -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(); diff --git a/crates/prometeu-compiler/tests/vm_ir_tests.rs b/crates/prometeu-compiler/tests/vm_ir_tests.rs index 831d9278..5059cdf4 100644 --- a/crates/prometeu-compiler/tests/vm_ir_tests.rs +++ b/crates/prometeu-compiler/tests/vm_ir_tests.rs @@ -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![