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