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 }