This commit is contained in:
phyto 2023-05-04 19:04:38 +00:00
commit a542346b3d
Signed by: phyto
SSH Key Fingerprint: SHA256:FJdIUDW+Q/c/oLlaNI7vrxCrv0VMxMgT6usJ+7A/wo0
8 changed files with 289 additions and 0 deletions

2
.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
/target
Cargo.lock

8
Cargo.toml Normal file
View File

@ -0,0 +1,8 @@
[package]
name = "braintal"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]

43
src/bf/mod.rs Normal file
View File

@ -0,0 +1,43 @@
/// A brainfuck operation, or an operation composed of sevral brainfuck operations.
///
/// [Op::LoopStart] and [Op::LoopEnd] contain absolute pointers to the corresponding bracket. They may have their pointers set to `0`, in which case they are "unset" and are to be set later by [self::optimise].
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Op {
/// (Pointer offset, ammount)
Add(isize),
/// (ammount)
Seek(isize),
LoopStart(usize),
LoopEnd(usize),
Input,
Output,
}
pub mod optimise;
pub fn str_to_ops(s: &str) -> Vec<Op> {
let mut r = vec![];
for c in s.chars() {
if let Some(x) = char_to_op(c) {
r.push(x)
}
}
r
}
fn char_to_op(c: char) -> Option<Op> {
use Op::*;
Some(match c {
'+' => Add(1),
'-' => Add(-1),
'<' => Seek(-1),
'>' => Seek(1),
'[' => LoopStart(0),
']' => LoopEnd(0),
',' => Input,
'.' => Output,
_ => return None,
})
}

143
src/bf/optimise.rs Normal file
View File

@ -0,0 +1,143 @@
use super::Op;
pub fn optimise(ops: &mut Vec<Op>) {
while merge_adjacent_adds(ops) {}
while merge_adjacent_seeks(ops) {}
//TODO:
// Clears: [-] and [+]
// Remove comments: [-][ whateve ] => [-], or loops at beginning
// NOPs: Add(_, 0) etc
// Copy, Set, Mul...
set_loop_adresses(ops);
purge_opening_comments(ops);
set_loop_adresses(ops);
remove_trailing_deadweight(ops);
}
/// Searches through an iterator to find something matching a pattern. Returns 0 if no pattern could be found.
macro_rules! find_op {
($indx:ident, $ops:ident, $op:pat, without $($haz:pat),*) => {
let mut $indx = 0;
for (i, v) in $ops.iter().enumerate() {
if let $op = v {
$indx = i;
} $(else if let $haz = v {break;})*
}
};
($indx:ident, $ops:ident, $op:pat, only $($haz:pat),*) => {
let mut $indx = 0;
for (i, v) in $ops.iter().enumerate() {
if let $op = v {
$indx = i;
} $(else if let $haz = v {continue;})*
else {break;}
}
};
}
fn remove_trailing_deadweight(ops: &mut Vec<Op>) {
let mut count = 0;
for v in ops.iter().rev() {
use Op::*;
match v {
Add(..) | Seek(..) => count += 1,
_ => break,
}
}
for _ in 0..count {
ops.pop();
}
}
fn purge_opening_comments(ops: &mut Vec<Op>) {
//FIXME: only to serve as an example.
if let Op::LoopStart(_) = ops[0] {
find_op!(indx, ops, Op::LoopEnd(0), without Op::Input);
if indx > 0 {
for _ in 0..=indx {
ops.remove(0);
}
}
}
}
// Search `ops[0..]` for `op`, and return the (relative) index, or [None] if it can't be found at all.
fn match_until(ops: &[Op], op: Op) -> Option<usize> {
for (i, o) in ops.iter().enumerate() {
if &op == o {
return Some(i);
}
}
None
}
fn merge_adjacent_adds(ops: &mut Vec<Op>) -> bool {
let mut acted = false;
let mut i = 0;
while i < ops.len() - 1 {
let op = &ops[i];
if let Op::Add(amt) = op {
if let Op::Add(other_amt) = ops[i + 1] {
ops[i] = Op::Add(amt + other_amt);
ops.remove(i + 1);
acted = true;
continue;
}
}
i += 1;
}
acted
}
fn merge_adjacent_seeks(ops: &mut Vec<Op>) -> bool {
let mut acted = false;
let mut i = 0;
while i < ops.len() - 1 {
let op = &ops[i];
if let Op::Seek(amt) = op {
if let Op::Seek(other_amt) = ops[i + 1] {
ops[i] = Op::Seek(amt + other_amt);
ops.remove(i + 1);
acted = true;
continue;
}
}
i += 1;
}
acted
}
/// Given a list of instructions, this function will set the adress of each [LoopStart] and [LoopEnd] to the corresponding one.
/// Here be dragons.
fn set_loop_adresses(ops: &mut Vec<Op>) {
let mut newops = ops.clone();
//FIXME: this could do it in less passes
'outer: for (i, op) in ops.iter().enumerate() {
if let Op::LoopStart(..) = op {
let mut nest = 0;
for (j, other_op) in ops.iter().skip(i + 1).enumerate() {
if let LoopEnd(..) = other_op {
if nest == 0 {
newops[i] = LoopStart(i + j + 1);
newops[i + j + 1] = LoopEnd(i);
continue 'outer;
}
}
use Op::*;
match other_op {
LoopStart(..) => nest += 1,
LoopEnd(..) => nest -= 1,
_ => (),
}
}
}
}
*ops = newops;
}

