Added once blocks

This commit is contained in:
2024-05-11 01:24:41 -05:00
parent f19736cb8a
commit 3b7ce3eb26
12 changed files with 132 additions and 28 deletions
+9 -2
View File
@@ -67,7 +67,8 @@ pub enum Statement {
Conditional(Conditional),
WhileLoop(WhileLoop),
RepeatLoop(RepeatLoop),
Asm(Asm)
Asm(Asm),
Once(Once)
}
#[derive(PartialEq, Clone, Debug)]
@@ -117,6 +118,11 @@ pub struct Conditional {
pub alternative: Option<Block>,
}
#[derive(PartialEq, Clone, Debug)]
pub struct Once {
pub body: Block
}
#[derive(PartialEq, Clone, Debug)]
pub struct WhileLoop {
pub condition: Expr,
@@ -242,7 +248,8 @@ impl Statement {
Statement::Conditional(_) => "conditional",
Statement::WhileLoop(_) => "whileLoop",
Statement::RepeatLoop(_) => "repeatLoop",
Statement::Asm(_) => "asm"
Statement::Asm(_) => "asm",
Statement::Once(_) => "once"
}
)
}
+4 -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();
@@ -66,6 +66,9 @@ impl Compilable for Block {
Statement::RepeatLoop(repeat_loop) => {
repeat_loop.process(state, Some(sig), loc)?;
}
Statement::Once(once) => {
once.process(state, Some(sig), loc)?;
}
}
}
+1
View File
@@ -14,3 +14,4 @@ mod r#const;
mod r#return;
mod conditional;
mod while_loop;
mod once;
+48
View File
@@ -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")
);
}
}
+5
View File
@@ -94,6 +94,11 @@ pub fn build_boot(src: &str, include_comments: bool) -> Result<Vec<String>, Comp
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:
// (this is just a cell with the address of the following word)
asm.push("stack: .db 0".into());
+8
View File
@@ -20,6 +20,8 @@ pub(crate) struct State {
pub strings: Vec<(Label, String)>,
/// The statically-allocated buffers (size in bytes)
pub buffers: Vec<(Label, usize)>,
/// The flags for once blocks
pub flags: Vec<Label>,
/// The functions that have been prototyped but not yet defined
pub prototypes: Scope,
/// The initialization code for globals
@@ -72,6 +74,12 @@ impl State {
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> {
// 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)
+2 -1
View File
@@ -14,4 +14,5 @@ mod while_loop;
mod repeat_loop;
mod program;
mod operator;
mod expr;
mod expr;
mod once;
+30
View File
@@ -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::while_loop => Self::WhileLoop(WhileLoop::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)),
_ => unreachable!(),
}
+2 -1
View File
@@ -73,7 +73,7 @@ suffix = _{ subscript | arglist }
subscript = { "[" ~ 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_args = _{ "(" ~ expr ~ ("," ~ expr)* ~ ")" }
@@ -95,6 +95,7 @@ return_stmt = { "return" ~ expr? }
conditional = { "if" ~ "(" ~ expr ~ ")" ~ block ~ ("else" ~ block)? }
while_loop = { "while" ~ "(" ~ expr ~ ")" ~ block }
repeat_loop = { "repeat" ~ "(" ~ expr ~ ")" ~ name? ~ block }
once = { "once" ~ block }
var_decl = { "var" ~ name ~ ("=" ~ expr)? }
global = { "global" ~ name ~ ("=" ~ expr)? ~ ";" }
const_decl = { "const" ~ name ~ "=" ~ (string | expr) ~ ";" }
+1 -23
View File
@@ -7,12 +7,8 @@ pub trait PairExt {
/// Go down one level in the AST, return the first node. Assumes the first node exists
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.
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
/// 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()
}
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 {
let first = self.into_inner().next().unwrap();
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
/// with optional modifiers following
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<'_>> {
fn next_if_rule(&mut self, rule: PestRule) -> Option<Pair> {
self.next_if(|p| p.as_rule() == rule)
}
fn first(&mut self) -> Pair {
self.next().unwrap().first()
}
}
+21
View File
@@ -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]
// 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.