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