Add function/struct definition parsing and program entry point
- ast.rs: Param, FieldDef, FuncDef, StructDef, TopLevelDef, TopLevelDefKind, Program - parser.rs: parse_program, parse_top_level_def, parse_func_def, parse_struct_def with param/field list helpers; synchronize_top_level for recovery; 14 new tests (76 total) - main.rs: parse source file as a Program and print the AST
This commit is contained in:
@@ -201,3 +201,64 @@ pub enum StmtKind {
|
|||||||
/// Error placeholder — emitted during recovery so the parent can continue.
|
/// Error placeholder — emitted during recovery so the parent can continue.
|
||||||
Error,
|
Error,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ── Top-level definitions ──────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
/// A function parameter: `[mut] name : type`.
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct Param {
|
||||||
|
pub mutable: bool,
|
||||||
|
pub name: String,
|
||||||
|
pub name_span: Span,
|
||||||
|
pub ty: Type,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A struct definition field: `name : type`.
|
||||||
|
///
|
||||||
|
/// Named `FieldDef` to distinguish from `StructField`, which is a
|
||||||
|
/// field in a struct *literal expression*.
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct FieldDef {
|
||||||
|
pub name: String,
|
||||||
|
pub name_span: Span,
|
||||||
|
pub ty: Type,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// `fn name ( params ) [ -> type ] block`
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct FuncDef {
|
||||||
|
pub name: String,
|
||||||
|
pub name_span: Span,
|
||||||
|
pub params: Vec<Param>,
|
||||||
|
pub ret_ty: Option<Type>,
|
||||||
|
pub body: Block,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// `struct name { fields }`
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct StructDef {
|
||||||
|
pub name: String,
|
||||||
|
pub name_span: Span,
|
||||||
|
pub fields: Vec<FieldDef>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct TopLevelDef {
|
||||||
|
pub kind: TopLevelDefKind,
|
||||||
|
pub span: Span,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub enum TopLevelDefKind {
|
||||||
|
Func(FuncDef),
|
||||||
|
Struct(StructDef),
|
||||||
|
/// Error placeholder for recovery.
|
||||||
|
Error,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The root of the AST — a sequence of top-level definitions.
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct Program {
|
||||||
|
pub defs: Vec<TopLevelDef>,
|
||||||
|
pub span: Span,
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
use std::io::{self, BufRead, Write};
|
use std::{env::args, fs};
|
||||||
|
|
||||||
use crate::parser::Parser;
|
use crate::parser::Parser;
|
||||||
|
|
||||||
@@ -8,39 +8,18 @@ pub mod parser;
|
|||||||
pub mod token;
|
pub mod token;
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
let stdin = io::stdin();
|
let path = args().nth(1).expect("usage: fluxc <file>");
|
||||||
let stdout = io::stdout();
|
let content = fs::read_to_string(&path).unwrap_or_else(|e| {
|
||||||
|
eprintln!("error: {e}");
|
||||||
|
std::process::exit(1)
|
||||||
|
});
|
||||||
|
|
||||||
println!("flux expression REPL (ctrl+d to exit)");
|
let mut parser = Parser::new(&content);
|
||||||
|
let program = parser.parse_program();
|
||||||
|
|
||||||
loop {
|
for err in &parser.errors {
|
||||||
print!("> ");
|
eprintln!("parse error: {err}");
|
||||||
stdout.lock().flush().unwrap();
|
|
||||||
|
|
||||||
let mut line = String::new();
|
|
||||||
match stdin.lock().read_line(&mut line) {
|
|
||||||
Ok(0) => break, // EOF
|
|
||||||
Ok(_) => {}
|
|
||||||
Err(e) => {
|
|
||||||
eprintln!("error: {e}");
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let src = line.trim();
|
|
||||||
if src.is_empty() {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut parser = Parser::new(src);
|
|
||||||
let node = parser.parse_stmt();
|
|
||||||
|
|
||||||
for err in &parser.errors {
|
|
||||||
eprintln!("parse error: {err}");
|
|
||||||
}
|
|
||||||
|
|
||||||
if parser.errors.is_empty() {
|
|
||||||
println!("{node:#?}");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
println!("{program:#?}");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,8 @@ use std::fmt;
|
|||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
ast::{
|
ast::{
|
||||||
BinaryOp, Block, ElseBranch, Expr, ExprKind, Stmt, StmtKind, StructField, Type, UnaryOp,
|
BinaryOp, Block, ElseBranch, Expr, ExprKind, FieldDef, FuncDef, Param, Program, Stmt,
|
||||||
|
StmtKind, StructDef, StructField, TopLevelDef, TopLevelDefKind, Type, UnaryOp,
|
||||||
},
|
},
|
||||||
lexer::Lexer,
|
lexer::Lexer,
|
||||||
token::{Span, Token, TokenKind},
|
token::{Span, Token, TokenKind},
|
||||||
@@ -657,6 +658,158 @@ impl<'src> Parser<'src> {
|
|||||||
|
|
||||||
// ── Argument list ─────────────────────────────────────────────────────────
|
// ── Argument list ─────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
// ── Top-level definitions ─────────────────────────────────────────────────
|
||||||
|
|
||||||
|
/// Parse an entire source file as a `Program`.
|
||||||
|
pub fn parse_program(&mut self) -> Program {
|
||||||
|
let start = self.current().span;
|
||||||
|
let mut defs = Vec::new();
|
||||||
|
loop {
|
||||||
|
if self.current().kind == TokenKind::Eof {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
defs.push(self.parse_top_level_def());
|
||||||
|
}
|
||||||
|
let span = start.cover(self.current().span);
|
||||||
|
Program { defs, span }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Parse one top-level definition (`fn` or `struct`).
|
||||||
|
pub fn parse_top_level_def(&mut self) -> TopLevelDef {
|
||||||
|
let tok = self.current();
|
||||||
|
match tok.kind {
|
||||||
|
TokenKind::Fn => self.parse_func_def(),
|
||||||
|
TokenKind::Struct => self.parse_struct_def(),
|
||||||
|
_ => {
|
||||||
|
self.errors.push(ParseError {
|
||||||
|
span: tok.span,
|
||||||
|
message: format!("expected `fn` or `struct`, found {}", tok.kind),
|
||||||
|
});
|
||||||
|
self.synchronize_top_level();
|
||||||
|
TopLevelDef {
|
||||||
|
kind: TopLevelDefKind::Error,
|
||||||
|
span: tok.span,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Skip tokens until the next top-level boundary (`fn`, `struct`, or EOF).
|
||||||
|
fn synchronize_top_level(&mut self) {
|
||||||
|
loop {
|
||||||
|
match self.current().kind {
|
||||||
|
TokenKind::Eof | TokenKind::Fn | TokenKind::Struct => break,
|
||||||
|
_ => {
|
||||||
|
self.advance();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_func_def(&mut self) -> TopLevelDef {
|
||||||
|
let kw = self.advance(); // consume `fn`
|
||||||
|
let name_tok = self.expect(TokenKind::Ident);
|
||||||
|
self.expect(TokenKind::LParen);
|
||||||
|
let params = self.parse_param_list();
|
||||||
|
self.expect(TokenKind::RParen);
|
||||||
|
let ret_ty = if self.current().kind == TokenKind::Arrow {
|
||||||
|
self.advance();
|
||||||
|
Some(self.parse_type())
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
let body = self.parse_block();
|
||||||
|
let span = kw.span.cover(body.span);
|
||||||
|
TopLevelDef {
|
||||||
|
kind: TopLevelDefKind::Func(FuncDef {
|
||||||
|
name: name_tok.text.to_owned(),
|
||||||
|
name_span: name_tok.span,
|
||||||
|
params,
|
||||||
|
ret_ty,
|
||||||
|
body,
|
||||||
|
}),
|
||||||
|
span,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_param_list(&mut self) -> Vec<Param> {
|
||||||
|
let mut params = Vec::new();
|
||||||
|
loop {
|
||||||
|
if matches!(self.current().kind, TokenKind::RParen | TokenKind::Eof) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
params.push(self.parse_param());
|
||||||
|
if self.current().kind == TokenKind::Comma {
|
||||||
|
self.advance();
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
params
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_param(&mut self) -> Param {
|
||||||
|
let mutable = if self.current().kind == TokenKind::Mut {
|
||||||
|
self.advance();
|
||||||
|
true
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
};
|
||||||
|
let name_tok = self.expect(TokenKind::Ident);
|
||||||
|
self.expect(TokenKind::Colon);
|
||||||
|
let ty = self.parse_type();
|
||||||
|
Param {
|
||||||
|
mutable,
|
||||||
|
name: name_tok.text.to_owned(),
|
||||||
|
name_span: name_tok.span,
|
||||||
|
ty,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_struct_def(&mut self) -> TopLevelDef {
|
||||||
|
let kw = self.advance(); // consume `struct`
|
||||||
|
let name_tok = self.expect(TokenKind::Ident);
|
||||||
|
self.expect(TokenKind::LCurly);
|
||||||
|
let fields = self.parse_field_def_list();
|
||||||
|
let close = self.expect(TokenKind::RCurly);
|
||||||
|
let span = kw.span.cover(close.span);
|
||||||
|
TopLevelDef {
|
||||||
|
kind: TopLevelDefKind::Struct(StructDef {
|
||||||
|
name: name_tok.text.to_owned(),
|
||||||
|
name_span: name_tok.span,
|
||||||
|
fields,
|
||||||
|
}),
|
||||||
|
span,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_field_def_list(&mut self) -> Vec<FieldDef> {
|
||||||
|
let mut fields = Vec::new();
|
||||||
|
loop {
|
||||||
|
if matches!(self.current().kind, TokenKind::RCurly | TokenKind::Eof) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
fields.push(self.parse_field_def());
|
||||||
|
if self.current().kind == TokenKind::Comma {
|
||||||
|
self.advance();
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fields
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_field_def(&mut self) -> FieldDef {
|
||||||
|
let name_tok = self.expect(TokenKind::Ident);
|
||||||
|
self.expect(TokenKind::Colon);
|
||||||
|
let ty = self.parse_type();
|
||||||
|
FieldDef {
|
||||||
|
name: name_tok.text.to_owned(),
|
||||||
|
name_span: name_tok.span,
|
||||||
|
ty,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Parse `arg, arg, …` up to `)`. The opening `(` has already been
|
/// Parse `arg, arg, …` up to `)`. The opening `(` has already been
|
||||||
/// consumed by `parse_led`. Returns `(args, close_span)`.
|
/// consumed by `parse_led`. Returns `(args, close_span)`.
|
||||||
fn parse_arg_list(&mut self) -> (Vec<Expr>, Span) {
|
fn parse_arg_list(&mut self) -> (Vec<Expr>, Span) {
|
||||||
@@ -683,7 +836,7 @@ impl<'src> Parser<'src> {
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::ast::{ElseBranch, ExprKind, StmtKind, Type};
|
use crate::ast::{ElseBranch, ExprKind, StmtKind, TopLevelDefKind, Type};
|
||||||
|
|
||||||
// ── Expression test helpers ───────────────────────────────────────────────
|
// ── Expression test helpers ───────────────────────────────────────────────
|
||||||
|
|
||||||
@@ -1165,4 +1318,169 @@ mod tests {
|
|||||||
let s = p.parse_stmt();
|
let s = p.parse_stmt();
|
||||||
assert!(matches!(s.kind, StmtKind::If { .. }));
|
assert!(matches!(s.kind, StmtKind::If { .. }));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ── Function definition tests ─────────────────────────────────────────────
|
||||||
|
|
||||||
|
fn top(src: &str) -> TopLevelDef {
|
||||||
|
Parser::new(src).parse_top_level_def()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn func_def_empty() {
|
||||||
|
let d = top("fn foo() { }");
|
||||||
|
match &d.kind {
|
||||||
|
TopLevelDefKind::Func(f) => {
|
||||||
|
assert_eq!(f.name, "foo");
|
||||||
|
assert!(f.params.is_empty());
|
||||||
|
assert!(f.ret_ty.is_none());
|
||||||
|
}
|
||||||
|
_ => panic!("expected func def"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn func_def_with_return_type() {
|
||||||
|
let d = top("fn answer() -> i32 { return 42; }");
|
||||||
|
match &d.kind {
|
||||||
|
TopLevelDefKind::Func(f) => {
|
||||||
|
assert!(matches!(f.ret_ty, Some(Type::I32)));
|
||||||
|
}
|
||||||
|
_ => panic!("expected func def"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn func_def_params() {
|
||||||
|
let d = top("fn add(a: i32, b: i32) -> i32 { return a + b; }");
|
||||||
|
match &d.kind {
|
||||||
|
TopLevelDefKind::Func(f) => {
|
||||||
|
assert_eq!(f.params.len(), 2);
|
||||||
|
assert_eq!(f.params[0].name, "a");
|
||||||
|
assert!(!f.params[0].mutable);
|
||||||
|
assert!(matches!(f.params[0].ty, Type::I32));
|
||||||
|
assert_eq!(f.params[1].name, "b");
|
||||||
|
}
|
||||||
|
_ => panic!("expected func def"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn func_def_mut_param() {
|
||||||
|
let d = top("fn inc(mut n: i32) -> i32 { n = n + 1; return n; }");
|
||||||
|
match &d.kind {
|
||||||
|
TopLevelDefKind::Func(f) => {
|
||||||
|
assert!(f.params[0].mutable);
|
||||||
|
}
|
||||||
|
_ => panic!("expected func def"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn func_def_pointer_param() {
|
||||||
|
let d = top("fn foo(p: *i32) { }");
|
||||||
|
match &d.kind {
|
||||||
|
TopLevelDefKind::Func(f) => {
|
||||||
|
assert!(matches!(f.params[0].ty, Type::Pointer(_)));
|
||||||
|
}
|
||||||
|
_ => panic!("expected func def"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Struct definition tests ───────────────────────────────────────────────
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn struct_def_empty() {
|
||||||
|
let d = top("struct Empty { }");
|
||||||
|
match &d.kind {
|
||||||
|
TopLevelDefKind::Struct(s) => {
|
||||||
|
assert_eq!(s.name, "Empty");
|
||||||
|
assert!(s.fields.is_empty());
|
||||||
|
}
|
||||||
|
_ => panic!("expected struct def"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn struct_def_with_fields() {
|
||||||
|
let d = top("struct Point { x: f64, y: f64 }");
|
||||||
|
match &d.kind {
|
||||||
|
TopLevelDefKind::Struct(s) => {
|
||||||
|
assert_eq!(s.name, "Point");
|
||||||
|
assert_eq!(s.fields.len(), 2);
|
||||||
|
assert_eq!(s.fields[0].name, "x");
|
||||||
|
assert!(matches!(s.fields[0].ty, Type::F64));
|
||||||
|
assert_eq!(s.fields[1].name, "y");
|
||||||
|
}
|
||||||
|
_ => panic!("expected struct def"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn struct_def_named_field_type() {
|
||||||
|
let d = top("struct Node { value: i32, next: *Node }");
|
||||||
|
match &d.kind {
|
||||||
|
TopLevelDefKind::Struct(s) => {
|
||||||
|
assert!(matches!(s.fields[1].ty, Type::Pointer(_)));
|
||||||
|
}
|
||||||
|
_ => panic!("expected struct def"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Program tests ─────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
fn program(src: &str) -> Program {
|
||||||
|
Parser::new(src).parse_program()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn program_empty() {
|
||||||
|
let p = program("");
|
||||||
|
assert!(p.defs.is_empty());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn program_single_func() {
|
||||||
|
let p = program("fn main() { }");
|
||||||
|
assert_eq!(p.defs.len(), 1);
|
||||||
|
assert!(matches!(p.defs[0].kind, TopLevelDefKind::Func(_)));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn program_struct_and_func() {
|
||||||
|
let src = "struct Point { x: f64, y: f64 } fn main() { }";
|
||||||
|
let p = program(src);
|
||||||
|
assert_eq!(p.defs.len(), 2);
|
||||||
|
assert!(matches!(p.defs[0].kind, TopLevelDefKind::Struct(_)));
|
||||||
|
assert!(matches!(p.defs[1].kind, TopLevelDefKind::Func(_)));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn program_multiple_funcs() {
|
||||||
|
let src = "fn foo() { } fn bar() -> i32 { return 1; } fn baz(x: bool) { }";
|
||||||
|
let p = program(src);
|
||||||
|
assert_eq!(p.defs.len(), 3);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Top-level recovery tests ──────────────────────────────────────────────
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn top_level_stray_token_synchronizes() {
|
||||||
|
// A stray token should produce Error and then the next definition
|
||||||
|
// should still parse correctly.
|
||||||
|
let mut p = Parser::new("42 fn foo() { }");
|
||||||
|
let d1 = p.parse_top_level_def();
|
||||||
|
let d2 = p.parse_top_level_def();
|
||||||
|
assert!(matches!(d1.kind, TopLevelDefKind::Error));
|
||||||
|
assert!(matches!(d2.kind, TopLevelDefKind::Func(_)));
|
||||||
|
assert!(!p.errors.is_empty());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn top_level_missing_func_name_inserts_dummy() {
|
||||||
|
// `fn () { }` — missing name; expect() inserts a dummy, no panic.
|
||||||
|
let mut p = Parser::new("fn () { }");
|
||||||
|
let d = p.parse_top_level_def();
|
||||||
|
assert!(!p.errors.is_empty());
|
||||||
|
assert!(matches!(d.kind, TopLevelDefKind::Func(_)));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user