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 }