2
src/lib.rs Normal file
View File

@ -0,0 +1,2 @@
pub mod bf;
pub mod uxn;

14
src/main.rs Normal file
View File

@ -0,0 +1,14 @@
fn main() {
let mut bf = read_stdin();
braintal::bf::optimise::optimise(&mut bf);
let tal = braintal::uxn::from_ops(&bf);
dbg!(bf);
print!("{}", tal);
}
fn read_stdin() -> Vec<braintal::bf::Op> {
let stdin = std::io::stdin();
let buffer = std::io::read_to_string(stdin).unwrap();
braintal::bf::str_to_ops(&buffer)
}

3
src/uxn/asm.rs Normal file
View File

@ -0,0 +1,3 @@
//! Assembles _the subset_ of tal, and assembles it.
//TODO: use nom you muppet

74
src/uxn/mod.rs Normal file
View File

@ -0,0 +1,74 @@
//! Turns (optimised) bf into uxntal.
//!
//! We could, at some point, integrate a very simple tal assembler into the program, so that it can work standalone.
//! The array of bf cells begins at `ffff` ond grows down, because if it started at `0000` then you would only have 0x100 cells untill you start reading program memory (!), and I can't be bothered to make it work out how big the program will be.
mod asm;
use crate::bf::Op;
pub fn from_ops(s: &[Op]) -> String {
let mut r = init();
let mut uxn_index = 0;
for (i, o) in s.iter().enumerate() {
let new = match_op(o, i, uxn_index);
uxn_index += new.split_whitespace().count();
r.push_str(&new);
r.push('\n');
}
r.push_str("#0f DEO");
r
}
fn match_op(s: &Op, bf_index: usize, uxn_index: usize) -> String {
use Op::*;
let mut prog = String::new();
// Each addition MUST
// - Have each whitespace-seperated word equate to 1 byte of uxn
// - Leave the stack in the same format it was found
match s {
Add(1) => prog.push_str("DUP2 LDAk INC ROT ROT STA"), //INC is slightly smaller
Add(amt) if amt > &0 => prog.push_str(&format!("DUP2 LDAk LIT {:02} ADD ROT ROT STA", amt)),
Add(amt) if amt < &0 => {
prog.push_str(&format!("DUP2 LDAk LIT {:02} SUB ROT ROT STA", -amt))
}
Seek(-1) => prog.push_str("INC2"),
Seek(amt) if amt > &0 => prog.push_str(&format!("LIT 00 LIT {:02} SUB2", amt)),
Seek(amt) if amt < &0 => prog.push_str(&format!("LIT 00 LIT {:02} ADD2", -amt)),
LoopStart(adr) => prog.push_str(&format!(
"LDAk LIT 00 EQU ;{} JCN2 @{}",
sanitize_num(*adr),
sanitize_num(bf_index)
)),
LoopEnd(adr) => prog.push_str(&format!(
"LDAk ;{} JCN2 @{}",
sanitize_num(*adr),
sanitize_num(bf_index)
)),
Output => prog.push_str("LDAk #18 DEO"),
Input => prog.push_str(&format!(
";{0} LIT 10 DEO2 BRK @{0} DUP2 LIT 12 DEI ROT ROT STA",
sanitize_num(bf_index)
)),
_ => panic!("Instruction used but not supported, please file a bug!"),
}
prog
}
/// Load the pointer to the stack
fn init() -> String {
"|100\n#ffff\n".to_string()
}
/// In tal you can't have raw decimals as symbol names, so we do a crude conversion.
fn sanitize_num(mut x: usize) -> String {
let mut r = String::from("_");
while x > 0 {
r.push((0x41 + (x % 25)) as u8 as char);
x /= 25;
}
r
}