From f19736cb8a9fb69c85857856feec91b0b729f93c Mon Sep 17 00:00:00 2001 From: Ross Andrews Date: Sat, 4 May 2024 20:37:47 -0500 Subject: [PATCH] Continue keyword --- forge_core/src/ast.rs | 2 + forge_core/src/compiler/ast_nodes/block.rs | 39 +++++++++++- .../src/compiler/ast_nodes/repeat_loop.rs | 6 +- .../src/compiler/ast_nodes/while_loop.rs | 2 +- forge_core/src/compiler/compiled_fn.rs | 16 ++--- forge_core/src/parser/ast_nodes/statement.rs | 1 + forge_core/src/parser/forge.pest | 3 +- vasm_core/src/ast.rs | 1 + vasm_core/src/vasm.pest | 2 +- vasm_core/src/vasm_parser.rs | 1 + vasm_core/src/vasm_preprocessor.rs | 61 ++++++++++++++++--- vtest/src/forge_tests.rs | 34 +++++++++++ vweb/src/forge_highlighter.js | 2 +- 13 files changed, 147 insertions(+), 23 deletions(-) diff --git a/forge_core/src/ast.rs b/forge_core/src/ast.rs index 49c8396..4acde6e 100644 --- a/forge_core/src/ast.rs +++ b/forge_core/src/ast.rs @@ -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", diff --git a/forge_core/src/compiler/ast_nodes/block.rs b/forge_core/src/compiler/ast_nodes/block.rs index b609f4e..0b84f4f 100644 --- a/forge_core/src/compiler/ast_nodes/block.rs +++ b/forge_core/src/compiler/ast_nodes/block.rs @@ -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", diff --git a/forge_core/src/compiler/ast_nodes/repeat_loop.rs b/forge_core/src/compiler/ast_nodes/repeat_loop.rs index f21e286..e0788eb 100644 --- a/forge_core/src/compiler/ast_nodes/repeat_loop.rs +++ b/forge_core/src/compiler/ast_nodes/repeat_loop.rs @@ -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 diff --git a/forge_core/src/compiler/ast_nodes/while_loop.rs b/forge_core/src/compiler/ast_nodes/while_loop.rs index bb2ab35..65c6749 100644 --- a/forge_core/src/compiler/ast_nodes/while_loop.rs +++ b/forge_core/src/compiler/ast_nodes/while_loop.rs @@ -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"); diff --git a/forge_core/src/compiler/compiled_fn.rs b/forge_core/src/compiler/compiled_fn.rs index 03a70ff..e6658cf 100644 --- a/forge_core/src/compiler/compiled_fn.rs +++ b/forge_core/src/compiler/compiled_fn.rs @@ -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>, /// 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