1 module optimize;
2 
3 import brainfuck;
4 
5 import std.algorithm.iteration;
6 import std.algorithm.searching;
7 import std.array;
8 import std.range;
9 import std.conv;
10 
11 alias Counts = size_t[string];
12 
13 Counts count_instructions(BrainfuckInstruction[] insts) {
14     Counts counts;
15 
16     foreach(inst; insts) {
17         counts[typeid(inst).toString]++;
18         if(auto loop = cast(Loop) inst) {
19             auto inner_counts = count_instructions(loop.insts);
20             add_counts(counts, inner_counts);
21         } else if(auto if_bf = cast(If) inst) {
22             auto inner_counts = count_instructions(if_bf.insts);
23             add_counts(counts, inner_counts);
24         } else if(auto unrolled = cast(UnrolledLoop) inst) {
25             auto inner_counts = count_instructions(unrolled.insts);
26             add_counts(counts, inner_counts);
27         }
28     }
29 
30     return counts;
31 }
32 
33 void add_counts(Counts dst, Counts src) {
34     foreach(k, v; src) {
35         dst[k] += v;
36     }
37 }
38 
39 BrainfuckInstruction[] clear_opt(BrainfuckInstruction[] insts) {
40     return insts.map!(delegate BrainfuckInstruction(inst) {
41         if(auto loop = cast(Loop) inst) {
42             auto loop_insts = loop.insts;
43             if(loop_insts.length == 1) {
44                 if(auto modify = cast(Modify) loop_insts[0]) {
45                     if(modify.amt == -1) return new Clear;
46                 }
47             }
48 
49             return new Loop(clear_opt(loop.insts));
50         }
51 
52         return inst;
53     }).array;
54 }
55 
56 BrainfuckInstruction[] balanced_opt(BrainfuckInstruction[] insts) {
57     return insts.map!(delegate BrainfuckInstruction(inst) {
58         if(auto loop = cast(Loop) inst) {
59             auto loop_insts = loop.insts;
60             if(loop_insts.all!(loop_inst => (cast(Modify) loop_inst) || (cast(Select) loop_inst))) {
61                 if(loop_insts
62                     .filter!(loop_inst => cast(Select) loop_inst)
63                     .map!(loop_inst => (cast(Select) loop_inst).amt)
64                     .sum == 0) {
65                     int[int] ms;
66                     int pointer = 0;
67                     
68                     foreach(loop_inst; loop_insts) {
69                         if(auto modify = cast(Modify) loop_inst) {
70                             ms[pointer] += modify.amt;
71                         } else if(auto select = cast(Select) loop_inst) {
72                             pointer += select.amt;
73                         }
74                     }
75 
76                     if(ms[0] == -1) {
77                         ms.remove(0);
78                         return new MoveLoop(ms);
79                     }
80                 }
81             }
82 
83             return new Loop(balanced_opt(loop.insts));
84         }
85 
86         return inst;
87     }).array;
88 }
89 
90 BrainfuckInstruction[] if_opt(BrainfuckInstruction[] insts) {
91     return insts.map!((inst) {
92         if(auto loop = cast(Loop) inst) {
93             int pointer;
94             ubyte[int] tape;
95 
96             foreach(ref loop_inst; loop.insts) {
97                 if(auto modify = cast(Modify) loop_inst) {
98                     if(pointer in tape)
99                         tape[pointer] += modify.amt;
100                 } else if(auto select = cast(Select) loop_inst) {
101                     pointer += select.amt;                    
102                 } else if(cast(Clear) loop_inst) {
103                     tape[pointer] = 0;
104                 } else if(cast(Input) loop_inst) {
105                     tape.remove(pointer);
106                 } else if(cast(Loop) loop_inst || cast(MoveLoop) loop_inst) {
107                     pointer = 0;
108                     tape.clear;
109                     tape[0] = 0;
110 
111                     if(auto inner_loop = cast(Loop) loop_inst) {
112                         loop_inst = new Loop(if_opt(inner_loop.insts));
113                     }
114                 }
115             }
116 
117             if(pointer in tape && tape[pointer] == 0) {
118                 return new If(loop.insts);
119             }
120 
121             return inst;
122         } else {
123             return inst;
124         }
125     }).array;
126 }
127 
128 BrainfuckInstruction[] unroll_opt(BrainfuckInstruction[] insts, bool program_body) {
129     int pointer;
130     ubyte[int] tape;
131     
132     if(program_body) {
133         for(uint i = 0; i < 30000; i++) {
134             tape[i] = 0;
135         }
136     }
137 
138     foreach(ref inst; insts) {
139         if(auto modify = cast(Modify) inst) {
140             if(pointer in tape)
141                 tape[pointer] += modify.amt;
142         } else if(auto select = cast(Select) inst) {
143             pointer += select.amt;
144         } else if(cast(Clear) inst) {
145             tape[pointer] = 0;
146         } else if(cast(Input) inst) {
147             tape.remove(pointer);
148         } else if(cast(Loop) inst || cast(MoveLoop) inst) {
149             if(auto loop = cast(Loop) inst) {
150                 if(pointer in tape) {
151                     auto count = tape[pointer];
152                     int inner_pointer;
153                     int[int] inner_tape;
154 
155                     foreach(inner_inst; loop.insts) {
156                         if(auto modify = cast(Modify) inner_inst) {
157                             inner_tape[inner_pointer] += modify.amt;
158                         } else if(auto select = cast(Select) inner_inst) {
159                             inner_pointer += select.amt;                    
160                         } else if(cast(Clear) inner_inst) {
161                             inner_tape[inner_pointer] = 0;
162                         } else if(cast(Input) inner_inst) {
163                             inner_tape.remove(inner_pointer);
164                         } else if(cast(Loop) inner_inst || cast(MoveLoop) inner_inst) {
165                             inner_pointer = 0;
166                             inner_tape.clear;
167                             inner_tape[0] = 0;
168                             
169                             if(auto inner_loop = cast(Loop) inner_inst) {
170                                 inner_loop.insts = unroll_opt(inner_loop.insts, false);
171                             }
172                         }
173                     }
174 
175                     if(0 in inner_tape && inner_tape[0] == -1 && inner_pointer == 0) {
176                         inst = new UnrolledLoop(count, loop.insts);
177                     }
178                 }
179             }
180             
181             pointer = 0;
182             tape.clear;
183             tape[0] = 0;
184         }
185     }
186 
187     return insts;
188 }