Continue keyword

This commit is contained in:
2024-05-04 20:37:47 -05:00
parent cd53a21e8f
commit f19736cb8a
13 changed files with 147 additions and 23 deletions
+2
View File
@@ -59,6 +59,7 @@ pub struct FunctionPrototype {
#[derive(PartialEq, Clone, Debug)]
pub enum Statement {
Break,
Continue,
Return(Return),
Assignment(Assignment),
Expr(Expr),
@@ -233,6 +234,7 @@ impl Statement {
String::from(
match self {
Statement::Break => "break",
Statement::Continue => "continue",
Statement::Return(_) => "return",
Statement::Assignment(_) => "assignment",
Statement::Expr(_) => "expr",
+36 -3
View File
@@ -22,6 +22,21 @@ impl Compilable for Block {
return Err(CompileError(loc.line, loc.col, "Break outside loop".to_string()))
}
}
Statement::Continue => {
if sig.in_loop() {
// This is subtle. To "continue" a while or until loop, we simply need to #continue
// and the assembler takes care of the macro for us. Repeat loops are different though
// because the counter inc / dec takes place at the end of the loop, so we can't use
// the macro, we just want to generate a jmpr instead
if let Some(label) = sig.continue_point() {
sig.emit_arg("jmpr", format!("@{}", label))
} else {
sig.emit("#continue")
}
} else {
return Err(CompileError(loc.line, loc.col, "Continue outside loop".to_string()))
}
}
Statement::Return(ret) => {
ret.process(state, Some(sig), loc)?;
}
@@ -89,7 +104,7 @@ mod test {
vec![
"push 10", "#while", "dup", "agt 0", "#do", // standard repeat loop header
"#break", // the break
"sub 1", "#end", "pop" // repeat loop footer
"_forge_gensym_3:", "sub 1", "#end", "pop" // repeat loop footer
].join("\n")
);
@@ -104,11 +119,28 @@ mod test {
}
#[test]
fn test_malformed_break_statements() {
fn test_continue_statements() {
assert_eq!(
test_body(state_for("fn test() { repeat(10) { continue; } }")),
vec![
"push 10", "#while", "dup", "agt 0", "#do", // standard repeat loop header
"jmpr @_forge_gensym_3", // the continue, jumping to the label
"_forge_gensym_3:", // The label demnoting the start of the counter update
"sub 1", "#end", "pop" // repeat loop footer
].join("\n")
);
}
#[test]
fn test_malformed_break_continue_statements() {
assert_eq!(
test_err("fn test() { break; }"),
Some(CompileError(1, 13, "Break outside loop".to_string()))
)
);
assert_eq!(
test_err("fn test() { continue; }"),
Some(CompileError(1, 13, "Continue outside loop".to_string()))
);
}
#[test]
@@ -129,6 +161,7 @@ mod test {
"#do",
"push 2", // Pointless loop body
"pop",
"_forge_gensym_3:",
"peekr", // Load c as an lvalue
"dup", // Dup it, load it, add 1
"loadw",
@@ -41,9 +41,11 @@ impl Compilable for RepeatLoop {
// Loop body:
sig.emit("#do");
sig.enter_loop();
let cp = state.gensym();
sig.enter_loop(Some(cp.clone()));
body.process(state, Some(sig), loc)?;
sig.exit_loop();
sig.emit(format!("{}:", cp).as_str());
// After the body we need to increment the counter if there is one
if named_counter {
@@ -115,6 +117,7 @@ mod test {
"peekr", // Load x as an lvalue
"add 3",
"storew", // Store c + x into it
"_forge_gensym_3:",
"peekr", // Load c as an lvalue
"add 6",
"dup", // Dup it, load it, add 1
@@ -157,6 +160,7 @@ mod test {
"peekr", // Load x as an lvalue
"add 3",
"storew", // Store 2x into it
"_forge_gensym_3:", // Label for the continue point, before the counter change
"sub 1", // decrement the counter
"#end", // End of the loop body!
"pop", // Drop the limit off the top
@@ -11,7 +11,7 @@ impl Compilable for WhileLoop {
sig.emit("#while");
condition.process(state, Some(sig), loc)?;
sig.emit("#do");
sig.enter_loop();
sig.enter_loop(None);
body.process(state, Some(sig), loc)?;
sig.exit_loop();
sig.emit("#end");
+9 -7
View File
@@ -1,7 +1,6 @@
use std::cmp::max;
use std::collections::btree_map::Entry::Vacant;
use std::fmt::Display;
use crate::ast::Location;
use crate::compiler::compile_error::CompileError;
use crate::compiler::text::Text;
use crate::compiler::utils::{Label, Scope, Variable};
@@ -57,7 +56,7 @@ pub struct CompiledFn {
pub max_frame_size: usize,
pub local_scope: Scope,
pub arity: usize,
pub loop_level: u32,
pub continue_points: Vec<Option<Label>>,
/// The preamble is the area of the function capturing the args, etc
pub preamble: Text,
@@ -85,12 +84,12 @@ impl CompiledFn {
}
}
pub fn enter_loop(&mut self) {
self.loop_level += 1
pub fn enter_loop(&mut self, continue_point: Option<Label>) {
self.continue_points.push(continue_point)
}
pub fn exit_loop(&mut self) {
if self.loop_level > 0 {
self.loop_level -= 1;
if self.continue_points.len() > 0 {
self.continue_points.pop();
} else {
// I doubt it's possible for this to happen, since it would be
// caught at the parse stage
@@ -98,7 +97,10 @@ impl CompiledFn {
}
}
pub fn in_loop(&self) -> bool {
self.loop_level > 0
self.continue_points.len() > 0
}
pub fn continue_point(&self) -> &Option<Label> {
self.continue_points.last().unwrap_or(&None)
}
/// Return an iterator over all the
@@ -7,6 +7,7 @@ impl AstNode for Statement {
let pair = pair.first();
match pair.as_rule() {
PestRule::break_stmt => Self::Break,
PestRule::continue_stmt => Self::Continue,
PestRule::return_stmt => Self::Return(Return::from_pair(pair)),
PestRule::assignment => Self::Assignment(Assignment::from_pair(pair)),
PestRule::expr => Self::Expr(Expr::from_pair(pair)),
+2 -1
View File
@@ -73,7 +73,7 @@ suffix = _{ subscript | arglist }
subscript = { "[" ~ expr ~ "]" }
arglist = { "(" ~ (expr ~ ("," ~ expr)*)? ~ ")" }
statement = { asm | ((break_stmt | return_stmt | var_decl | assignment | expr) ~ ";") | conditional | while_loop | repeat_loop }
statement = { asm | ((break_stmt | continue_stmt | return_stmt | var_decl | assignment | expr) ~ ";") | conditional | while_loop | repeat_loop }
asm = { "asm" ~ asm_args? ~ "{" ~ asm_body ~ "}" }
asm_args = _{ "(" ~ expr ~ ("," ~ expr)* ~ ")" }
@@ -90,6 +90,7 @@ declaration = { function | function_prototype | global | const_decl }
program = { (COMMENT | WHITESPACE)? ~ declaration* ~ EOI }
break_stmt = { "break" }
continue_stmt = { "continue" }
return_stmt = { "return" ~ expr? }
conditional = { "if" ~ "(" ~ expr ~ ")" ~ block ~ ("else" ~ block)? }
while_loop = { "while" ~ "(" ~ expr ~ ")" ~ block }