1 module bfvm; 2 import std.stdio; 3 import std.conv; 4 import std.string; 5 import std.algorithm; 6 import std.array; 7 import std.range; 8 import arsd.terminal; 9 10 class BFVM 11 { 12 bool silent; 13 ubyte[] memory; 14 string program; 15 string mock_input; 16 string output; 17 BricketFinder brickets; 18 19 size_t cycle; 20 size_t input_ptr; 21 size_t memory_ptr; 22 size_t instruction_ptr; 23 24 this(size_t memory_size = 4096, bool silent = false) 25 { 26 this.silent = silent; 27 reset(); 28 setMemorySize(memory_size); 29 loadDefault(); 30 } 31 32 auto toggleSilence() 33 { 34 silent = !silent; 35 } 36 37 auto reset() 38 { 39 cycle = 0; 40 output = ""; 41 input_ptr = 0; 42 memory_ptr = 0; 43 mock_input = ""; 44 instruction_ptr = 0; 45 clearMemory(); 46 } 47 48 auto dumpMemory() 49 { 50 return memory; 51 } 52 53 auto dumpOutput() 54 { 55 return output; 56 } 57 58 auto setMemorySize(size_t size) 59 { 60 memory.length = size; 61 } 62 63 auto load(string program) 64 { 65 this.program = program; 66 brickets = new BricketFinder(program); 67 } 68 69 static pure stringToBF(alias str)() 70 { 71 immutable a = "[-]" ~ reduce!((newProgram, chr) => newProgram ~ (join("+".repeat(cast(size_t) chr)) ~ "." ~ join("-".repeat(cast(size_t) chr))) 72 )("", str); 73 return a; 74 } 75 76 auto loadDefault() 77 { 78 load(stringToBF!("Welcome to your BFVM.\nTo load a program use the .load() method\n")()); 79 } 80 81 auto clearMemory() 82 { 83 auto previous_memory_size = memory.length; 84 memory.destroy(); 85 memory.length = previous_memory_size; 86 } 87 88 auto encode(string[] array) 89 { 90 auto encoded_array = reduce!((accumulator, i) => accumulator ~ i.representation ~ [cast(ubyte) 0])(cast(ubyte[])[], array); 91 auto encoded_length = cast(ubyte) array.length; 92 return [encoded_length] ~ encoded_array; 93 } 94 95 auto insertMemory(size_t start, ubyte[] data) 96 { 97 memory[start..data.length+start] = data; 98 } 99 100 auto mockInput(string input) 101 { 102 input_ptr = 0; 103 mock_input = input; 104 } 105 106 auto run(size_t max_cycles=0, string[] argv=[]) 107 { 108 insertMemory(0, encode(argv)); 109 110 while (instruction_ptr < program.length && (max_cycles == 0 || cycle < max_cycles)) 111 { 112 step(); 113 } 114 } 115 116 auto step() 117 { 118 switch (program[instruction_ptr]) 119 { 120 case '+': 121 memory[memory_ptr]++; 122 break; 123 124 case '-': 125 memory[memory_ptr]--; 126 break; 127 128 case '>': 129 if (memory_ptr < memory.length-1) 130 { 131 memory_ptr++; 132 } 133 else 134 { 135 memory_ptr = 0; 136 } 137 break; 138 139 case '<': 140 if (memory_ptr > 0) 141 { 142 memory_ptr--; 143 } 144 else 145 { 146 memory_ptr = memory.length-1; 147 } 148 break; 149 150 case '.': 151 output ~= memory[memory_ptr].to!(char); 152 if (!silent) 153 { 154 write(memory[memory_ptr].to!(char)); 155 } 156 break; 157 158 case ',': 159 memory[memory_ptr] = getInput(); 160 break; 161 162 case '[': 163 if (memory[memory_ptr] == 0) 164 { 165 instruction_ptr = brickets.getCloseBricket(instruction_ptr); 166 } 167 break; 168 169 case ']': 170 instruction_ptr = brickets.getOpenBricket(instruction_ptr); 171 return; 172 173 default: 174 break; 175 } 176 instruction_ptr++; 177 cycle++; 178 } 179 180 auto getInput() 181 { 182 if (mock_input != "") 183 { 184 if (input_ptr < mock_input.length) 185 { 186 auto character = cast(ubyte) mock_input[input_ptr]; 187 input_ptr++; 188 return character; 189 } 190 else 191 { 192 return cast(ubyte) 0; 193 } 194 } 195 else 196 { 197 auto terminal = Terminal(ConsoleOutputType.linear); 198 auto terminalInput = RealTimeConsoleInput(&terminal, ConsoleInputFlags.raw); 199 return cast(ubyte) terminalInput.getch(); 200 } 201 } 202 } 203 204 class BricketFinder 205 { 206 size_t[size_t] open_bricket_map; 207 size_t[size_t] close_bricket_map; 208 209 this(string program) 210 { 211 size_t[] open_bricket_stack = []; 212 213 size_t instruction_ptr = 0; 214 while (instruction_ptr < program.length) 215 { 216 if (program[instruction_ptr] == '[') 217 { 218 open_bricket_stack ~= instruction_ptr; 219 } 220 if (program[instruction_ptr] == ']') 221 { 222 close_bricket_map[instruction_ptr] = open_bricket_stack.back(); 223 open_bricket_map[open_bricket_stack.back()] = instruction_ptr; 224 open_bricket_stack.popBack(); 225 } 226 instruction_ptr++; 227 } 228 } 229 230 this(size_t[size_t] open_bricket_map, size_t[size_t] close_bricket_map) 231 { 232 this.open_bricket_map = open_bricket_map; 233 this.close_bricket_map = close_bricket_map; 234 } 235 236 auto getOpenBricket(size_t close_bricket_ptr) 237 { 238 return this.close_bricket_map[close_bricket_ptr]; 239 } 240 241 auto getCloseBricket(size_t open_bricket_ptr) 242 { 243 return this.open_bricket_map[open_bricket_ptr]; 244 } 245 } 246 247 unittest 248 { 249 writeln("Default program"); 250 auto vm = new BFVM(); 251 vm.toggleSilence(); 252 vm.run(); 253 assert(vm.dumpOutput()=="Welcome to your BFVM.\nTo load a program use the .load() method\n"); 254 } 255 256 unittest 257 { 258 writeln("Argument passing"); 259 auto vm = new BFVM(); 260 vm.toggleSilence(); 261 vm.load("[[>]++++++++++++++++++++++++++++++++[<]>-]>[>]<----------------------[<]>[.>]"); 262 vm.run(0, ["./bfvm","dank memes"]); 263 assert(vm.dumpOutput()=="./bfvm dank memes\n"); 264 } 265 266 unittest 267 { 268 writeln("Cycle limiting"); 269 auto vm = new BFVM(); 270 vm.toggleSilence(); 271 vm.load("[>.]"); 272 vm.run(6, ["./bfvm"]); 273 assert(vm.dumpOutput()=="./"); 274 } 275 276 unittest 277 { 278 writeln("Input mocking"); 279 auto vm = new BFVM(); 280 vm.toggleSilence(); 281 vm.load(",.,.,."); 282 vm.mockInput("abc"); 283 vm.run(); 284 assert(vm.dumpOutput() == "abc"); 285 }