diff --git a/crates/prometeu-compiler/src/frontends/pbs/ast.rs b/crates/prometeu-compiler/src/frontends/pbs/ast.rs new file mode 100644 index 00000000..3fbb07ff --- /dev/null +++ b/crates/prometeu-compiler/src/frontends/pbs/ast.rs @@ -0,0 +1,230 @@ +use crate::common::spans::Span; +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +#[serde(tag = "kind")] +pub enum Node { + File(FileNode), + Import(ImportNode), + ImportSpec(ImportSpecNode), + ServiceDecl(ServiceDeclNode), + ServiceFnSig(ServiceFnSigNode), + FnDecl(FnDeclNode), + TypeDecl(TypeDeclNode), + TypeBody(TypeBodyNode), + Block(BlockNode), + LetStmt(LetStmtNode), + ExprStmt(ExprStmtNode), + ReturnStmt(ReturnStmtNode), + IntLit(IntLitNode), + FloatLit(FloatLitNode), + BoundedLit(BoundedLitNode), + StringLit(StringLitNode), + Ident(IdentNode), + Call(CallNode), + Unary(UnaryNode), + Binary(BinaryNode), + Cast(CastNode), + IfExpr(IfExprNode), + WhenExpr(WhenExprNode), + WhenArm(WhenArmNode), + TypeName(TypeNameNode), + TypeApp(TypeAppNode), +} + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +pub struct FileNode { + pub span: Span, + pub imports: Vec, + pub decls: Vec, +} + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +pub struct ImportNode { + pub span: Span, + pub spec: Box, // Must be ImportSpec + pub from: String, +} + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +pub struct ImportSpecNode { + pub span: Span, + pub path: Vec, +} + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +pub struct ServiceDeclNode { + pub span: Span, + pub vis: String, // "pub" | "mod" + pub name: String, + pub extends: Option, + pub members: Vec, // ServiceFnSig +} + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +pub struct ServiceFnSigNode { + pub span: Span, + pub name: String, + pub params: Vec, + pub ret: Box, // TypeName or TypeApp +} + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +pub struct ParamNode { + pub span: Span, + pub name: String, + pub ty: Box, +} + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +pub struct FnDeclNode { + pub span: Span, + pub name: String, + pub params: Vec, + pub ret: Option>, + pub else_fallback: Option>, // Block + pub body: Box, // Block +} + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +pub struct TypeDeclNode { + pub span: Span, + pub vis: Option, + pub type_kind: String, // "struct" | "contract" | "error" + pub name: String, + pub body: Box, // TypeBody +} + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +pub struct TypeBodyNode { + pub span: Span, + pub members: Vec, +} + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +pub struct TypeMemberNode { + pub span: Span, + pub name: String, + pub ty: Box, +} + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +pub struct BlockNode { + pub span: Span, + pub stmts: Vec, + pub tail: Option>, +} + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +pub struct LetStmtNode { + pub span: Span, + pub name: String, + pub is_mut: bool, + pub ty: Option>, + pub init: Box, +} + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +pub struct ExprStmtNode { + pub span: Span, + pub expr: Box, +} + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +pub struct ReturnStmtNode { + pub span: Span, + pub expr: Option>, +} + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +pub struct IntLitNode { + pub span: Span, + pub value: i64, +} + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +pub struct FloatLitNode { + pub span: Span, + pub value: f64, +} + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +pub struct BoundedLitNode { + pub span: Span, + pub value: u32, +} + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +pub struct StringLitNode { + pub span: Span, + pub value: String, +} + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +pub struct IdentNode { + pub span: Span, + pub name: String, +} + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +pub struct CallNode { + pub span: Span, + pub callee: Box, + pub args: Vec, +} + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +pub struct UnaryNode { + pub span: Span, + pub op: String, + pub expr: Box, +} + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +pub struct BinaryNode { + pub span: Span, + pub op: String, + pub left: Box, + pub right: Box, +} + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +pub struct CastNode { + pub span: Span, + pub expr: Box, + pub ty: Box, +} + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +pub struct IfExprNode { + pub span: Span, + pub cond: Box, + pub then_block: Box, + pub else_block: Option>, +} + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +pub struct WhenExprNode { + pub span: Span, + pub arms: Vec, // WhenArm +} + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +pub struct WhenArmNode { + pub span: Span, + pub cond: Box, + pub body: Box, +} + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +pub struct TypeNameNode { + pub span: Span, + pub name: String, +} + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +pub struct TypeAppNode { + pub span: Span, + pub base: String, + pub args: Vec, +} diff --git a/crates/prometeu-compiler/src/frontends/pbs/mod.rs b/crates/prometeu-compiler/src/frontends/pbs/mod.rs index dbb46688..41ec8a1b 100644 --- a/crates/prometeu-compiler/src/frontends/pbs/mod.rs +++ b/crates/prometeu-compiler/src/frontends/pbs/mod.rs @@ -1,5 +1,7 @@ pub mod token; pub mod lexer; +pub mod ast; +pub mod parser; pub use lexer::Lexer; pub use token::{Token, TokenKind}; @@ -19,10 +21,18 @@ impl Frontend for PbsFrontend { fn compile_to_ir( &self, - _entry: &Path, - _file_manager: &mut FileManager, + entry: &Path, + file_manager: &mut FileManager, ) -> Result { + let source = std::fs::read_to_string(entry).map_err(|e| { + DiagnosticBundle::error(format!("Failed to read file: {}", e), None) + })?; + let file_id = file_manager.add(entry.to_path_buf(), source.clone()); + + let mut parser = parser::Parser::new(&source, file_id); + let _ast = parser.parse_file()?; + // Parsing and full compilation will be implemented in future PRs. - Err(DiagnosticBundle::error("Frontend 'pbs' not yet implemented".to_string(), None)) + Err(DiagnosticBundle::error("Frontend 'pbs' not yet fully implemented (Parser OK)".to_string(), None)) } } diff --git a/crates/prometeu-compiler/src/frontends/pbs/parser.rs b/crates/prometeu-compiler/src/frontends/pbs/parser.rs new file mode 100644 index 00000000..1798a459 --- /dev/null +++ b/crates/prometeu-compiler/src/frontends/pbs/parser.rs @@ -0,0 +1,672 @@ +use crate::common::diagnostics::{Diagnostic, DiagnosticBundle, DiagnosticLevel}; +use crate::common::spans::Span; +use crate::frontends::pbs::ast::*; +use crate::frontends::pbs::lexer::Lexer; +use crate::frontends::pbs::token::{Token, TokenKind}; + +pub struct Parser { + tokens: Vec, + pos: usize, + file_id: usize, + errors: Vec, +} + +impl Parser { + pub fn new(source: &str, file_id: usize) -> Self { + let mut lexer = Lexer::new(source, file_id); + let mut tokens = Vec::new(); + loop { + let token = lexer.next_token(); + let is_eof = token.kind == TokenKind::Eof; + tokens.push(token); + if is_eof { + break; + } + } + + Self { + tokens, + pos: 0, + file_id, + errors: Vec::new(), + } + } + + pub fn parse_file(&mut self) -> Result { + let start_span = self.peek().span; + let mut imports = Vec::new(); + let mut decls = Vec::new(); + + while self.peek().kind != TokenKind::Eof { + if self.peek().kind == TokenKind::Import { + match self.parse_import() { + Ok(imp) => imports.push(imp), + Err(_) => self.recover_to_top_level(), + } + } else { + match self.parse_top_level_decl() { + Ok(decl) => decls.push(decl), + Err(_) => self.recover_to_top_level(), + } + } + } + + let end_span = self.peek().span; + + if !self.errors.is_empty() { + return Err(DiagnosticBundle { + diagnostics: self.errors.clone(), + }); + } + + Ok(FileNode { + span: Span::new(self.file_id, start_span.start, end_span.end), + imports, + decls, + }) + } + + fn recover_to_top_level(&mut self) { + while self.peek().kind != TokenKind::Eof { + match self.peek().kind { + TokenKind::Import + | TokenKind::Fn + | TokenKind::Pub + | TokenKind::Mod + | TokenKind::Declare + | TokenKind::Service => break, + _ => self.advance(), + }; + } + } + + fn parse_import(&mut self) -> Result { + let start_span = self.consume(TokenKind::Import)?.span; + let spec = self.parse_import_spec()?; + self.consume(TokenKind::Identifier("from".to_string()))?; + + let path_tok = match self.peek().kind { + TokenKind::StringLit(ref s) => { + let s = s.clone(); + let span = self.advance().span; + (s, span) + } + _ => return Err(self.error("Expected string literal after 'from'")), + }; + + if self.peek().kind == TokenKind::Semicolon { + self.advance(); + } + + Ok(Node::Import(ImportNode { + span: Span::new(self.file_id, start_span.start, path_tok.1.end), + spec: Box::new(spec), + from: path_tok.0, + })) + } + + fn parse_import_spec(&mut self) -> Result { + let mut path = Vec::new(); + let start_span = self.peek().span; + loop { + if let TokenKind::Identifier(ref name) = self.peek().kind { + path.push(name.clone()); + self.advance(); + } else { + return Err(self.error("Expected identifier in import spec")); + } + + if self.peek().kind == TokenKind::Dot { + self.advance(); + } else { + break; + } + } + let end_span = self.tokens[self.pos-1].span; + Ok(Node::ImportSpec(ImportSpecNode { + span: Span::new(self.file_id, start_span.start, end_span.end), + path + })) + } + + fn parse_top_level_decl(&mut self) -> Result { + match self.peek().kind { + TokenKind::Fn => self.parse_fn_decl(), + TokenKind::Pub | TokenKind::Mod | TokenKind::Declare | TokenKind::Service => self.parse_decl(), + _ => Err(self.error("Expected top-level declaration")), + } + } + + fn parse_decl(&mut self) -> Result { + let vis = if self.peek().kind == TokenKind::Pub { + self.advance(); + Some("pub".to_string()) + } else if self.peek().kind == TokenKind::Mod { + self.advance(); + Some("mod".to_string()) + } else { + None + }; + + match self.peek().kind { + TokenKind::Service => self.parse_service_decl(vis.unwrap_or_else(|| "pub".to_string())), + TokenKind::Declare => self.parse_type_decl(vis), + _ => Err(self.error("Expected 'service' or 'declare'")), + } + } + + fn parse_service_decl(&mut self, vis: String) -> Result { + let start_span = self.consume(TokenKind::Service)?.span; + let name = self.expect_identifier()?; + let mut extends = None; + if self.peek().kind == TokenKind::Colon { + self.advance(); + extends = Some(self.expect_identifier()?); + } + + self.consume(TokenKind::OpenBrace)?; + let mut members = Vec::new(); + while self.peek().kind != TokenKind::CloseBrace && self.peek().kind != TokenKind::Eof { + members.push(self.parse_service_member()?); + // Optional semicolon after signature + if self.peek().kind == TokenKind::Semicolon { + self.advance(); + } + } + let end_span = self.consume(TokenKind::CloseBrace)?.span; + + Ok(Node::ServiceDecl(ServiceDeclNode { + span: Span::new(self.file_id, start_span.start, end_span.end), + vis, + name, + extends, + members, + })) + } + + fn parse_service_member(&mut self) -> Result { + let start_span = self.consume(TokenKind::Fn)?.span; + let name = self.expect_identifier()?; + let params = self.parse_param_list()?; + let ret = if self.peek().kind == TokenKind::Arrow { + self.advance(); + Box::new(self.parse_type_ref()?) + } else { + Box::new(Node::TypeName(TypeNameNode { + span: Span::new(self.file_id, 0, 0), // Placeholder for void + name: "void".to_string(), + })) + }; + + Ok(Node::ServiceFnSig(ServiceFnSigNode { + span: Span::new(self.file_id, start_span.start, ret.span().end), + name, + params, + ret, + })) + } + + fn parse_type_decl(&mut self, vis: Option) -> Result { + let start_span = self.consume(TokenKind::Declare)?.span; + let type_kind = match self.peek().kind { + TokenKind::Struct => { self.advance(); "struct".to_string() } + TokenKind::Contract => { self.advance(); "contract".to_string() } + TokenKind::Error => { self.advance(); "error".to_string() } + _ => return Err(self.error("Expected 'struct', 'contract', or 'error'")), + }; + let name = self.expect_identifier()?; + let body = self.parse_type_body()?; + + let body_span = body.span(); + Ok(Node::TypeDecl(TypeDeclNode { + span: Span::new(self.file_id, start_span.start, body_span.end), + vis, + type_kind, + name, + body: Box::new(body), + })) + } + + fn parse_type_body(&mut self) -> Result { + let start_span = self.consume(TokenKind::OpenBrace)?.span; + let mut members = Vec::new(); + while self.peek().kind != TokenKind::CloseBrace && self.peek().kind != TokenKind::Eof { + let m_start = self.peek().span.start; + let name = self.expect_identifier()?; + self.consume(TokenKind::Colon)?; + let ty = self.parse_type_ref()?; + let m_end = ty.span().end; + members.push(TypeMemberNode { + span: Span::new(self.file_id, m_start, m_end), + name, + ty: Box::new(ty) + }); + if self.peek().kind == TokenKind::Comma { + self.advance(); + } else { + break; + } + } + let end_span = self.consume(TokenKind::CloseBrace)?.span; + Ok(Node::TypeBody(TypeBodyNode { + span: Span::new(self.file_id, start_span.start, end_span.end), + members, + })) + } + + fn parse_fn_decl(&mut self) -> Result { + let start_span = self.consume(TokenKind::Fn)?.span; + let name = self.expect_identifier()?; + let params = self.parse_param_list()?; + let _ret = if self.peek().kind == TokenKind::Arrow { + self.advance(); + Some(Box::new(self.parse_type_ref()?)) + } else { + None + }; + + let mut else_fallback = None; + if self.peek().kind == TokenKind::Else { + self.advance(); + else_fallback = Some(Box::new(self.parse_block()?)); + } + + let body = self.parse_block()?; + let body_span = body.span(); + + Ok(Node::FnDecl(FnDeclNode { + span: Span::new(self.file_id, start_span.start, body_span.end), + name, + params, + ret: _ret, + else_fallback, + body: Box::new(body), + })) + } + + fn parse_param_list(&mut self) -> Result, DiagnosticBundle> { + self.consume(TokenKind::OpenParen)?; + let mut params = Vec::new(); + while self.peek().kind != TokenKind::CloseParen && self.peek().kind != TokenKind::Eof { + let p_start = self.peek().span.start; + let name = self.expect_identifier()?; + self.consume(TokenKind::Colon)?; + let ty = self.parse_type_ref()?; + let p_end = ty.span().end; + params.push(ParamNode { + span: Span::new(self.file_id, p_start, p_end), + name, + ty: Box::new(ty) + }); + if self.peek().kind == TokenKind::Comma { + self.advance(); + } else { + break; + } + } + self.consume(TokenKind::CloseParen)?; + Ok(params) + } + + fn parse_type_ref(&mut self) -> Result { + let id_tok = self.peek().clone(); + let name = self.expect_identifier()?; + if self.peek().kind == TokenKind::Lt { + self.advance(); // < + let mut args = Vec::new(); + loop { + args.push(self.parse_type_ref()?); + if self.peek().kind == TokenKind::Comma { + self.advance(); + } else { + break; + } + } + let end_tok = self.consume(TokenKind::Gt)?; + Ok(Node::TypeApp(TypeAppNode { + span: Span::new(self.file_id, id_tok.span.start, end_tok.span.end), + base: name, + args, + })) + } else { + Ok(Node::TypeName(TypeNameNode { + span: id_tok.span, + name, + })) + } + } + + fn parse_block(&mut self) -> Result { + let start_span = self.consume(TokenKind::OpenBrace)?.span; + let mut stmts = Vec::new(); + let mut tail = None; + + while self.peek().kind != TokenKind::CloseBrace && self.peek().kind != TokenKind::Eof { + if self.peek().kind == TokenKind::Let { + stmts.push(self.parse_let_stmt()?); + } else if self.peek().kind == TokenKind::Return { + stmts.push(self.parse_return_stmt()?); + } else { + let expr = self.parse_expr(0)?; + if self.peek().kind == TokenKind::Semicolon { + let semi_span = self.advance().span; + let expr_start = expr.span().start; + stmts.push(Node::ExprStmt(ExprStmtNode { + span: Span::new(self.file_id, expr_start, semi_span.end), + expr: Box::new(expr), + })); + } else if self.peek().kind == TokenKind::CloseBrace { + tail = Some(Box::new(expr)); + } else { + // Treat as ExprStmt even without semicolon (e.g. for if/when used as statement) + let expr_span = expr.span(); + stmts.push(Node::ExprStmt(ExprStmtNode { + span: expr_span, + expr: Box::new(expr), + })); + } + } + } + + let end_span = self.consume(TokenKind::CloseBrace)?.span; + Ok(Node::Block(BlockNode { + span: Span::new(self.file_id, start_span.start, end_span.end), + stmts, + tail, + })) + } + + fn parse_let_stmt(&mut self) -> Result { + let start_span = self.consume(TokenKind::Let)?.span; + let is_mut = if self.peek().kind == TokenKind::Mut { + self.advance(); + true + } else { + false + }; + let name = self.expect_identifier()?; + let ty = if self.peek().kind == TokenKind::Colon { + self.advance(); + Some(Box::new(self.parse_type_ref()?)) + } else { + None + }; + self.consume(TokenKind::Assign)?; + let init = self.parse_expr(0)?; + let end_span = self.consume(TokenKind::Semicolon)?.span; + + Ok(Node::LetStmt(LetStmtNode { + span: Span::new(self.file_id, start_span.start, end_span.end), + name, + is_mut, + ty, + init: Box::new(init), + })) + } + + fn parse_return_stmt(&mut self) -> Result { + let start_span = self.consume(TokenKind::Return)?.span; + let mut expr = None; + if self.peek().kind != TokenKind::Semicolon { + expr = Some(Box::new(self.parse_expr(0)?)); + } + let end_span = self.consume(TokenKind::Semicolon)?.span; + Ok(Node::ReturnStmt(ReturnStmtNode { + span: Span::new(self.file_id, start_span.start, end_span.end), + expr, + })) + } + + fn parse_expr(&mut self, min_precedence: u8) -> Result { + let mut left = self.parse_primary()?; + + loop { + let (op, precedence) = match self.get_binary_precedence() { + Some((op, p)) if p >= min_precedence => (op, p), + _ => break, + }; + + self.advance(); + let right = self.parse_expr(precedence + 1)?; + let span = Span::new(self.file_id, left.span().start, right.span().end); + left = Node::Binary(BinaryNode { + span, + op, + left: Box::new(left), + right: Box::new(right), + }); + } + + Ok(left) + } + + fn parse_primary(&mut self) -> Result { + let tok = self.peek().clone(); + match tok.kind { + TokenKind::IntLit(v) => { + self.advance(); + Ok(Node::IntLit(IntLitNode { span: tok.span, value: v })) + } + TokenKind::FloatLit(v) => { + self.advance(); + Ok(Node::FloatLit(FloatLitNode { span: tok.span, value: v })) + } + TokenKind::BoundedLit(v) => { + self.advance(); + Ok(Node::BoundedLit(BoundedLitNode { span: tok.span, value: v })) + } + TokenKind::StringLit(s) => { + self.advance(); + Ok(Node::StringLit(StringLitNode { span: tok.span, value: s })) + } + TokenKind::Identifier(name) => { + self.advance(); + let mut node = Node::Ident(IdentNode { span: tok.span, name }); + loop { + if self.peek().kind == TokenKind::OpenParen { + node = self.parse_call(node)?; + } else if self.peek().kind == TokenKind::As { + node = self.parse_cast(node)?; + } else { + break; + } + } + Ok(node) + } + TokenKind::OpenParen => { + self.advance(); + let expr = self.parse_expr(0)?; + self.consume(TokenKind::CloseParen)?; + Ok(expr) + } + TokenKind::OpenBrace => self.parse_block(), + TokenKind::If => self.parse_if_expr(), + TokenKind::When => self.parse_when_expr(), + TokenKind::Minus | TokenKind::Not => { + self.advance(); + let op = match tok.kind { + TokenKind::Minus => "-".to_string(), + TokenKind::Not => "!".to_string(), + _ => unreachable!(), + }; + let expr = self.parse_expr(11)?; + Ok(Node::Unary(UnaryNode { + span: Span::new(self.file_id, tok.span.start, expr.span().end), + op, + expr: Box::new(expr), + })) + } + _ => Err(self.error("Expected expression")), + } + } + + fn parse_call(&mut self, callee: Node) -> Result { + self.consume(TokenKind::OpenParen)?; + let mut args = Vec::new(); + while self.peek().kind != TokenKind::CloseParen && self.peek().kind != TokenKind::Eof { + args.push(self.parse_expr(0)?); + if self.peek().kind == TokenKind::Comma { + self.advance(); + } else { + break; + } + } + let end_span = self.consume(TokenKind::CloseParen)?.span; + Ok(Node::Call(CallNode { + span: Span::new(self.file_id, callee.span().start, end_span.end), + callee: Box::new(callee), + args, + })) + } + + fn parse_cast(&mut self, expr: Node) -> Result { + self.consume(TokenKind::As)?; + let ty = self.parse_type_ref()?; + Ok(Node::Cast(CastNode { + span: Span::new(self.file_id, expr.span().start, ty.span().end), + expr: Box::new(expr), + ty: Box::new(ty), + })) + } + + fn parse_if_expr(&mut self) -> Result { + let start_span = self.consume(TokenKind::If)?.span; + let cond = self.parse_expr(0)?; + let then_block = self.parse_block()?; + let mut else_block = None; + if self.peek().kind == TokenKind::Else { + self.advance(); + if self.peek().kind == TokenKind::If { + else_block = Some(Box::new(self.parse_if_expr()?)); + } else { + else_block = Some(Box::new(self.parse_block()?)); + } + } + + let end_span = else_block.as_ref().map(|b| b.span().end).unwrap_or(then_block.span().end); + + Ok(Node::IfExpr(IfExprNode { + span: Span::new(self.file_id, start_span.start, end_span), + cond: Box::new(cond), + then_block: Box::new(then_block), + else_block, + })) + } + + fn parse_when_expr(&mut self) -> Result { + let start_span = self.consume(TokenKind::When)?.span; + self.consume(TokenKind::OpenBrace)?; + let mut arms = Vec::new(); + while self.peek().kind != TokenKind::CloseBrace && self.peek().kind != TokenKind::Eof { + let arm_start = self.peek().span.start; + let cond = self.parse_expr(0)?; + self.consume(TokenKind::Arrow)?; + let body = self.parse_block()?; + arms.push(Node::WhenArm(WhenArmNode { + span: Span::new(self.file_id, arm_start, body.span().end), + cond: Box::new(cond), + body: Box::new(body), + })); + if self.peek().kind == TokenKind::Comma { + self.advance(); + } + } + let end_span = self.consume(TokenKind::CloseBrace)?.span; + Ok(Node::WhenExpr(WhenExprNode { + span: Span::new(self.file_id, start_span.start, end_span.end), + arms, + })) + } + + fn get_binary_precedence(&self) -> Option<(String, u8)> { + match self.peek().kind { + TokenKind::Plus => Some(("+".to_string(), 5)), + TokenKind::Minus => Some(("-".to_string(), 5)), + TokenKind::Star => Some(("*".to_string(), 4)), + TokenKind::Slash => Some(("/".to_string(), 4)), + TokenKind::Percent => Some(("%".to_string(), 4)), + TokenKind::Lt => Some(("<".to_string(), 7)), + TokenKind::Lte => Some(("<=".to_string(), 7)), + TokenKind::Gt => Some((">".to_string(), 7)), + TokenKind::Gte => Some((">=".to_string(), 7)), + TokenKind::Eq => Some(("==".to_string(), 8)), + TokenKind::Neq => Some(("!=".to_string(), 8)), + TokenKind::And => Some(("&&".to_string(), 9)), + TokenKind::Or => Some(("||".to_string(), 10)), + _ => None, + } + } + + fn peek(&self) -> &Token { + &self.tokens[self.pos] + } + + fn advance(&mut self) -> Token { + let tok = self.tokens[self.pos].clone(); + if tok.kind != TokenKind::Eof { + self.pos += 1; + } + tok + } + + fn consume(&mut self, kind: TokenKind) -> Result { + if self.peek().kind == kind { + Ok(self.advance()) + } else { + Err(self.error(&format!("Expected {:?}", kind))) + } + } + + fn expect_identifier(&mut self) -> Result { + if let TokenKind::Identifier(ref name) = self.peek().kind { + let name = name.clone(); + self.advance(); + Ok(name) + } else { + Err(self.error("Expected identifier")) + } + } + + fn error(&mut self, message: &str) -> DiagnosticBundle { + let diag = Diagnostic { + level: DiagnosticLevel::Error, + message: message.to_string(), + span: Some(self.peek().span), + }; + self.errors.push(diag.clone()); + DiagnosticBundle::from(diag) + } +} + +impl Node { + pub fn span(&self) -> Span { + match self { + Node::File(n) => n.span, + Node::Import(n) => n.span, + Node::ImportSpec(n) => n.span, + Node::ServiceDecl(n) => n.span, + Node::ServiceFnSig(n) => n.span, + Node::FnDecl(n) => n.span, + Node::TypeDecl(n) => n.span, + Node::TypeBody(n) => n.span, + Node::Block(n) => n.span, + Node::LetStmt(n) => n.span, + Node::ExprStmt(n) => n.span, + Node::ReturnStmt(n) => n.span, + Node::IntLit(n) => n.span, + Node::FloatLit(n) => n.span, + Node::BoundedLit(n) => n.span, + Node::StringLit(n) => n.span, + Node::Ident(n) => n.span, + Node::Call(n) => n.span, + Node::Unary(n) => n.span, + Node::Binary(n) => n.span, + Node::Cast(n) => n.span, + Node::IfExpr(n) => n.span, + Node::WhenExpr(n) => n.span, + Node::WhenArm(n) => n.span, + Node::TypeName(n) => n.span, + Node::TypeApp(n) => n.span, + } + } +} diff --git a/crates/prometeu-compiler/tests/config_integration.rs b/crates/prometeu-compiler/tests/config_integration.rs index dea779bd..096c5457 100644 --- a/crates/prometeu-compiler/tests/config_integration.rs +++ b/crates/prometeu-compiler/tests/config_integration.rs @@ -23,13 +23,13 @@ fn test_project_root_and_entry_resolution() { // Call compile let result = compiler::compile(project_dir); - // It should fail with "Frontend 'pbs' not yet implemented" + // 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 implemented"), "Unexpected error: {}", msg); + 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"), } diff --git a/crates/prometeu-compiler/tests/parser_tests.rs b/crates/prometeu-compiler/tests/parser_tests.rs new file mode 100644 index 00000000..79f80284 --- /dev/null +++ b/crates/prometeu-compiler/tests/parser_tests.rs @@ -0,0 +1,155 @@ +use prometeu_compiler::frontends::pbs::parser::Parser; +use prometeu_compiler::frontends::pbs::ast::*; +use serde_json; + +#[test] +fn test_parse_empty_file() { + let mut parser = Parser::new("", 0); + let result = parser.parse_file().unwrap(); + assert_eq!(result.imports.len(), 0); + assert_eq!(result.decls.len(), 0); +} + +#[test] +fn test_parse_imports() { + let source = r#" +import std.io from "std"; +import math from "./math.pbs"; +"#; + let mut parser = Parser::new(source, 0); + let result = parser.parse_file().unwrap(); + assert_eq!(result.imports.len(), 2); + + if let Node::Import(ref imp) = result.imports[0] { + assert_eq!(imp.from, "std"); + if let Node::ImportSpec(ref spec) = *imp.spec { + assert_eq!(spec.path, vec!["std", "io"]); + } else { panic!("Expected ImportSpec"); } + } else { panic!("Expected Import"); } +} + +#[test] +fn test_parse_fn_decl() { + let source = r#" +fn add(a: int, b: int) -> int { + return a + b; +} +"#; + let mut parser = Parser::new(source, 0); + let result = parser.parse_file().unwrap(); + assert_eq!(result.decls.len(), 1); + + if let Node::FnDecl(ref f) = result.decls[0] { + assert_eq!(f.name, "add"); + assert_eq!(f.params.len(), 2); + assert_eq!(f.params[0].name, "a"); + assert_eq!(f.params[1].name, "b"); + } else { panic!("Expected FnDecl"); } +} + +#[test] +fn test_parse_type_decl() { + let source = r#" +pub declare struct Point { + x: int, + y: int +} +"#; + let mut parser = Parser::new(source, 0); + let result = parser.parse_file().unwrap(); + assert_eq!(result.decls.len(), 1); + + if let Node::TypeDecl(ref t) = result.decls[0] { + assert_eq!(t.name, "Point"); + assert_eq!(t.type_kind, "struct"); + assert_eq!(t.vis, Some("pub".to_string())); + } else { panic!("Expected TypeDecl"); } +} + +#[test] +fn test_parse_service_decl() { + let source = r#" +pub service Audio { + fn play(sound: Sound); + fn stop() -> bool; +} +"#; + let mut parser = Parser::new(source, 0); + let result = parser.parse_file().unwrap(); + assert_eq!(result.decls.len(), 1); + + if let Node::ServiceDecl(ref s) = result.decls[0] { + assert_eq!(s.name, "Audio"); + assert_eq!(s.members.len(), 2); + } else { panic!("Expected ServiceDecl"); } +} + +#[test] +fn test_parse_expressions() { + let source = r#" +fn main() { + let x = 10 + 20 * 30; + let y = (x - 5) / 2; + foo(x, y); +} +"#; + let mut parser = Parser::new(source, 0); + let result = parser.parse_file().unwrap(); + assert_eq!(result.decls.len(), 1); +} + +#[test] +fn test_parse_if_when() { + let source = r#" +fn main(x: int) { + if x > 0 { + print("positive"); + } else { + print("non-positive"); + } + + let msg = when { + x == 0 -> { return "zero"; }, + x == 1 -> { return "one"; } + }; +} +"#; + let mut parser = Parser::new(source, 0); + let result = parser.parse_file().unwrap(); + assert_eq!(result.decls.len(), 1); +} + +#[test] +fn test_parse_error_recovery() { + let source = r#" +fn bad() { + let x = ; // Missing init + let y = 10; +} + +fn good() {} +"#; + let mut parser = Parser::new(source, 0); + let result = parser.parse_file(); + // It should fail but we should see both good and bad decls if we didn't return Err early + // Currently parse_file returns Err if there are any errors. + assert!(result.is_err()); +} + +#[test] +fn test_ast_json_snapshot() { + let source = r#" +fn main() { + return 42; +} +"#; + let mut parser = Parser::new(source, 0); + let result = parser.parse_file().unwrap(); + let json = serde_json::to_string_pretty(&Node::File(result)).unwrap(); + + // We don't assert the exact string here because spans will vary, + // but we check that it serializes correctly and has the "kind" field. + assert!(json.contains("\"kind\": \"File\"")); + assert!(json.contains("\"kind\": \"FnDecl\"")); + assert!(json.contains("\"name\": \"main\"")); +}