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 }