dev/pbs #8
230
crates/prometeu-compiler/src/frontends/pbs/ast.rs
Normal file
230
crates/prometeu-compiler/src/frontends/pbs/ast.rs
Normal file
@ -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<Node>,
|
||||||
|
pub decls: Vec<Node>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||||
|
pub struct ImportNode {
|
||||||
|
pub span: Span,
|
||||||
|
pub spec: Box<Node>, // Must be ImportSpec
|
||||||
|
pub from: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||||
|
pub struct ImportSpecNode {
|
||||||
|
pub span: Span,
|
||||||
|
pub path: Vec<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||||
|
pub struct ServiceDeclNode {
|
||||||
|
pub span: Span,
|
||||||
|
pub vis: String, // "pub" | "mod"
|
||||||
|
pub name: String,
|
||||||
|
pub extends: Option<String>,
|
||||||
|
pub members: Vec<Node>, // ServiceFnSig
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||||
|
pub struct ServiceFnSigNode {
|
||||||
|
pub span: Span,
|
||||||
|
pub name: String,
|
||||||
|
pub params: Vec<ParamNode>,
|
||||||
|
pub ret: Box<Node>, // TypeName or TypeApp
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||||
|
pub struct ParamNode {
|
||||||
|
pub span: Span,
|
||||||
|
pub name: String,
|
||||||
|
pub ty: Box<Node>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||||
|
pub struct FnDeclNode {
|
||||||
|
pub span: Span,
|
||||||
|
pub name: String,
|
||||||
|
pub params: Vec<ParamNode>,
|
||||||
|
pub ret: Option<Box<Node>>,
|
||||||
|
pub else_fallback: Option<Box<Node>>, // Block
|
||||||
|
pub body: Box<Node>, // Block
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||||
|
pub struct TypeDeclNode {
|
||||||
|
pub span: Span,
|
||||||
|
pub vis: Option<String>,
|
||||||
|
pub type_kind: String, // "struct" | "contract" | "error"
|
||||||
|
pub name: String,
|
||||||
|
pub body: Box<Node>, // TypeBody
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||||
|
pub struct TypeBodyNode {
|
||||||
|
pub span: Span,
|
||||||
|
pub members: Vec<TypeMemberNode>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||||
|
pub struct TypeMemberNode {
|
||||||
|
pub span: Span,
|
||||||
|
pub name: String,
|
||||||
|
pub ty: Box<Node>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||||
|
pub struct BlockNode {
|
||||||
|
pub span: Span,
|
||||||
|
pub stmts: Vec<Node>,
|
||||||
|
pub tail: Option<Box<Node>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||||
|
pub struct LetStmtNode {
|
||||||
|
pub span: Span,
|
||||||
|
pub name: String,
|
||||||
|
pub is_mut: bool,
|
||||||
|
pub ty: Option<Box<Node>>,
|
||||||
|
pub init: Box<Node>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||||
|
pub struct ExprStmtNode {
|
||||||
|
pub span: Span,
|
||||||
|
pub expr: Box<Node>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||||
|
pub struct ReturnStmtNode {
|
||||||
|
pub span: Span,
|
||||||
|
pub expr: Option<Box<Node>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[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<Node>,
|
||||||
|
pub args: Vec<Node>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||||
|
pub struct UnaryNode {
|
||||||
|
pub span: Span,
|
||||||
|
pub op: String,
|
||||||
|
pub expr: Box<Node>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||||
|
pub struct BinaryNode {
|
||||||
|
pub span: Span,
|
||||||
|
pub op: String,
|
||||||
|
pub left: Box<Node>,
|
||||||
|
pub right: Box<Node>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||||
|
pub struct CastNode {
|
||||||
|
pub span: Span,
|
||||||
|
pub expr: Box<Node>,
|
||||||
|
pub ty: Box<Node>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||||
|
pub struct IfExprNode {
|
||||||
|
pub span: Span,
|
||||||
|
pub cond: Box<Node>,
|
||||||
|
pub then_block: Box<Node>,
|
||||||
|
pub else_block: Option<Box<Node>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||||
|
pub struct WhenExprNode {
|
||||||
|
pub span: Span,
|
||||||
|
pub arms: Vec<Node>, // WhenArm
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||||
|
pub struct WhenArmNode {
|
||||||
|
pub span: Span,
|
||||||
|
pub cond: Box<Node>,
|
||||||
|
pub body: Box<Node>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[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<Node>,
|
||||||
|
}
|
||||||
@ -1,5 +1,7 @@
|
|||||||
pub mod token;
|
pub mod token;
|
||||||
pub mod lexer;
|
pub mod lexer;
|
||||||
|
pub mod ast;
|
||||||
|
pub mod parser;
|
||||||
|
|
||||||
pub use lexer::Lexer;
|
pub use lexer::Lexer;
|
||||||
pub use token::{Token, TokenKind};
|
pub use token::{Token, TokenKind};
|
||||||
@ -19,10 +21,18 @@ impl Frontend for PbsFrontend {
|
|||||||
|
|
||||||
fn compile_to_ir(
|
fn compile_to_ir(
|
||||||
&self,
|
&self,
|
||||||
_entry: &Path,
|
entry: &Path,
|
||||||
_file_manager: &mut FileManager,
|
file_manager: &mut FileManager,
|
||||||
) -> Result<ir::Module, DiagnosticBundle> {
|
) -> Result<ir::Module, DiagnosticBundle> {
|
||||||
|
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.
|
// 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))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
672
crates/prometeu-compiler/src/frontends/pbs/parser.rs
Normal file
672
crates/prometeu-compiler/src/frontends/pbs/parser.rs
Normal file
@ -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<Token>,
|
||||||
|
pos: usize,
|
||||||
|
file_id: usize,
|
||||||
|
errors: Vec<Diagnostic>,
|
||||||
|
}
|
||||||
|
|
||||||
|
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<FileNode, DiagnosticBundle> {
|
||||||
|
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<Node, DiagnosticBundle> {
|
||||||
|
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<Node, DiagnosticBundle> {
|
||||||
|
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<Node, DiagnosticBundle> {
|
||||||
|
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<Node, DiagnosticBundle> {
|
||||||
|
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<Node, DiagnosticBundle> {
|
||||||
|
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<Node, DiagnosticBundle> {
|
||||||
|
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<String>) -> Result<Node, DiagnosticBundle> {
|
||||||
|
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<Node, DiagnosticBundle> {
|
||||||
|
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<Node, DiagnosticBundle> {
|
||||||
|
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<Vec<ParamNode>, 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<Node, DiagnosticBundle> {
|
||||||
|
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<Node, DiagnosticBundle> {
|
||||||
|
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<Node, DiagnosticBundle> {
|
||||||
|
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<Node, DiagnosticBundle> {
|
||||||
|
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<Node, DiagnosticBundle> {
|
||||||
|
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<Node, DiagnosticBundle> {
|
||||||
|
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<Node, DiagnosticBundle> {
|
||||||
|
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<Node, DiagnosticBundle> {
|
||||||
|
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<Node, DiagnosticBundle> {
|
||||||
|
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<Node, DiagnosticBundle> {
|
||||||
|
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<Token, DiagnosticBundle> {
|
||||||
|
if self.peek().kind == kind {
|
||||||
|
Ok(self.advance())
|
||||||
|
} else {
|
||||||
|
Err(self.error(&format!("Expected {:?}", kind)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn expect_identifier(&mut self) -> Result<String, DiagnosticBundle> {
|
||||||
|
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,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -23,13 +23,13 @@ fn test_project_root_and_entry_resolution() {
|
|||||||
// Call compile
|
// Call compile
|
||||||
let result = compiler::compile(project_dir);
|
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.
|
// but ONLY after successfully loading the config and resolving the entry.
|
||||||
|
|
||||||
match result {
|
match result {
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
let msg = e.to_string();
|
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"),
|
Ok(_) => panic!("Should have failed as pbs is not implemented yet"),
|
||||||
}
|
}
|
||||||
|
|||||||
155
crates/prometeu-compiler/tests/parser_tests.rs
Normal file
155
crates/prometeu-compiler/tests/parser_tests.rs
Normal file
@ -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\""));
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user