Added break statements
This commit is contained in:
@@ -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",
|
||||
|
||||
@@ -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(())
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)),
|
||||
|
||||
@@ -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 }
|
||||
|
||||
@@ -62,6 +62,7 @@ pub enum Macro {
|
||||
Until,
|
||||
Do,
|
||||
End,
|
||||
Break,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
|
||||
@@ -69,7 +69,7 @@ org_directive = { label_def? ~ ".org" ~ expr }
|
||||
equ_directive = { label_def ~ ".equ" ~ expr }
|
||||
|
||||
include = { "include" ~ string }
|
||||
control = { "if" | "unless" | "while" | "until" | "do" | "else" | "end" }
|
||||
control = { "if" | "unless" | "while" | "until" | "do" | "else" | "end" | "break" }
|
||||
preprocessor = {"#" ~ (control | include) }
|
||||
|
||||
blank = { WHITESPACE? ~ COMMENT? }
|
||||
|
||||
@@ -208,6 +208,7 @@ fn parse_macro(line: Pair) -> Macro {
|
||||
"until" => Macro::Until,
|
||||
"do" => Macro::Do,
|
||||
"end" => Macro::End,
|
||||
"break" => Macro::Break,
|
||||
_ => unreachable!(),
|
||||
},
|
||||
Rule::include => Macro::Include(create_string(pre.only())),
|
||||
|
||||
@@ -81,7 +81,10 @@ impl<T: IntoIterator<Item = String>, F: Fn(String) -> Result<T, AssembleError>>
|
||||
|
||||
// It's a macro, so do it and then try again
|
||||
Ok(VASMLine::Macro(mac)) => {
|
||||
self.handle_macro(mac);
|
||||
match self.handle_macro(mac) {
|
||||
None => {}
|
||||
Some(err) => return Some(Err(err))
|
||||
}
|
||||
self.next()
|
||||
}
|
||||
|
||||
@@ -214,6 +217,20 @@ impl<T: IntoIterator<Item = String>, F: Fn(String) -> Result<T, AssembleError>>
|
||||
}
|
||||
_ => return Some(AssembleError::MacroError(self.current_location())),
|
||||
},
|
||||
|
||||
Macro::Break => {
|
||||
let innermost_loop = self.control_stack.iter().rev().find(|cs| {
|
||||
match cs {
|
||||
ControlStructure::LoopDo(_, _) => true,
|
||||
_ => false
|
||||
}
|
||||
});
|
||||
if let Some(ControlStructure::LoopDo(_, after)) = innermost_loop {
|
||||
self.emit(format!("jmpr @{}", after));
|
||||
} else {
|
||||
return Some(AssembleError::MacroError(self.current_location()))
|
||||
}
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
@@ -227,10 +244,18 @@ mod tests {
|
||||
|
||||
fn lines_for(source: Vec<String>) -> Vec<VASMLine> {
|
||||
let include = |_name: String| panic!();
|
||||
let src = LineSource::new("blah", source, include);
|
||||
let src = LineSource::new("<none>", source, include);
|
||||
src.map(|line| line.unwrap().line).collect()
|
||||
}
|
||||
|
||||
fn error_for(source: Vec<String>) -> Option<AssembleError> {
|
||||
let include = |_name: String| panic!();
|
||||
for line in LineSource::new("<none>", source, include) {
|
||||
if line.is_err() { return line.err() }
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
fn stringify(a: Vec<&str>) -> Vec<String> {
|
||||
a.into_iter().map(String::from).collect()
|
||||
}
|
||||
@@ -325,4 +350,40 @@ mod tests {
|
||||
]
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_preprocess_break() {
|
||||
// First a working one
|
||||
assert_eq!(
|
||||
lines_for(stringify(vec!["#while", "#do", "#break", "#end"])),
|
||||
vec![
|
||||
VASMLine::LabelDef(Label("__gensym_1".to_string())),
|
||||
VASMLine::Instruction(
|
||||
None,
|
||||
Brz,
|
||||
Some(Node::RelativeLabel("__gensym_2".to_string()))
|
||||
),
|
||||
VASMLine::Instruction(
|
||||
None,
|
||||
Jmpr,
|
||||
Some(Node::RelativeLabel("__gensym_2".to_string()))
|
||||
),
|
||||
VASMLine::Instruction(
|
||||
None,
|
||||
Jmpr,
|
||||
Some(Node::RelativeLabel("__gensym_1".to_string()))
|
||||
),
|
||||
VASMLine::LabelDef(Label::from("__gensym_2"))
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_malformed_break() {
|
||||
// Malformed one
|
||||
assert_eq!(
|
||||
error_for(stringify(vec!["#while", "#break"])),
|
||||
Some(AssembleError::MacroError(Location::from(2)))
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@ export default {
|
||||
if (word = stream.match(/^[a-zA-Z_$][a-zA-Z0-9_$]*/)) {
|
||||
// This is either a keyword or an identifier.
|
||||
// All the keywords are valid names, so, check that:
|
||||
if (word[0].match(/^fn|var|new|static|asm|return|if|while|repeat|global|const|peek|poke$/)) {
|
||||
if (word[0].match(/^fn|var|new|static|asm|return|if|while|repeat|global|const|peek|poke|break$/)) {
|
||||
if (word[0] === 'asm') {
|
||||
state.asm = true
|
||||
} // This is the start of an asm control structure
|
||||
|
||||
Reference in New Issue
Block a user