feat: add while loops, break and continue statements
This commit is contained in:
@@ -45,29 +45,37 @@ A Rust-flavored, C-targeting language - built pipeline-first.
|
|||||||
- [x] Output System V AMD64 ABI-compliant `.o` machine code files
|
- [x] Output System V AMD64 ABI-compliant `.o` machine code files
|
||||||
- [x] End-to-end test: compile a simple `fn` → link via `gcc` → run → correct exit code
|
- [x] End-to-end test: compile a simple `fn` → link via `gcc` → run → correct exit code
|
||||||
|
|
||||||
|
## Phase 5 - The Ray Tracer Milestone (Current)
|
||||||
|
|
||||||
|
To successfully write a simple ray tracer, we need continuous math, data structures, and I/O. The following path establishes these prerequisites:
|
||||||
|
|
||||||
|
- [ ] **Floating-Point Support:** Add `f32`/`f64` types, decimal literals, and Cranelift lowering for `fadd`, `fmul`, etc.
|
||||||
|
- [ ] **FFI & Interop:** Implement `extern fn` declarations to bind C standard library functions (like `putchar` or `printf`) for `.ppm` image output.
|
||||||
|
- [ ] **Type Casting:** Add the `as` operator to convert floating-point color bounds `[0.0, 1.0]` into integer byte formats `[0, 255]`.
|
||||||
|
- [ ] **Pointers:** Add pointer types (`*T`), address-of (`&`), and dereference (`*`) operators.
|
||||||
|
- [ ] **Structs:** Add `struct` definitions, initializers, and field access (`ray.origin.x`) to represent 3D vectors, rays, and spheres.
|
||||||
|
- [ ] **Arrays:** Add fixed-size arrays (`[T; N]`) or heap allocations for the scene and framebuffers.
|
||||||
|
|
||||||
## Planned Features (Backlog)
|
## Planned Features (Backlog)
|
||||||
|
|
||||||
### Control flow
|
### Control flow & Variables
|
||||||
- [x] booleans and comparision operators
|
- [x] booleans and comparision operators
|
||||||
- [x] `if` / `else` branching
|
- [x] `if` / `else` branching
|
||||||
- [ ] `while` loops
|
- [x] `while` loops, `break`, `continue`
|
||||||
|
- [x] `let` bindings and variable assignments
|
||||||
|
|
||||||
### Types & memory
|
### Types & memory
|
||||||
- [ ] Typed pointers (`*T`)
|
|
||||||
- [ ] Opaque pointers (`*void` / `*opaque`)
|
- [ ] Opaque pointers (`*void` / `*opaque`)
|
||||||
- [ ] Raw pointer arithmetic & dereference
|
- [ ] Raw pointer arithmetic
|
||||||
- [ ] Fixed-size arrays (`[T; N]`)
|
|
||||||
- [ ] Slices (`&[T]` / `[]T`)
|
- [ ] Slices (`&[T]` / `[]T`)
|
||||||
|
|
||||||
### Composite types
|
### Composite types
|
||||||
- [ ] Structs & field access
|
|
||||||
- [ ] Enums (C-style tagged unions)
|
- [ ] Enums (C-style tagged unions)
|
||||||
- [ ] Pattern matching (`match` / `switch`)
|
- [ ] Pattern matching (`match` / `switch`)
|
||||||
|
|
||||||
### Strings & interop
|
### Strings & interop
|
||||||
- [ ] String literals & `*u8` handling
|
- [ ] String literals
|
||||||
- [ ] Variadic functions (for `printf` interop)
|
- [ ] Variadic functions (for `printf` interop)
|
||||||
- [ ] `extern` / FFI declarations
|
|
||||||
|
|
||||||
### Tooling & backend
|
### Tooling & backend
|
||||||
- [ ] Proper register allocator
|
- [ ] Proper register allocator
|
||||||
|
|||||||
@@ -107,4 +107,10 @@ run_test "tests/let_stmt.src" 30
|
|||||||
# Test assignments and multi-assignments
|
# Test assignments and multi-assignments
|
||||||
run_test "tests/assign.src" 42
|
run_test "tests/assign.src" 42
|
||||||
|
|
||||||
|
# Test while loops
|
||||||
|
run_test "tests/fibonacci.src" 55
|
||||||
|
|
||||||
|
# Test break and continue inside while loops
|
||||||
|
run_test "tests/while_break_continue.src" 16
|
||||||
|
|
||||||
echo "All end-to-end tests passed!"
|
echo "All end-to-end tests passed!"
|
||||||
@@ -98,6 +98,10 @@ pub enum StmtKind<P: Phase = Untyped> {
|
|||||||
then: Box<Stmt<P>>,
|
then: Box<Stmt<P>>,
|
||||||
elze: Option<Box<Stmt<P>>>,
|
elze: Option<Box<Stmt<P>>>,
|
||||||
},
|
},
|
||||||
|
While {
|
||||||
|
condition: Expr<P>,
|
||||||
|
body: Box<Stmt<P>>,
|
||||||
|
},
|
||||||
Return {
|
Return {
|
||||||
value: Option<Expr<P>>,
|
value: Option<Expr<P>>,
|
||||||
},
|
},
|
||||||
@@ -110,6 +114,8 @@ pub enum StmtKind<P: Phase = Untyped> {
|
|||||||
Expression {
|
Expression {
|
||||||
expr: Expr<P>,
|
expr: Expr<P>,
|
||||||
},
|
},
|
||||||
|
Break,
|
||||||
|
Continue,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Eq)]
|
#[derive(Debug, PartialEq, Eq)]
|
||||||
|
|||||||
@@ -68,6 +68,9 @@ impl<'src> Lexer<'src> {
|
|||||||
"else" => TokenKind::Else,
|
"else" => TokenKind::Else,
|
||||||
"return" => TokenKind::Return,
|
"return" => TokenKind::Return,
|
||||||
"let" => TokenKind::Let,
|
"let" => TokenKind::Let,
|
||||||
|
"while" => TokenKind::While,
|
||||||
|
"break" => TokenKind::Break,
|
||||||
|
"continue" => TokenKind::Continue,
|
||||||
|
|
||||||
"i8" => TokenKind::I8,
|
"i8" => TokenKind::I8,
|
||||||
"i16" => TokenKind::I16,
|
"i16" => TokenKind::I16,
|
||||||
@@ -206,7 +209,7 @@ mod test {
|
|||||||
#[test]
|
#[test]
|
||||||
fn identifiers() {
|
fn identifiers() {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
tokenize("HELLO _hello _0@ fn if else return let"),
|
tokenize("HELLO _hello _0@ fn if else return let while break continue"),
|
||||||
vec![
|
vec![
|
||||||
Token::new(TokenKind::Identifier, "HELLO", Span::new(0, 5)),
|
Token::new(TokenKind::Identifier, "HELLO", Span::new(0, 5)),
|
||||||
Token::new(TokenKind::Identifier, "_hello", Span::new(6, 12)),
|
Token::new(TokenKind::Identifier, "_hello", Span::new(6, 12)),
|
||||||
@@ -217,6 +220,9 @@ mod test {
|
|||||||
Token::new(TokenKind::Else, "else", Span::new(23, 27)),
|
Token::new(TokenKind::Else, "else", Span::new(23, 27)),
|
||||||
Token::new(TokenKind::Return, "return", Span::new(28, 34)),
|
Token::new(TokenKind::Return, "return", Span::new(28, 34)),
|
||||||
Token::new(TokenKind::Let, "let", Span::new(35, 38)),
|
Token::new(TokenKind::Let, "let", Span::new(35, 38)),
|
||||||
|
Token::new(TokenKind::While, "while", Span::new(39, 44)),
|
||||||
|
Token::new(TokenKind::Break, "break", Span::new(45, 50)),
|
||||||
|
Token::new(TokenKind::Continue, "continue", Span::new(51, 59)),
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -287,6 +287,10 @@ impl<'src> Parser<'src> {
|
|||||||
/// | if_stmt
|
/// | if_stmt
|
||||||
/// | return_stmt
|
/// | return_stmt
|
||||||
/// | let_stmt
|
/// | let_stmt
|
||||||
|
/// | while_stmt
|
||||||
|
/// | break_stmt
|
||||||
|
/// | continue_stmt
|
||||||
|
/// | expr_stmt
|
||||||
/// ;
|
/// ;
|
||||||
/// ```
|
/// ```
|
||||||
pub fn parse_stmt(&mut self) -> ParseResult<Stmt> {
|
pub fn parse_stmt(&mut self) -> ParseResult<Stmt> {
|
||||||
@@ -295,8 +299,11 @@ impl<'src> Parser<'src> {
|
|||||||
match peek_token.kind {
|
match peek_token.kind {
|
||||||
TokenKind::LBrace => self.parse_compound_stmt(),
|
TokenKind::LBrace => self.parse_compound_stmt(),
|
||||||
TokenKind::If => self.parse_if_stmt(),
|
TokenKind::If => self.parse_if_stmt(),
|
||||||
|
TokenKind::While => self.parse_while_stmt(),
|
||||||
TokenKind::Return => self.parse_return_stmt(),
|
TokenKind::Return => self.parse_return_stmt(),
|
||||||
TokenKind::Let => self.parse_let_stmt(),
|
TokenKind::Let => self.parse_let_stmt(),
|
||||||
|
TokenKind::Break => self.parse_break_stmt(),
|
||||||
|
TokenKind::Continue => self.parse_continue_stmt(),
|
||||||
_ => self.parse_expr_stmt(),
|
_ => self.parse_expr_stmt(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -374,6 +381,26 @@ impl<'src> Parser<'src> {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Parses a while statement.
|
||||||
|
///
|
||||||
|
/// ```ebnf
|
||||||
|
/// while_stmt = "while" expr compound_stmt ;
|
||||||
|
/// ```
|
||||||
|
fn parse_while_stmt(&mut self) -> ParseResult<Stmt> {
|
||||||
|
let while_token = self.expect(TokenKind::While)?;
|
||||||
|
let condition = self.parse_expr()?;
|
||||||
|
let body = self.parse_compound_stmt()?;
|
||||||
|
let span = while_token.span.join(body.span);
|
||||||
|
|
||||||
|
Ok(Stmt {
|
||||||
|
kind: StmtKind::While {
|
||||||
|
condition,
|
||||||
|
body: Box::new(body),
|
||||||
|
},
|
||||||
|
span,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
/// Parses an expression statement.
|
/// Parses an expression statement.
|
||||||
///
|
///
|
||||||
/// ```ebnf
|
/// ```ebnf
|
||||||
@@ -454,6 +481,38 @@ impl<'src> Parser<'src> {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Parses a break statement.
|
||||||
|
///
|
||||||
|
/// ```ebnf
|
||||||
|
/// break_stmt = "break" ";" ;
|
||||||
|
/// ```
|
||||||
|
fn parse_break_stmt(&mut self) -> ParseResult<Stmt> {
|
||||||
|
let break_token = self.expect(TokenKind::Break)?;
|
||||||
|
let semi_token = self.expect(TokenKind::Semicolon)?;
|
||||||
|
let span = break_token.span.join(semi_token.span);
|
||||||
|
|
||||||
|
Ok(Stmt {
|
||||||
|
kind: StmtKind::Break,
|
||||||
|
span,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Parses a continue statement.
|
||||||
|
///
|
||||||
|
/// ```ebnf
|
||||||
|
/// continue_stmt = "continue" ";" ;
|
||||||
|
/// ```
|
||||||
|
fn parse_continue_stmt(&mut self) -> ParseResult<Stmt> {
|
||||||
|
let continue_token = self.expect(TokenKind::Continue)?;
|
||||||
|
let semi_token = self.expect(TokenKind::Semicolon)?;
|
||||||
|
let span = continue_token.span.join(semi_token.span);
|
||||||
|
|
||||||
|
Ok(Stmt {
|
||||||
|
kind: StmtKind::Continue,
|
||||||
|
span,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
// ====== Pratt Parsing Implementation ======
|
// ====== Pratt Parsing Implementation ======
|
||||||
|
|
||||||
/// Parses an expression.
|
/// Parses an expression.
|
||||||
@@ -910,6 +969,54 @@ mod test {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn while_stmt() {
|
||||||
|
assert_eq!(
|
||||||
|
parse("while true { break; }", Parser::parse_stmt),
|
||||||
|
Success(Stmt {
|
||||||
|
kind: StmtKind::While {
|
||||||
|
condition: Expr {
|
||||||
|
kind: ExprKind::Boolean { value: true },
|
||||||
|
ty: (),
|
||||||
|
span: Span::new(6, 10)
|
||||||
|
},
|
||||||
|
body: Box::new(Stmt {
|
||||||
|
kind: StmtKind::Compound {
|
||||||
|
inner: vec![Stmt {
|
||||||
|
kind: StmtKind::Break,
|
||||||
|
span: Span::new(13, 19)
|
||||||
|
}]
|
||||||
|
},
|
||||||
|
span: Span::new(11, 21)
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
span: Span::new(0, 21)
|
||||||
|
})
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn break_stmt() {
|
||||||
|
assert_eq!(
|
||||||
|
parse("break;", Parser::parse_stmt),
|
||||||
|
Success(Stmt {
|
||||||
|
kind: StmtKind::Break,
|
||||||
|
span: Span::new(0, 6)
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn continue_stmt() {
|
||||||
|
assert_eq!(
|
||||||
|
parse("continue;", Parser::parse_stmt),
|
||||||
|
Success(Stmt {
|
||||||
|
kind: StmtKind::Continue,
|
||||||
|
span: Span::new(0, 9)
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn assign_expr_stmt() {
|
fn assign_expr_stmt() {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
|
|||||||
@@ -78,6 +78,7 @@ pub struct Sema {
|
|||||||
deferred_unary_neg: Vec<(Span, Ty, Ty, Option<u64>)>,
|
deferred_unary_neg: Vec<(Span, Ty, Ty, Option<u64>)>,
|
||||||
deferred_binary: Vec<(Span, Ty)>,
|
deferred_binary: Vec<(Span, Ty)>,
|
||||||
deferred_literals: Vec<(Span, Ty)>,
|
deferred_literals: Vec<(Span, Ty)>,
|
||||||
|
loop_depth: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Sema {
|
impl Sema {
|
||||||
@@ -91,6 +92,7 @@ impl Sema {
|
|||||||
deferred_unary_neg: Vec::new(),
|
deferred_unary_neg: Vec::new(),
|
||||||
deferred_binary: Vec::new(),
|
deferred_binary: Vec::new(),
|
||||||
deferred_literals: Vec::new(),
|
deferred_literals: Vec::new(),
|
||||||
|
loop_depth: 0,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -323,6 +325,47 @@ impl Sema {
|
|||||||
span: stmt.span,
|
span: stmt.span,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
StmtKind::While { condition, body } => {
|
||||||
|
let typed_condition = self.analyze_expr(condition);
|
||||||
|
|
||||||
|
if let Err(err) = self.unify(&typed_condition.ty, &Ty::Bool) {
|
||||||
|
self.errors.push(SemanticError::new(err, condition.span));
|
||||||
|
}
|
||||||
|
|
||||||
|
self.loop_depth += 1;
|
||||||
|
let typed_body = self.analyze_stmt(body, expected_ret_ty);
|
||||||
|
self.loop_depth -= 1;
|
||||||
|
|
||||||
|
TypedStmt {
|
||||||
|
kind: TypedStmtKind::While {
|
||||||
|
condition: typed_condition,
|
||||||
|
body: Box::new(typed_body),
|
||||||
|
},
|
||||||
|
span: stmt.span,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
StmtKind::Break => {
|
||||||
|
if self.loop_depth == 0 {
|
||||||
|
self.errors
|
||||||
|
.push(SemanticError::new("`break` outside of a loop", stmt.span));
|
||||||
|
}
|
||||||
|
TypedStmt {
|
||||||
|
kind: TypedStmtKind::Break,
|
||||||
|
span: stmt.span,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
StmtKind::Continue => {
|
||||||
|
if self.loop_depth == 0 {
|
||||||
|
self.errors.push(SemanticError::new(
|
||||||
|
"`continue` outside of a loop",
|
||||||
|
stmt.span,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
TypedStmt {
|
||||||
|
kind: TypedStmtKind::Continue,
|
||||||
|
span: stmt.span,
|
||||||
|
}
|
||||||
|
}
|
||||||
StmtKind::Return { value } => {
|
StmtKind::Return { value } => {
|
||||||
if let Some(expr) = value {
|
if let Some(expr) = value {
|
||||||
let typed_expr = self.analyze_expr(expr);
|
let typed_expr = self.analyze_expr(expr);
|
||||||
@@ -604,6 +647,14 @@ impl Sema {
|
|||||||
elze: elze.map(|s| Box::new(self.apply_subst_stmt(*s))),
|
elze: elze.map(|s| Box::new(self.apply_subst_stmt(*s))),
|
||||||
},
|
},
|
||||||
|
|
||||||
|
TypedStmtKind::While { condition, body } => TypedStmtKind::While {
|
||||||
|
condition: self.apply_subst_expr(condition),
|
||||||
|
body: Box::new(self.apply_subst_stmt(*body)),
|
||||||
|
},
|
||||||
|
|
||||||
|
TypedStmtKind::Break => TypedStmtKind::Break,
|
||||||
|
TypedStmtKind::Continue => TypedStmtKind::Continue,
|
||||||
|
|
||||||
TypedStmtKind::Return { value } => TypedStmtKind::Return {
|
TypedStmtKind::Return { value } => TypedStmtKind::Return {
|
||||||
value: value.map(|e| self.apply_subst_expr(e)),
|
value: value.map(|e| self.apply_subst_expr(e)),
|
||||||
},
|
},
|
||||||
@@ -933,4 +984,21 @@ mod test {
|
|||||||
.any(|e| e.message.contains("invalid left-hand side of assignment"))
|
.any(|e| e.message.contains("invalid left-hand side of assignment"))
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn valid_while() {
|
||||||
|
let src = "fn test() { while true { break; continue; } }";
|
||||||
|
assert!(analyze(src).is_ok());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn invalid_break() {
|
||||||
|
let src = "fn test() { break; }";
|
||||||
|
let errors = analyze(src).unwrap_err();
|
||||||
|
assert!(
|
||||||
|
errors
|
||||||
|
.iter()
|
||||||
|
.any(|e| e.message.contains("`break` outside of a loop"))
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -60,6 +60,9 @@ pub enum TokenKind {
|
|||||||
Else,
|
Else,
|
||||||
Return,
|
Return,
|
||||||
Let,
|
Let,
|
||||||
|
While,
|
||||||
|
Break,
|
||||||
|
Continue,
|
||||||
|
|
||||||
// Types
|
// Types
|
||||||
I8,
|
I8,
|
||||||
@@ -117,6 +120,9 @@ impl Display for TokenKind {
|
|||||||
TokenKind::Else => "`else`",
|
TokenKind::Else => "`else`",
|
||||||
TokenKind::Return => "`return`",
|
TokenKind::Return => "`return`",
|
||||||
TokenKind::Let => "`let`",
|
TokenKind::Let => "`let`",
|
||||||
|
TokenKind::While => "`while`",
|
||||||
|
TokenKind::Break => "`break`",
|
||||||
|
TokenKind::Continue => "`continue`",
|
||||||
TokenKind::I8 => "`i8`",
|
TokenKind::I8 => "`i8`",
|
||||||
TokenKind::I16 => "`i16`",
|
TokenKind::I16 => "`i16`",
|
||||||
TokenKind::I32 => "`i32`",
|
TokenKind::I32 => "`i32`",
|
||||||
|
|||||||
@@ -85,6 +85,9 @@ struct FuncBuilder {
|
|||||||
|
|
||||||
/// Scoped mapping from user-defined variable names to their corresponding `LocalId`.
|
/// Scoped mapping from user-defined variable names to their corresponding `LocalId`.
|
||||||
scopes: Vec<HashMap<String, LocalId>>,
|
scopes: Vec<HashMap<String, LocalId>>,
|
||||||
|
|
||||||
|
/// Stack of `(continue_target, break_target)` for nested loops
|
||||||
|
loop_stack: Vec<(BlockId, BlockId)>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FuncBuilder {
|
impl FuncBuilder {
|
||||||
@@ -100,6 +103,7 @@ impl FuncBuilder {
|
|||||||
current_statements: Vec::new(),
|
current_statements: Vec::new(),
|
||||||
next_block_id: 0,
|
next_block_id: 0,
|
||||||
scopes: vec![HashMap::new()],
|
scopes: vec![HashMap::new()],
|
||||||
|
loop_stack: Vec::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -249,6 +253,57 @@ impl FuncBuilder {
|
|||||||
|
|
||||||
self.switch_to_block(merge_block);
|
self.switch_to_block(merge_block);
|
||||||
}
|
}
|
||||||
|
TypedStmtKind::While { condition, body } => {
|
||||||
|
let cond_block = self.new_block();
|
||||||
|
let body_block = self.new_block();
|
||||||
|
let merge_block = self.new_block();
|
||||||
|
|
||||||
|
self.terminate(Terminator {
|
||||||
|
kind: TerminatorKind::Goto { target: cond_block },
|
||||||
|
span: stmt.span,
|
||||||
|
});
|
||||||
|
|
||||||
|
self.switch_to_block(cond_block);
|
||||||
|
let cond_op = self.lower_expr(condition);
|
||||||
|
self.terminate(Terminator {
|
||||||
|
kind: TerminatorKind::CondBranch {
|
||||||
|
cond: cond_op,
|
||||||
|
target_true: body_block,
|
||||||
|
target_false: merge_block,
|
||||||
|
},
|
||||||
|
span: condition.span,
|
||||||
|
});
|
||||||
|
|
||||||
|
self.switch_to_block(body_block);
|
||||||
|
self.loop_stack.push((cond_block, merge_block));
|
||||||
|
self.lower_stmt(body);
|
||||||
|
self.loop_stack.pop();
|
||||||
|
|
||||||
|
self.terminate(Terminator {
|
||||||
|
kind: TerminatorKind::Goto { target: cond_block },
|
||||||
|
span: body.span,
|
||||||
|
});
|
||||||
|
|
||||||
|
self.switch_to_block(merge_block);
|
||||||
|
}
|
||||||
|
TypedStmtKind::Break => {
|
||||||
|
if let Some(&(_, merge_block)) = self.loop_stack.last() {
|
||||||
|
self.terminate(Terminator {
|
||||||
|
kind: TerminatorKind::Goto {
|
||||||
|
target: merge_block,
|
||||||
|
},
|
||||||
|
span: stmt.span,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
TypedStmtKind::Continue => {
|
||||||
|
if let Some(&(cond_block, _)) = self.loop_stack.last() {
|
||||||
|
self.terminate(Terminator {
|
||||||
|
kind: TerminatorKind::Goto { target: cond_block },
|
||||||
|
span: stmt.span,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
TypedStmtKind::Return { value } => {
|
TypedStmtKind::Return { value } => {
|
||||||
let val_op = value.as_ref().map(|v| self.lower_expr(v));
|
let val_op = value.as_ref().map(|v| self.lower_expr(v));
|
||||||
self.terminate(Terminator {
|
self.terminate(Terminator {
|
||||||
@@ -330,3 +385,94 @@ impl FuncBuilder {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test {
|
||||||
|
use super::*;
|
||||||
|
use crate::frontend::parser::Parser;
|
||||||
|
use crate::frontend::sema::Sema;
|
||||||
|
|
||||||
|
/// Helper function to parse, analyze, and build a MIR module from source code.
|
||||||
|
fn build_mir(source: &str) -> MirModule {
|
||||||
|
let mut parser = Parser::new(source);
|
||||||
|
let module = parser.parse_module();
|
||||||
|
if let Some(errors) = parser.errors() {
|
||||||
|
panic!("Parse errors: {:?}", errors);
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut sema = Sema::new();
|
||||||
|
let typed_module = sema.analyze_module(&module);
|
||||||
|
if let Some(errors) = sema.errors() {
|
||||||
|
panic!("Semantic errors: {:?}", errors);
|
||||||
|
}
|
||||||
|
|
||||||
|
MirBuilder::build(&typed_module)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_lower_while_loop() {
|
||||||
|
let mir = build_mir("fn main() { while true { } }");
|
||||||
|
let func = &mir.functions[0];
|
||||||
|
|
||||||
|
// Ensure exactly 4 basic blocks are generated
|
||||||
|
assert_eq!(func.blocks.len(), 4);
|
||||||
|
|
||||||
|
// Block 0: Entry -> Goto(1)
|
||||||
|
assert!(matches!(
|
||||||
|
func.blocks[0].terminator.kind,
|
||||||
|
TerminatorKind::Goto { target: BlockId(1) }
|
||||||
|
));
|
||||||
|
// Block 1: Cond -> CondBranch(true -> 2, false -> 3)
|
||||||
|
assert!(matches!(
|
||||||
|
func.blocks[1].terminator.kind,
|
||||||
|
TerminatorKind::CondBranch {
|
||||||
|
target_true: BlockId(2),
|
||||||
|
target_false: BlockId(3),
|
||||||
|
..
|
||||||
|
}
|
||||||
|
));
|
||||||
|
// Block 2: Body -> Goto(1)
|
||||||
|
assert!(matches!(
|
||||||
|
func.blocks[2].terminator.kind,
|
||||||
|
TerminatorKind::Goto { target: BlockId(1) }
|
||||||
|
));
|
||||||
|
// Block 3: Merge -> Return
|
||||||
|
assert!(matches!(
|
||||||
|
func.blocks[3].terminator.kind,
|
||||||
|
TerminatorKind::Return { value: None }
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_lower_break_and_continue() {
|
||||||
|
let mir = build_mir("fn main() { while true { continue; break; } }");
|
||||||
|
let func = &mir.functions[0];
|
||||||
|
|
||||||
|
// The body block (BlockId(2)) hits `continue` first, meaning it terminates immediately with a Goto back to the condition block.
|
||||||
|
// The trailing `break` is correctly skipped by the builder as automatic dead code!
|
||||||
|
assert!(matches!(
|
||||||
|
func.blocks[2].terminator.kind,
|
||||||
|
TerminatorKind::Goto { target: BlockId(1) }
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_lower_let_and_assign() {
|
||||||
|
let mir = build_mir("fn main() { let a = 5; a = 10; }");
|
||||||
|
let func = &mir.functions[0];
|
||||||
|
|
||||||
|
assert_eq!(func.locals.len(), 1); // Only 1 variable 'a' (No temporaries generated for literals)
|
||||||
|
assert_eq!(func.blocks.len(), 1); // No branches, so 1 block
|
||||||
|
|
||||||
|
let block = &func.blocks[0];
|
||||||
|
assert_eq!(block.statements.len(), 2); // Includes both the let initialization and the later assignment
|
||||||
|
assert!(matches!(
|
||||||
|
block.statements[0].kind,
|
||||||
|
StatementKind::Assign(LocalId(0), _)
|
||||||
|
));
|
||||||
|
assert!(matches!(
|
||||||
|
block.statements[1].kind,
|
||||||
|
StatementKind::Assign(LocalId(0), _)
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -0,0 +1,15 @@
|
|||||||
|
fn main() -> i32 {
|
||||||
|
let n = 10;
|
||||||
|
let a = 0;
|
||||||
|
let b = 1;
|
||||||
|
let i = 0;
|
||||||
|
|
||||||
|
while i < n {
|
||||||
|
let temp = a + b;
|
||||||
|
a = b;
|
||||||
|
b = temp;
|
||||||
|
i = i + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return a;
|
||||||
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
fn main() -> i32 {
|
||||||
|
let i = 0;
|
||||||
|
let sum = 0;
|
||||||
|
|
||||||
|
while i < 10 {
|
||||||
|
i = i + 1;
|
||||||
|
|
||||||
|
if i % 2 == 0 {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if i > 7 {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
sum = sum + i;
|
||||||
|
}
|
||||||
|
|
||||||
|
return sum;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user