Added break statements

This commit is contained in:
2024-05-04 18:57:35 -05:00
parent 3f1b8fd316
commit cd53a21e8f
13 changed files with 138 additions and 6 deletions
+2
View File
@@ -58,6 +58,7 @@ pub struct FunctionPrototype {
#[derive(PartialEq, Clone, Debug)]
pub enum Statement {
Break,
Return(Return),
Assignment(Assignment),
Expr(Expr),
@@ -231,6 +232,7 @@ impl Statement {
pub fn description(&self) -> String {
String::from(
match self {
Statement::Break => "break",
Statement::Return(_) => "return",
Statement::Assignment(_) => "assignment",
Statement::Expr(_) => "expr",
+38 -1
View File
@@ -5,7 +5,7 @@ use crate::compiler::CompileError;
use crate::compiler::state::State;
impl Compilable for Block {
fn process(self, state: &mut State, sig: Option<&mut CompiledFn>, _loc: Location) -> Result<(), CompileError> {
fn process(self, state: &mut State, sig: Option<&mut CompiledFn>, loc: Location) -> Result<(), CompileError> {
let sig = sig.expect("Block outside function");
let frame_size_before_block = sig.frame_size();
@@ -15,6 +15,13 @@ impl Compilable for Block {
let loc = stmt.location;
sig.emit_comment(format!(";; {}:{} :: {}", loc.line, loc.col, stmt.ast.description()));
match stmt.ast {
Statement::Break => {
if sig.in_loop() {
sig.emit("#break")
} else {
return Err(CompileError(loc.line, loc.col, "Break outside loop".to_string()))
}
}
Statement::Return(ret) => {
ret.process(state, Some(sig), loc)?;
}
@@ -59,6 +66,7 @@ impl Compilable for Block {
#[cfg(test)]
mod test {
use crate::compiler::CompileError;
use crate::compiler::test_utils::*;
#[test]
@@ -74,6 +82,35 @@ mod test {
)
}
#[test]
fn test_break_statements() {
assert_eq!(
test_body(state_for("fn test() { repeat(10) { break; } }")),
vec![
"push 10", "#while", "dup", "agt 0", "#do", // standard repeat loop header
"#break", // the break
"sub 1", "#end", "pop" // repeat loop footer
].join("\n")
);
assert_eq!(
test_body(state_for("fn test() { while(1) { break; } }")),
vec![
"#while", "push 1", "#do", // standard repeat loop header
"#break", // the break
"#end" // repeat loop footer
].join("\n")
)
}
#[test]
fn test_malformed_break_statements() {
assert_eq!(
test_err("fn test() { break; }"),
Some(CompileError(1, 13, "Break outside loop".to_string()))
)
}
#[test]
fn test_block_scoping() {
assert_eq!(
@@ -41,7 +41,9 @@ impl Compilable for RepeatLoop {
// Loop body:
sig.emit("#do");
sig.enter_loop();
body.process(state, Some(sig), loc)?;
sig.exit_loop();
// After the body we need to increment the counter if there is one
if named_counter {
@@ -11,7 +11,9 @@ impl Compilable for WhileLoop {
sig.emit("#while");
condition.process(state, Some(sig), loc)?;
sig.emit("#do");
sig.enter_loop();
body.process(state, Some(sig), loc)?;
sig.exit_loop();
sig.emit("#end");
Ok(())
+18
View File
@@ -1,6 +1,7 @@
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};
@@ -56,6 +57,7 @@ pub struct CompiledFn {
pub max_frame_size: usize,
pub local_scope: Scope,
pub arity: usize,
pub loop_level: u32,
/// The preamble is the area of the function capturing the args, etc
pub preamble: Text,
@@ -83,6 +85,22 @@ impl CompiledFn {
}
}
pub fn enter_loop(&mut self) {
self.loop_level += 1
}
pub fn exit_loop(&mut self) {
if self.loop_level > 0 {
self.loop_level -= 1;
} else {
// I doubt it's possible for this to happen, since it would be
// caught at the parse stage
unreachable!()
}
}
pub fn in_loop(&self) -> bool {
self.loop_level > 0
}
/// Return an iterator over all the
pub fn text(&self) -> impl Iterator<Item = &str> {
self.preamble.0.iter().chain(self.body.0.iter()).chain(self.outro.0.iter()).map(String::as_str)
+6
View File
@@ -1,4 +1,5 @@
use crate::compiler::compilable::Compilable;
use crate::compiler::CompileError;
use crate::compiler::state::State;
use crate::parser::parse;
@@ -15,6 +16,11 @@ pub(crate) fn test_body(state: State) -> String {
state.functions.get("test").unwrap().body.0.join("\n")
}
pub(crate) fn test_err(src: &str) -> Option<CompileError> {
let mut state = State::default();
parse(src).unwrap().process(&mut state, None, (0, 0).into()).err()
}
pub(crate) fn test_preamble(state: State) -> String {
state.functions.get("test").unwrap().preamble.0.join("\n")
}
@@ -6,6 +6,7 @@ impl AstNode for Statement {
fn from_pair(pair: Pair) -> Self {
let pair = pair.first();
match pair.as_rule() {
PestRule::break_stmt => Self::Break,
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 | ((return_stmt | var_decl | assignment | expr) ~ ";") | conditional | while_loop | repeat_loop }
statement = { asm | ((break_stmt | return_stmt | var_decl | assignment | expr) ~ ";") | conditional | while_loop | repeat_loop }
asm = { "asm" ~ asm_args? ~ "{" ~ asm_body ~ "}" }
asm_args = _{ "(" ~ expr ~ ("," ~ expr)* ~ ")" }
@@ -89,6 +89,7 @@ function_prototype = { "fn" ~ name ~ argnames ~ ";" }
declaration = { function | function_prototype | global | const_decl }
program = { (COMMENT | WHITESPACE)? ~ declaration* ~ EOI }
break_stmt = { "break" }
return_stmt = { "return" ~ expr? }
conditional = { "if" ~ "(" ~ expr ~ ")" ~ block ~ ("else" ~ block)? }
while_loop = { "while" ~ "(" ~ expr ~ ")" ~ block }