Added once blocks
This commit is contained in:
@@ -67,7 +67,8 @@ pub enum Statement {
|
|||||||
Conditional(Conditional),
|
Conditional(Conditional),
|
||||||
WhileLoop(WhileLoop),
|
WhileLoop(WhileLoop),
|
||||||
RepeatLoop(RepeatLoop),
|
RepeatLoop(RepeatLoop),
|
||||||
Asm(Asm)
|
Asm(Asm),
|
||||||
|
Once(Once)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(PartialEq, Clone, Debug)]
|
#[derive(PartialEq, Clone, Debug)]
|
||||||
@@ -117,6 +118,11 @@ pub struct Conditional {
|
|||||||
pub alternative: Option<Block>,
|
pub alternative: Option<Block>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(PartialEq, Clone, Debug)]
|
||||||
|
pub struct Once {
|
||||||
|
pub body: Block
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(PartialEq, Clone, Debug)]
|
#[derive(PartialEq, Clone, Debug)]
|
||||||
pub struct WhileLoop {
|
pub struct WhileLoop {
|
||||||
pub condition: Expr,
|
pub condition: Expr,
|
||||||
@@ -242,7 +248,8 @@ impl Statement {
|
|||||||
Statement::Conditional(_) => "conditional",
|
Statement::Conditional(_) => "conditional",
|
||||||
Statement::WhileLoop(_) => "whileLoop",
|
Statement::WhileLoop(_) => "whileLoop",
|
||||||
Statement::RepeatLoop(_) => "repeatLoop",
|
Statement::RepeatLoop(_) => "repeatLoop",
|
||||||
Statement::Asm(_) => "asm"
|
Statement::Asm(_) => "asm",
|
||||||
|
Statement::Once(_) => "once"
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ use crate::compiler::CompileError;
|
|||||||
use crate::compiler::state::State;
|
use crate::compiler::state::State;
|
||||||
|
|
||||||
impl Compilable for Block {
|
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 sig = sig.expect("Block outside function");
|
||||||
|
|
||||||
let frame_size_before_block = sig.frame_size();
|
let frame_size_before_block = sig.frame_size();
|
||||||
@@ -66,6 +66,9 @@ impl Compilable for Block {
|
|||||||
Statement::RepeatLoop(repeat_loop) => {
|
Statement::RepeatLoop(repeat_loop) => {
|
||||||
repeat_loop.process(state, Some(sig), loc)?;
|
repeat_loop.process(state, Some(sig), loc)?;
|
||||||
}
|
}
|
||||||
|
Statement::Once(once) => {
|
||||||
|
once.process(state, Some(sig), loc)?;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -14,3 +14,4 @@ mod r#const;
|
|||||||
mod r#return;
|
mod r#return;
|
||||||
mod conditional;
|
mod conditional;
|
||||||
mod while_loop;
|
mod while_loop;
|
||||||
|
mod once;
|
||||||
|
|||||||
@@ -0,0 +1,48 @@
|
|||||||
|
use crate::ast::{Once, Location};
|
||||||
|
use crate::compiler::compilable::Compilable;
|
||||||
|
use crate::compiler::compiled_fn::CompiledFn;
|
||||||
|
use crate::compiler::CompileError;
|
||||||
|
use crate::compiler::state::State;
|
||||||
|
|
||||||
|
impl Compilable for Once {
|
||||||
|
fn process(self, state: &mut State, sig: Option<&mut CompiledFn>, loc: Location) -> Result<(), CompileError> {
|
||||||
|
let sig = sig.expect("Once block outside function");
|
||||||
|
|
||||||
|
let Once { body } = self;
|
||||||
|
let sym = state.add_flag();
|
||||||
|
|
||||||
|
sig.emit_arg("loadw", sym.clone());
|
||||||
|
sig.emit("#if");
|
||||||
|
sig.emit_arg("push", 0);
|
||||||
|
sig.emit_arg("storew", sym);
|
||||||
|
body.process(state, Some(sig), loc)?;
|
||||||
|
sig.emit("#end");
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test {
|
||||||
|
use crate::compiler::test_utils::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_once() {
|
||||||
|
assert_eq!(
|
||||||
|
test_body(state_for("fn test() { var x = 0; once { x = 1; } }")),
|
||||||
|
vec![
|
||||||
|
"push 0",
|
||||||
|
"peekr",
|
||||||
|
"storew", // x = 0
|
||||||
|
"loadw _forge_gensym_3", // Load the once flag
|
||||||
|
"#if",
|
||||||
|
"push 0", // clear the once flag
|
||||||
|
"storew _forge_gensym_3",
|
||||||
|
"push 1", // x = 1
|
||||||
|
"peekr",
|
||||||
|
"storew",
|
||||||
|
"#end" // end once block
|
||||||
|
]
|
||||||
|
.join("\n")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -94,6 +94,11 @@ pub fn build_boot(src: &str, include_comments: bool) -> Result<Vec<String>, Comp
|
|||||||
asm.push(format!(".org {} + {}", label, size));
|
asm.push(format!(".org {} + {}", label, size));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Once flags:
|
||||||
|
for label in state.flags {
|
||||||
|
asm.push(format!("{}: .db 1", label));
|
||||||
|
}
|
||||||
|
|
||||||
// Final thing is to place a label for the stack:
|
// Final thing is to place a label for the stack:
|
||||||
// (this is just a cell with the address of the following word)
|
// (this is just a cell with the address of the following word)
|
||||||
asm.push("stack: .db 0".into());
|
asm.push("stack: .db 0".into());
|
||||||
|
|||||||
@@ -20,6 +20,8 @@ pub(crate) struct State {
|
|||||||
pub strings: Vec<(Label, String)>,
|
pub strings: Vec<(Label, String)>,
|
||||||
/// The statically-allocated buffers (size in bytes)
|
/// The statically-allocated buffers (size in bytes)
|
||||||
pub buffers: Vec<(Label, usize)>,
|
pub buffers: Vec<(Label, usize)>,
|
||||||
|
/// The flags for once blocks
|
||||||
|
pub flags: Vec<Label>,
|
||||||
/// The functions that have been prototyped but not yet defined
|
/// The functions that have been prototyped but not yet defined
|
||||||
pub prototypes: Scope,
|
pub prototypes: Scope,
|
||||||
/// The initialization code for globals
|
/// The initialization code for globals
|
||||||
@@ -72,6 +74,12 @@ impl State {
|
|||||||
sym
|
sym
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) fn add_flag(&mut self) -> Label {
|
||||||
|
let sym = self.gensym();
|
||||||
|
self.flags.push(sym.clone());
|
||||||
|
sym
|
||||||
|
}
|
||||||
|
|
||||||
pub(crate) fn declare_function(&mut self, name: &str, _args: Vec<String>) -> Result<(), CompileError> {
|
pub(crate) fn declare_function(&mut self, name: &str, _args: Vec<String>) -> Result<(), CompileError> {
|
||||||
// If it's not already prototyped, gensym a label and put it in the list. If it is, just
|
// If it's not already prototyped, gensym a label and put it in the list. If it is, just
|
||||||
// ignore this (we don't check arity so it's not like the arglist being different matters)
|
// ignore this (we don't check arity so it's not like the arglist being different matters)
|
||||||
|
|||||||
@@ -15,3 +15,4 @@ mod repeat_loop;
|
|||||||
mod program;
|
mod program;
|
||||||
mod operator;
|
mod operator;
|
||||||
mod expr;
|
mod expr;
|
||||||
|
mod once;
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
use crate::ast::{Block, Once};
|
||||||
|
use crate::parser::{AstNode, Pair, PestRule};
|
||||||
|
|
||||||
|
impl AstNode for Once {
|
||||||
|
const RULE: PestRule = PestRule::once;
|
||||||
|
fn from_pair(pair: Pair) -> Self {
|
||||||
|
let mut inner = pair.into_inner().peekable();
|
||||||
|
let body = Block::from_pair(inner.next().unwrap());
|
||||||
|
Self { body }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test {
|
||||||
|
use crate::ast::Statement;
|
||||||
|
use crate::parser::test_utils::{dislocate, dislocated_block};
|
||||||
|
use crate::parser::Parseable;
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parse_once_blocks() {
|
||||||
|
assert!(match Statement::from_str("once { foo(x); }") {
|
||||||
|
Ok(Statement::Once(Once { body })) => {
|
||||||
|
assert_eq!(dislocate(body.0), dislocated_block("{ foo(x); }"));
|
||||||
|
true
|
||||||
|
},
|
||||||
|
_ => false
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -15,6 +15,7 @@ impl AstNode for Statement {
|
|||||||
PestRule::conditional => Self::Conditional(Conditional::from_pair(pair)),
|
PestRule::conditional => Self::Conditional(Conditional::from_pair(pair)),
|
||||||
PestRule::while_loop => Self::WhileLoop(WhileLoop::from_pair(pair)),
|
PestRule::while_loop => Self::WhileLoop(WhileLoop::from_pair(pair)),
|
||||||
PestRule::repeat_loop => Self::RepeatLoop(RepeatLoop::from_pair(pair)),
|
PestRule::repeat_loop => Self::RepeatLoop(RepeatLoop::from_pair(pair)),
|
||||||
|
PestRule::once => Self::Once(Once::from_pair(pair)),
|
||||||
PestRule::asm => Self::Asm(Asm::from_pair(pair)),
|
PestRule::asm => Self::Asm(Asm::from_pair(pair)),
|
||||||
_ => unreachable!(),
|
_ => unreachable!(),
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -73,7 +73,7 @@ suffix = _{ subscript | arglist }
|
|||||||
subscript = { "[" ~ expr ~ "]" }
|
subscript = { "[" ~ expr ~ "]" }
|
||||||
arglist = { "(" ~ (expr ~ ("," ~ expr)*)? ~ ")" }
|
arglist = { "(" ~ (expr ~ ("," ~ expr)*)? ~ ")" }
|
||||||
|
|
||||||
statement = { asm | ((break_stmt | continue_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 | once }
|
||||||
|
|
||||||
asm = { "asm" ~ asm_args? ~ "{" ~ asm_body ~ "}" }
|
asm = { "asm" ~ asm_args? ~ "{" ~ asm_body ~ "}" }
|
||||||
asm_args = _{ "(" ~ expr ~ ("," ~ expr)* ~ ")" }
|
asm_args = _{ "(" ~ expr ~ ("," ~ expr)* ~ ")" }
|
||||||
@@ -95,6 +95,7 @@ return_stmt = { "return" ~ expr? }
|
|||||||
conditional = { "if" ~ "(" ~ expr ~ ")" ~ block ~ ("else" ~ block)? }
|
conditional = { "if" ~ "(" ~ expr ~ ")" ~ block ~ ("else" ~ block)? }
|
||||||
while_loop = { "while" ~ "(" ~ expr ~ ")" ~ block }
|
while_loop = { "while" ~ "(" ~ expr ~ ")" ~ block }
|
||||||
repeat_loop = { "repeat" ~ "(" ~ expr ~ ")" ~ name? ~ block }
|
repeat_loop = { "repeat" ~ "(" ~ expr ~ ")" ~ name? ~ block }
|
||||||
|
once = { "once" ~ block }
|
||||||
var_decl = { "var" ~ name ~ ("=" ~ expr)? }
|
var_decl = { "var" ~ name ~ ("=" ~ expr)? }
|
||||||
global = { "global" ~ name ~ ("=" ~ expr)? ~ ";" }
|
global = { "global" ~ name ~ ("=" ~ expr)? ~ ";" }
|
||||||
const_decl = { "const" ~ name ~ "=" ~ (string | expr) ~ ";" }
|
const_decl = { "const" ~ name ~ "=" ~ (string | expr) ~ ";" }
|
||||||
|
|||||||
@@ -7,12 +7,8 @@ pub trait PairExt {
|
|||||||
/// Go down one level in the AST, return the first node. Assumes the first node exists
|
/// Go down one level in the AST, return the first node. Assumes the first node exists
|
||||||
fn first(self) -> Self;
|
fn first(self) -> Self;
|
||||||
|
|
||||||
/// Returns the first child node as a String; useful for things like names and numbers
|
|
||||||
/// which are really just typed strings
|
|
||||||
fn first_as_string(self) -> String;
|
|
||||||
|
|
||||||
/// Just like `first` but panics if there's more than one child node.
|
/// Just like `first` but panics if there's more than one child node.
|
||||||
fn only(self) -> Self;
|
//fn only(self) -> Self;
|
||||||
|
|
||||||
/// This should really be an AstNode or something, but it's used so often: turn a Pair into
|
/// This should really be an AstNode or something, but it's used so often: turn a Pair into
|
||||||
/// an i32 by parsing the Forge number format (0xwhatever, 0bwhatever, etc)
|
/// an i32 by parsing the Forge number format (0xwhatever, 0bwhatever, etc)
|
||||||
@@ -30,17 +26,6 @@ impl<'a> PairExt for Pair<'a> {
|
|||||||
self.into_inner().next().unwrap()
|
self.into_inner().next().unwrap()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn first_as_string(self) -> String {
|
|
||||||
String::from(self.first().as_str())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn only(self) -> Pair<'a> {
|
|
||||||
let mut iter = self.into_inner();
|
|
||||||
let child = iter.next().unwrap();
|
|
||||||
debug_assert_eq!(iter.next(), None);
|
|
||||||
child
|
|
||||||
}
|
|
||||||
|
|
||||||
fn into_number(self) -> i32 {
|
fn into_number(self) -> i32 {
|
||||||
let first = self.into_inner().next().unwrap();
|
let first = self.into_inner().next().unwrap();
|
||||||
match first.as_rule() {
|
match first.as_rule() {
|
||||||
@@ -75,17 +60,10 @@ pub trait PairsExt {
|
|||||||
/// Peek the next sibling but only if it matches a given rule: used for things
|
/// Peek the next sibling but only if it matches a given rule: used for things
|
||||||
/// with optional modifiers following
|
/// with optional modifiers following
|
||||||
fn next_if_rule(&mut self, rule: PestRule) -> Option<Pair>;
|
fn next_if_rule(&mut self, rule: PestRule) -> Option<Pair>;
|
||||||
|
|
||||||
/// Grab the first thing from the list if you're sure it exists
|
|
||||||
fn first(&mut self) -> Pair;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PairsExt for Peekable<Pairs<'_>> {
|
impl PairsExt for Peekable<Pairs<'_>> {
|
||||||
fn next_if_rule(&mut self, rule: PestRule) -> Option<Pair> {
|
fn next_if_rule(&mut self, rule: PestRule) -> Option<Pair> {
|
||||||
self.next_if(|p| p.as_rule() == rule)
|
self.next_if(|p| p.as_rule() == rule)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn first(&mut self) -> Pair {
|
|
||||||
self.next().unwrap().first()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -231,6 +231,27 @@ fn static2_test() {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn once_test() {
|
||||||
|
// A once block.
|
||||||
|
// We pass a pointer in, it increments the var, but only the first time.
|
||||||
|
let src = "
|
||||||
|
fn foo(n) {
|
||||||
|
once {
|
||||||
|
*n = *n + 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn main() {
|
||||||
|
var n = 0;
|
||||||
|
foo(&n); foo(&n); foo(&n);
|
||||||
|
return n;
|
||||||
|
}";
|
||||||
|
assert_eq!(
|
||||||
|
main_return(src),
|
||||||
|
1
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
//#[test]
|
//#[test]
|
||||||
// This is no longer cursed, or a test. The revised calling convention with the pool pointer makes
|
// This is no longer cursed, or a test. The revised calling convention with the pool pointer makes
|
||||||
// it now perfectly sane. Left here for posterity.
|
// it now perfectly sane. Left here for posterity.
|
||||||
|
|||||||
Reference in New Issue
Block a user