init
This commit is contained in:
commit
a542346b3d
|
@ -0,0 +1,2 @@
|
||||||
|
/target
|
||||||
|
Cargo.lock
|
|
@ -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]
|
|
@ -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,
|
||||||
|
})
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
|
@ -0,0 +1,2 @@
|
||||||
|
pub mod bf;
|
||||||
|
pub mod uxn;
|
|
@ -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)
|
||||||
|
}
|
|
@ -0,0 +1,3 @@
|
||||||
|
//! Assembles _the subset_ of tal, and assembles it.
|
||||||
|
|
||||||
|
//TODO: use nom you muppet
|
|
@ -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
|
||||||
|
}
|
Loading…
Reference in New Issue