1 module dfuck;
2 
3 import optimize;
4 import brainfuck;
5 import input_stream;
6 import parse;
7 import cfg;
8 
9 import darg;
10 
11 import std.stdio;
12 import core.stdc.stdlib;
13 import std.file;
14 import std.range;
15 import std.conv;
16 import std.traits;
17 import std.regex;
18 import std.string;
19 import std.conv;
20 import std.process;
21 import std.array;
22 import std.algorithm.iteration;
23 
24 struct Options {
25     @Option("help", "h")
26     @Help("Prints this help.")
27     OptionFlag help;
28 
29     @Option("only-compile", "oc")
30     @Help("Compile a brainfuck file and generate an executable.")
31     OptionFlag only_compile;
32 
33     @Option("compile-run", "cr")
34     @Help("Compile and run a brainfuck file.")
35     OptionFlag compile_run;
36     
37     @Option("interpret", "i")
38     @Help("Interpret a brainfuck file.")
39     OptionFlag interpret;
40 
41     @Option("intermediate", "ir")
42     @Help("Specifies a file to ouput IR code to.")
43     string intermediate;
44 
45     @Option("compiler", "c")
46     @Help("The compiler to use. Must be one of: gcc. Defaults to gcc.")
47     string compiler = "gcc";
48 
49     @Option("graph", "g")
50     @Help("Specifies a file to output the CFG to.")
51     string graph;
52 
53     @Argument("source")
54     @Help("The brainfuck file to be run.")
55     string source;
56 }
57 
58 int main(string[] args) {
59     immutable help = helpString!Options;
60     immutable usage = usageString!Options("dfuck");
61     Options options;
62 
63     try {
64         options = parseArgs!Options(args[1 .. $]);
65     } catch (ArgParseError e) {
66         writeln(e.msg);
67         writeln(usage);
68         return 1;
69     } catch (ArgParseHelp e) {
70         writeln(usage);
71         write(help);
72         return 0;
73     }
74 
75     auto code = readText(options.source);
76 
77     std.stdio.write("Sanitizing code... ");
78     stdout.flush;
79     auto re = regex(r"[^(\[|\]|\+|\-|>|<|\.|,)]", "g");
80     auto sanitized_code = replaceAll(code, re, "");
81     writeln("done\n");
82 
83     BrainfuckInstruction[] insts;
84 
85     writeln("Parsing instructions...");
86     stdout.flush;
87     insts = parse_brainfuck(sanitized_code);
88     auto counts0 = count_instructions(insts);
89     write_inst_count(counts0);
90     writeln;
91 
92     writeln("Clear optimization...");
93     stdout.flush;
94     insts = clear_opt(insts);
95     Counts counts1 = count_instructions(insts);
96     write_inst_count(counts1);
97     writefln("Removed %s instructions.\n", get_total_insts(counts0) - get_total_insts(counts1));
98     
99     writeln("UnrolledLoop optimization...");
100     stdout.flush;
101     insts = unroll_opt(insts, true);
102     Counts counts2 = count_instructions(insts);
103     write_inst_count(counts2);
104     writeln;
105 
106     writeln("If optimization...");
107     stdout.flush;
108     insts = if_opt(insts);
109     Counts counts3 = count_instructions(insts);
110     write_inst_count(counts3);
111     writeln;
112 
113     writeln("BalancedLoop optimization...");
114     stdout.flush;
115     insts = balanced_opt(insts);
116     Counts counts4 = count_instructions(insts);
117     write_inst_count(counts4);
118     writefln("Removed %s instructions.\n\n", get_total_insts(counts3) - get_total_insts(counts4));
119     
120     stdout.flush;
121 
122     auto parser = new IRParser(insts);
123     auto cfg = parser.parse;
124 
125     char[] file_out;
126 
127     if(options.intermediate != "") {
128         foreach(inst; insts) {
129             file_out ~= inst.to_string ~ "\n";
130         }
131         std.file.write(options.intermediate, file_out);
132     }
133 
134     if(options.graph != "") {
135         file_out.length = 0;
136         file_out ~= "digraph {\n";
137 
138         bool[CFG] cfgs;
139         collect_nodes(cfg, cfgs);
140 
141         file_out ~= "    _%s [color=red];\n".format(cast(void*) cfg);
142         foreach(cfg, _; cfgs) {
143             if(auto basic_cfg = cast(BasicBlockCFG) cfg) {
144                 char[] label = cast(char[]) "%s\\n".format(typeid(cfg).toString);
145                 
146                 foreach(inst; basic_cfg.insts) {
147                     label ~= "%s\\n".format(inst.to_string);
148                 }
149 
150                 file_out ~= "    _%s [label=\"%s\"];\n".format(cast(void*) cfg, cast(string) label);
151             } else if(auto move_cfg = cast(MoveCFG) cfg) {
152                 char[] label = cast(char[]) "%s\\n%s".format(typeid(cfg).toString, move_cfg.inst.to_string(0));
153                 file_out ~= "    _%s [label=\"%s\"];\n".format(cast(void*) cfg, cast(string) label);
154             } else if(auto if_cfg = cast(IfCFG) cfg) {
155                 file_out ~= "    _%s [label=\"%s\\n%s\"];\n".format(cast(void*) cfg, typeid(cfg).toString, if_cfg.type);
156             }
157         }
158         
159         foreach(cfg, _; cfgs) {
160             if(auto basic_cfg = cast(BasicBlockCFG) cfg) {
161                 auto basic_ptr = cast(void*) basic_cfg;
162                 auto next_ptr = cast(void*) basic_cfg.outgoing;
163                 file_out ~= "    _%s -> _%s [splines=ortho];\n".format(basic_ptr, next_ptr);
164             } else if(auto if_cfg = cast(IfCFG) cfg) {
165                 auto if_ptr = cast(void*) if_cfg;
166                 auto true_ptr = cast(void*) if_cfg.outgoing_true;
167                 auto false_ptr = cast(void*) if_cfg.outgoing_false;
168                 file_out ~= "    _%s -> _%s [label=\"T\"] [splines=ortho];\n".format(if_ptr, true_ptr);
169                 file_out ~= "    _%s -> _%s [label=\"F\"] [splines=ortho];\n".format(if_ptr, false_ptr);
170             } else if(auto move_cfg = cast(MoveCFG) cfg) {
171                 auto move_ptr = cast(void*) move_cfg;
172                 auto next_ptr = cast(void*) move_cfg.outgoing;
173                 file_out ~= "    _%s -> _%s [splines=ortho];\n".format(move_ptr, next_ptr);
174             }
175         }
176 
177         file_out ~= "}";
178         std.file.write(options.graph, file_out);
179     }
180 
181     if(options.compile_run == OptionFlag.yes || options.only_compile == OptionFlag.yes) {
182         file_out.length = 0;
183         file_out ~=
184 "#include <stdio.h>
185 char t[30000];
186 char* p = t;
187 int main(int argc, const char* argv[]) {\n";
188         
189         file_out ~= "goto _%s;\n".format(cast(void*) cfg);
190 
191         bool[CFG] cfgs;
192         collect_nodes(cfg, cfgs);
193 
194         foreach(cfg, _; cfgs) {
195             file_out ~= "%s\n".format(cfg.compile);
196         }
197         
198         file_out ~= "    _null:\n";
199         file_out ~= "    return 0;\n";
200         file_out ~= "}";
201         std.file.write("dfuck_temp.c", file_out);
202 
203         switch(options.compiler) {
204             case "gcc":
205                 auto com = "gcc -O3 dfuck_temp.c -o dfuck_temp.exe";
206                 writefln("Running %s", com);
207                 stdout.flush;
208                 executeShell(com);
209                 break;
210             default:
211                 writefln("Compiler %s is not supported.", options.compiler);
212                 return -1;
213         }
214 
215         if(options.compile_run == OptionFlag.yes) {
216             auto pid = spawnProcess("./dfuck_temp.exe");
217             wait(pid);
218             executeShell("rm dfuck_temp.c && rm dfuck_temp.exe");
219         }
220     }
221 
222     if(options.interpret == OptionFlag.yes) {
223         auto vm = new VM;
224 
225         while(cfg !is null) {
226             cfg = cfg.run(vm);
227         }
228     }
229 
230     return 0;
231 }
232 
233 void write_inst_count(Counts counts) {
234     foreach(k, v; counts) {
235         writefln("%s: %s", k, v);
236     }
237 }
238 
239 uint get_total_insts(Counts counts) {
240     return counts.byValue.sum;
241 }
242 
243 void collect_nodes(CFG cfg, ref bool[CFG] collected) {
244     if(cfg in collected || cfg is null) return;
245     collected[cfg] = true;
246 
247     if(auto basic_cfg = cast(BasicBlockCFG) cfg) {
248         collect_nodes(basic_cfg.outgoing, collected);
249     } else if(auto if_cfg = cast(IfCFG) cfg) {
250         collect_nodes(if_cfg.outgoing_true, collected);
251         collect_nodes(if_cfg.outgoing_false, collected);
252     } else if(auto move_cfg = cast(MoveCFG) cfg) {
253         collect_nodes(move_cfg.outgoing, collected);
254     }
255 }