loops
This commit is contained in:
108
libs/compiler/src/test/loops.rs
Normal file
108
libs/compiler/src/test/loops.rs
Normal file
@@ -0,0 +1,108 @@
|
||||
use crate::compile;
|
||||
use indoc::indoc;
|
||||
use pretty_assertions::assert_eq;
|
||||
|
||||
#[test]
|
||||
fn test_infinite_loop() -> anyhow::Result<()> {
|
||||
let compiled = compile! {
|
||||
debug
|
||||
"
|
||||
let a = 0;
|
||||
loop {
|
||||
a = a + 1;
|
||||
}
|
||||
"
|
||||
};
|
||||
|
||||
// Labels: L1 (start), L2 (end)
|
||||
assert_eq!(
|
||||
compiled,
|
||||
indoc! {
|
||||
"
|
||||
j main
|
||||
main:
|
||||
move r8 0 #a
|
||||
L1:
|
||||
add r1 r8 1
|
||||
move r8 r1 #a
|
||||
j L1
|
||||
L2:
|
||||
"
|
||||
}
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_loop_break() -> anyhow::Result<()> {
|
||||
let compiled = compile! {
|
||||
debug
|
||||
"
|
||||
let a = 0;
|
||||
loop {
|
||||
a = a + 1;
|
||||
if (a > 10) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
"
|
||||
};
|
||||
|
||||
// Labels: L1 (start), L2 (end), L3 (if end - implicit else label)
|
||||
assert_eq!(
|
||||
compiled,
|
||||
indoc! {
|
||||
"
|
||||
j main
|
||||
main:
|
||||
move r8 0 #a
|
||||
L1:
|
||||
add r1 r8 1
|
||||
move r8 r1 #a
|
||||
sgt r2 r8 10
|
||||
beq r2 0 L3
|
||||
j L2
|
||||
L3:
|
||||
j L1
|
||||
L2:
|
||||
"
|
||||
}
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_while_loop() -> anyhow::Result<()> {
|
||||
let compiled = compile! {
|
||||
debug
|
||||
"
|
||||
let a = 0;
|
||||
while (a < 10) {
|
||||
a = a + 1;
|
||||
}
|
||||
"
|
||||
};
|
||||
|
||||
// Labels: L1 (start), L2 (end)
|
||||
assert_eq!(
|
||||
compiled,
|
||||
indoc! {
|
||||
"
|
||||
j main
|
||||
main:
|
||||
move r8 0 #a
|
||||
L1:
|
||||
slt r1 r8 10
|
||||
beq r1 0 L2
|
||||
add r2 r8 1
|
||||
move r8 r2 #a
|
||||
j L1
|
||||
L2:
|
||||
"
|
||||
}
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -46,3 +46,4 @@ mod declaration_function_invocation;
|
||||
mod declaration_literal;
|
||||
mod function_declaration;
|
||||
mod logic_expression;
|
||||
mod loops;
|
||||
|
||||
@@ -4,7 +4,7 @@ use parser::{
|
||||
tree_node::{
|
||||
AssignmentExpression, BinaryExpression, BlockExpression, DeviceDeclarationExpression,
|
||||
Expression, FunctionExpression, IfExpression, InvocationExpression, Literal,
|
||||
LogicalExpression,
|
||||
LogicalExpression, LoopExpression, WhileExpression,
|
||||
},
|
||||
};
|
||||
use quick_error::quick_error;
|
||||
@@ -77,6 +77,7 @@ pub struct Compiler<'a, W: std::io::Write> {
|
||||
config: CompilerConfig,
|
||||
temp_counter: usize,
|
||||
label_counter: usize,
|
||||
loop_stack: Vec<String>, // Stores the 'end' label of the current loops
|
||||
}
|
||||
|
||||
impl<'a, W: std::io::Write> Compiler<'a, W> {
|
||||
@@ -96,6 +97,7 @@ impl<'a, W: std::io::Write> Compiler<'a, W> {
|
||||
config: config.unwrap_or_default(),
|
||||
temp_counter: 0,
|
||||
label_counter: 0,
|
||||
loop_stack: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -146,6 +148,18 @@ impl<'a, W: std::io::Write> Compiler<'a, W> {
|
||||
self.expression_if(expr_if, scope)?;
|
||||
Ok(None)
|
||||
}
|
||||
Expression::Loop(expr_loop) => {
|
||||
self.expression_loop(expr_loop, scope)?;
|
||||
Ok(None)
|
||||
}
|
||||
Expression::While(expr_while) => {
|
||||
self.expression_while(expr_while, scope)?;
|
||||
Ok(None)
|
||||
}
|
||||
Expression::Break => {
|
||||
self.expression_break()?;
|
||||
Ok(None)
|
||||
}
|
||||
Expression::DeviceDeclaration(expr_dev) => {
|
||||
self.expression_device(expr_dev)?;
|
||||
Ok(None)
|
||||
@@ -570,6 +584,77 @@ impl<'a, W: std::io::Write> Compiler<'a, W> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn expression_loop<'v>(
|
||||
&mut self,
|
||||
expr: LoopExpression,
|
||||
scope: &mut VariableScope<'v>,
|
||||
) -> Result<(), Error> {
|
||||
let start_label = self.next_label_name();
|
||||
let end_label = self.next_label_name();
|
||||
|
||||
// Push end label to stack for 'break'
|
||||
self.loop_stack.push(end_label.clone());
|
||||
|
||||
self.write_output(format!("{start_label}:"))?;
|
||||
|
||||
// Compile Body
|
||||
self.expression_block(expr.body, scope)?;
|
||||
|
||||
// Jump back to start
|
||||
self.write_output(format!("j {start_label}"))?;
|
||||
self.write_output(format!("{end_label}:"))?;
|
||||
|
||||
self.loop_stack.pop();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn expression_while<'v>(
|
||||
&mut self,
|
||||
expr: WhileExpression,
|
||||
scope: &mut VariableScope<'v>,
|
||||
) -> Result<(), Error> {
|
||||
let start_label = self.next_label_name();
|
||||
let end_label = self.next_label_name();
|
||||
|
||||
// Push end label to stack for 'break'
|
||||
self.loop_stack.push(end_label.clone());
|
||||
|
||||
self.write_output(format!("{start_label}:"))?;
|
||||
|
||||
// Compile Condition
|
||||
let (cond_str, cleanup) = self.compile_operand(*expr.condition, scope)?;
|
||||
|
||||
// If condition is FALSE, jump to end
|
||||
self.write_output(format!("beq {cond_str} 0 {end_label}"))?;
|
||||
|
||||
if let Some(name) = cleanup {
|
||||
scope.free_temp(name)?;
|
||||
}
|
||||
|
||||
// Compile Body
|
||||
self.expression_block(expr.body, scope)?;
|
||||
|
||||
// Jump back to start
|
||||
self.write_output(format!("j {start_label}"))?;
|
||||
self.write_output(format!("{end_label}:"))?;
|
||||
|
||||
self.loop_stack.pop();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn expression_break(&mut self) -> Result<(), Error> {
|
||||
if let Some(label) = self.loop_stack.last() {
|
||||
self.write_output(format!("j {label}"))?;
|
||||
Ok(())
|
||||
} else {
|
||||
// This is a semantic error, but for now we can return a generic error
|
||||
// Ideally we'd have a specific error type for this
|
||||
Err(Error::Unknown("Break statement outside of loop".into()))
|
||||
}
|
||||
}
|
||||
|
||||
/// Helper to resolve a location to a register string (e.g., "r0").
|
||||
/// Note: This does not handle Stack locations automatically, as they require
|
||||
/// instruction emission to load. Use `compile_operand` for general handling.
|
||||
|
||||
Reference in New Issue
Block a user