Skip to content

Commit 80b558d

Browse files
authored
Merge pull request #34 from Heathcorp/dev
Better output statement optimisations + fix of post-optimiser
2 parents 1f71a3b + 069bc25 commit 80b558d

File tree

14 files changed

+1009
-586
lines changed

14 files changed

+1009
-586
lines changed

LICENSE

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
MIT License
2+
3+
Copyright (c) 2025 Heathcorp
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
SOFTWARE.

compiler/src/backend/bf2d.rs

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -81,18 +81,18 @@ impl BrainfuckProgram for Vec<Opcode2D> {
8181
ops.push(Opcode2D::Clear);
8282
i += 3;
8383
} else {
84-
match substr.chars().next().unwrap() {
85-
'+' => ops.push(Opcode2D::Add),
86-
'-' => ops.push(Opcode2D::Subtract),
87-
'>' => ops.push(Opcode2D::Right),
88-
'<' => ops.push(Opcode2D::Left),
89-
'[' => ops.push(Opcode2D::OpenLoop),
90-
']' => ops.push(Opcode2D::CloseLoop),
91-
'.' => ops.push(Opcode2D::Output),
92-
',' => ops.push(Opcode2D::Input),
93-
'^' => ops.push(Opcode2D::Up),
94-
'v' => ops.push(Opcode2D::Down),
95-
_ => (), // could put a little special opcode in for other characters
84+
match substr.chars().next() {
85+
Some('+') => ops.push(Opcode2D::Add),
86+
Some('-') => ops.push(Opcode2D::Subtract),
87+
Some('>') => ops.push(Opcode2D::Right),
88+
Some('<') => ops.push(Opcode2D::Left),
89+
Some('[') => ops.push(Opcode2D::OpenLoop),
90+
Some(']') => ops.push(Opcode2D::CloseLoop),
91+
Some('.') => ops.push(Opcode2D::Output),
92+
Some(',') => ops.push(Opcode2D::Input),
93+
Some('^') => ops.push(Opcode2D::Up),
94+
Some('v') => ops.push(Opcode2D::Down),
95+
_ => (),
9696
}
9797
i += 1;
9898
}

compiler/src/backend/common.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -459,6 +459,6 @@ pub trait BrainfuckBuilder<TC, OC> {
459459
}
460460

461461
pub trait BrainfuckProgram {
462-
fn to_string(self) -> String;
463462
fn from_str(s: &str) -> Self;
463+
fn to_string(self) -> String;
464464
}

compiler/src/backend/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,4 @@ pub mod bf;
44
pub mod bf2d;
55

66
mod constants_optimiser;
7+
mod optimiser;
Lines changed: 221 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,221 @@
1+
use crate::{backend::bf::*, misc::MastermindContext};
2+
use std::{collections::HashMap, num::Wrapping};
3+
4+
impl MastermindContext {
5+
pub fn optimise_bf(&self, ops: Vec<Opcode>) -> Vec<Opcode> {
6+
let mut output = Vec::new();
7+
8+
// get stretch of characters to optimise (+-<>)
9+
let mut subset = Vec::new();
10+
for op in ops {
11+
match op {
12+
Opcode::Add | Opcode::Subtract | Opcode::Right | Opcode::Left | Opcode::Clear => {
13+
subset.push(op);
14+
}
15+
Opcode::OpenLoop | Opcode::CloseLoop | Opcode::Input | Opcode::Output => {
16+
// optimise subset and push
17+
let optimised_subset = optimise_bf_subset(subset);
18+
subset = vec![];
19+
20+
// remove any redundant movement at the beginning
21+
// (this shouldn't really be in the loop,
22+
// but it's tested and works, and compiler code isn't performance critical)
23+
for subset_op in optimised_subset {
24+
if let (0, Opcode::Left | Opcode::Right) = (output.len(), subset_op) {
25+
continue;
26+
}
27+
output.push(subset_op);
28+
}
29+
output.push(op);
30+
}
31+
}
32+
}
33+
34+
output
35+
}
36+
}
37+
38+
fn optimise_bf_subset(run: Vec<Opcode>) -> Vec<Opcode> {
39+
#[derive(Clone)]
40+
enum Change {
41+
Add(Wrapping<i8>),
42+
Set(Wrapping<i8>),
43+
}
44+
let mut tape: HashMap<i32, Change> = HashMap::new();
45+
let mut head: i32 = 0;
46+
47+
let mut i = 0;
48+
while i < run.len() {
49+
let op = run[i];
50+
match op {
51+
Opcode::Clear => {
52+
tape.insert(head, Change::Set(Wrapping(0i8)));
53+
}
54+
Opcode::Subtract | Opcode::Add => {
55+
let mut change = tape.remove(&head).unwrap_or(Change::Add(Wrapping(0i8)));
56+
57+
let (Change::Add(val) | Change::Set(val)) = &mut change;
58+
*val += match op {
59+
Opcode::Add => 1,
60+
Opcode::Subtract => -1,
61+
_ => 0,
62+
};
63+
64+
match &change {
65+
Change::Add(val) => {
66+
if *val != Wrapping(0i8) {
67+
tape.insert(head, change);
68+
}
69+
}
70+
Change::Set(_) => {
71+
tape.insert(head, change);
72+
}
73+
}
74+
}
75+
Opcode::Right => {
76+
head += 1;
77+
}
78+
Opcode::Left => {
79+
head -= 1;
80+
}
81+
_ => (),
82+
}
83+
i += 1;
84+
}
85+
// always have a start and end cell
86+
if !tape.contains_key(&0) {
87+
tape.insert(0, Change::Add(Wrapping(0i8)));
88+
}
89+
if !tape.contains_key(&head) {
90+
tape.insert(head, Change::Add(Wrapping(0i8)));
91+
}
92+
93+
// This whole algorithm is probably really efficient and I reckon there's almost certainly a better way
94+
// It's also just really poorly done in general, I don't understand what everything does and I wrote the damned thing
95+
// TODO: refactor this properly
96+
// convert hashmap to array
97+
// start by making a negative and positive array
98+
let mut pos_arr = Vec::new();
99+
let mut neg_arr = Vec::new();
100+
for (cell, value) in tape.into_iter() {
101+
let i: usize;
102+
let arr: &mut Vec<Change>;
103+
if cell < 0 {
104+
i = (-(cell + 1)) as usize;
105+
arr = &mut neg_arr;
106+
} else {
107+
i = cell as usize;
108+
arr = &mut pos_arr;
109+
}
110+
111+
if i >= arr.len() {
112+
arr.resize(i + 1, Change::Add(Wrapping(0i8)));
113+
}
114+
arr[i] = value;
115+
}
116+
let start_index = neg_arr.len();
117+
// now combine the two arrays
118+
let mut tape_arr: Vec<Change> = Vec::new();
119+
tape_arr.extend(neg_arr.into_iter().rev());
120+
tape_arr.extend(pos_arr.into_iter());
121+
122+
if ((start_index) + 1) >= (tape_arr.len()) {
123+
tape_arr.resize(start_index + 1, Change::Add(Wrapping(0i8)));
124+
}
125+
let final_index = ((start_index as i32) + head) as usize;
126+
127+
let mut output = Vec::new();
128+
129+
// Also this following algorithm for zig-zagging around the tape is pretty poor as well, there has to be a nicer way of doing it
130+
131+
// if final cell is to the right of the start cell then we need to go to the left first, and vice-versa
132+
// 1. go to the furthest point on the tape (opposite of direction to final cell)
133+
// 2. go from that end of the tape to the opposite end of the tape
134+
// 3. return to the final cell
135+
let mut idx = start_index;
136+
let d2 = final_index >= start_index;
137+
let d1 = !d2;
138+
let d3 = !d2;
139+
140+
//1
141+
match d1 {
142+
true => {
143+
for _ in start_index..(tape_arr.len() - 1) {
144+
output.push(Opcode::Right);
145+
idx += 1;
146+
}
147+
}
148+
false => {
149+
for _ in (1..=start_index).rev() {
150+
output.push(Opcode::Left);
151+
idx -= 1;
152+
}
153+
}
154+
}
155+
156+
//2
157+
match d2 {
158+
true => {
159+
for cell in idx..tape_arr.len() {
160+
let change = &tape_arr[cell];
161+
if let Change::Set(_) = change {
162+
output.push(Opcode::Clear);
163+
}
164+
let (Change::Add(v) | Change::Set(v)) = change;
165+
let v = v.0;
166+
167+
for _ in 0..(v as i32).abs() {
168+
output.push(match v > 0 {
169+
true => Opcode::Add,
170+
false => Opcode::Subtract,
171+
});
172+
}
173+
174+
if cell < (tape_arr.len() - 1) {
175+
output.push(Opcode::Right);
176+
idx += 1;
177+
}
178+
}
179+
}
180+
false => {
181+
for cell in (0..=idx).rev() {
182+
let change = &tape_arr[cell];
183+
if let Change::Set(_) = change {
184+
output.push(Opcode::Clear);
185+
}
186+
let (Change::Add(v) | Change::Set(v)) = change;
187+
let v = v.0;
188+
189+
for _ in 0..(v as i32).abs() {
190+
output.push(match v > 0 {
191+
true => Opcode::Add,
192+
false => Opcode::Subtract,
193+
});
194+
}
195+
196+
if cell > 0 {
197+
output.push(Opcode::Left);
198+
idx -= 1;
199+
}
200+
}
201+
}
202+
}
203+
204+
//3
205+
match d3 {
206+
true => {
207+
for _ in idx..final_index {
208+
output.push(Opcode::Right);
209+
idx += 1;
210+
}
211+
}
212+
false => {
213+
for _ in final_index..idx {
214+
output.push(Opcode::Left);
215+
idx -= 1;
216+
}
217+
}
218+
}
219+
220+
output
221+
}

0 commit comments

Comments
 (0)