dev/pbs #8

Merged
bquarkz merged 74 commits from dev/pbs into master 2026-02-03 15:28:31 +00:00
21 changed files with 934 additions and 37 deletions
Showing only changes of commit ced7ff0607 - Show all commits

View File

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

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

View 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(&param.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
}
}

View File

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

View File

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

View File

@ -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.
}
_ => {}
}
}

View File

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

View File

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

View File

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

View File

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

View File

@ -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::*;

View File

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

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

View File

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

View File

@ -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());
}

View File

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

View File

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

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

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

View File

@ -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();

View File

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