1 /** 2 * Provides common functions during language conversion. 3 */ 4 module codebuilder.structure; 5 6 import std.algorithm; 7 import std.conv; 8 import std.range; 9 import std.regex; 10 11 /** 12 * Specifies what string will be prepended for each indentation level. 13 */ 14 string indentation = "\t"; 15 unittest { 16 auto code = CodeBuilder(1); 17 code.modifyLine = false; 18 scope(exit) indentation = "\t"; 19 indentation = " "; 20 code.put("Hello"); 21 assert(code.finalize().startsWith(" ")); 22 } 23 24 /** 25 * Converts a numeric to an indent string 26 */ 27 string indented(int indentCount) { 28 assert(indentCount > -1, "Indentation can never be less than 0."); 29 if(__ctfe) 30 return to!(string)(repeat(" ", indentCount).join.array); 31 else 32 return to!(string)(repeat(indentation, indentCount).join.array); 33 } unittest { 34 static assert(indented(2) == " "); 35 assert(indented(2) == "\t\t"); 36 } 37 38 /** 39 * Specifies how indentation should be applied to this line. 40 * 41 * open - specifies that this opens a block and should indent 42 * future lines. 43 * 44 * close - specifies that this closes a block, itself and 45 * future lines should indent one less. 46 * 47 * none - perform not modification to the indentation. 48 */ 49 enum Indent { none, open, close = 4 } 50 51 /** 52 * An output range that provides extended functionality for 53 * constructing a well formatted string of code. 54 * 55 * CodeBuilder has three main operations. 56 * 57 * $(UL 58 * $(LI put - Applies string directly to desired output) 59 * $(LI push - Stacks string for later) 60 * $(LI build - Constructs a sequence of strings) 61 * ) 62 * 63 * $(H1 put) 64 * 65 * The main operation for placing code into the buffer. This will sequentially 66 * place code into the buffer. Similar operations are provided for the other 67 * operations. 68 * 69 * $(D rawPut) will place the string without indentation added. 70 * 71 * One can place the current indentation level without code by calling put(""); 72 * 73 * $(H1 push) 74 * 75 * This places code onto a stack. $(B pop) can be used to put the code into the 76 * buffer. 77 * 78 * $(H1 build) 79 * 80 * Building code in a sequence can be pushed onto the stack, or saved to be put 81 * into the buffer later. 82 */ 83 struct CodeBuilder { 84 struct Operation { 85 string text; 86 Indent indent; 87 bool raw; 88 string file; 89 int line; 90 } 91 private Operation[] upper; 92 private Operation[][] lower; 93 94 int indentCount; 95 bool modifyLine = true; 96 97 /** 98 */ 99 static CodeBuilder opCall(int indentedCount) { 100 CodeBuilder b; 101 b.indentCount = indentedCount; 102 return b; 103 } 104 105 /** 106 * Places str into the buffer. 107 * 108 * put("text", Indent.open); will indent "text" at the current level and 109 * future code will be indented one level. 110 * 111 * put("other", Indent.close); will indent "other" at one less the current 112 * indentation. 113 * 114 * put("}{", Indent.close | Indent.open) will indent "}{" at one less the 115 * current indentation and continue indentation for future code. 116 * 117 * rawPut provides the same operations but does not include the current 118 * indentation level. 119 * 120 * To place indentation but no other code use put(""); 121 * 122 * To reduce indentation without inserting code use put(Indent.close); 123 */ 124 void put(string str, Indent indent = Indent.none, string f = __FILE__, int l = __LINE__) { 125 version(ModifyLine) if(modifyLine) 126 upper ~= Operation("#line " ~ l.to!string ~ " \"" ~ f ~ "\"\n", Indent.none, true); 127 upper ~= Operation(str, indent); 128 } unittest { 129 /// Line numbers are added when using put 130 auto code = CodeBuilder(0); 131 132 mixin(`#line 0 "fake.file" 133 code.put("line");`); 134 135 auto ans = code.finalize(); 136 137 assert(ans == "#line 0 \"fake.file\"\nline", ans); 138 } 139 140 141 /// ditto 142 void rawPut(string str, Indent indent = Indent.none, string f = __FILE__, int l = __LINE__) { 143 upper ~= Operation(str, indent, true); 144 } unittest { 145 /// Raw input does not insert indents 146 auto code = CodeBuilder(1); 147 148 code.rawPut("No indentation"); 149 150 auto ans = code.finalize(); 151 152 assert(ans == "No indentation", ans); 153 } unittest { 154 /// Call finalization twice 155 auto code = CodeBuilder(1); 156 code.modifyLine = false; 157 158 code.rawPut("No indentation\n"); 159 code.put("{\n", Indent.open); 160 161 code.finalize(); 162 auto ans = code.finalize(); 163 164 assert(ans == "No indentation\n\t{\n", ans); 165 } 166 167 /// ditto 168 void put(Indent indent) { 169 assert(!(indent & Indent.close & Indent.open), "No-op indent"); 170 upper ~= Operation(null, indent); 171 } unittest { 172 auto code = CodeBuilder(0); 173 code.modifyLine = false; 174 code.put("No Indent"); 175 code.put(Indent.open); 176 code.put("Indented"); 177 assert(!code.finalize().startsWith(indentation)); 178 assert(code.finalize() == "No Indent\tIndented", code.finalize()); 179 } 180 181 /** 182 */ 183 void put(CodeBuilder build) { 184 build.finalize(); 185 upper ~= build.upper; 186 } unittest { 187 // CodeBuilders can be added to each other 188 auto code = CodeBuilder(5); 189 code.modifyLine = false; 190 code.put("a\n", Indent.open); 191 code.put("b\n"); 192 code.put("c\n", Indent.close); 193 auto code2 = CodeBuilder(0); 194 code2.modifyLine = false; 195 code2.put("{\n", Indent.open); 196 code2.push("}\n", Indent.close); 197 code2.put(code); 198 assert(code2.finalize() == "{\n\ta\n\t\tb\n\tc\n}\n", code2.finalize()); 199 } 200 201 /** 202 * Places str onto a stack that can latter be popped into 203 * the current buffer. 204 * 205 * See put for specifics. 206 */ 207 void push(string str, Indent indent = Indent.close, string f = __FILE__, int l = __LINE__) { 208 lower ~= [Operation(str, indent, false, f, l)]; 209 } unittest { 210 auto code = CodeBuilder(0); 211 code.modifyLine = false; 212 code.push("}\n"); 213 code.push("b\n", Indent.none); 214 code.push("{\n", Indent.open); 215 assert(code.finalize() == "{\n\tb\n}\n", code.finalize()); 216 } 217 218 /// ditto 219 void push(Indent indent, string f = __FILE__, int l = __LINE__) { 220 lower ~= [Operation(null, indent, false, f, l)]; 221 } unittest { 222 auto code = CodeBuilder(0); 223 code.modifyLine = false; 224 code.push("b\n", Indent.none); 225 code.push(Indent.open); 226 code.push("{\n", Indent.none); 227 assert(code.finalize() == "{\n\tb\n", code.finalize()); 228 } 229 230 /// ditto 231 void rawPush(string str = null, Indent indent = Indent.close, string f = __FILE__, int l = __LINE__) { 232 lower ~= [Operation(str, indent, true, f, l)]; 233 } unittest { 234 auto code = CodeBuilder(5); 235 code.modifyLine = false; 236 code.rawPush("b\n", Indent.none); 237 code.push("{\n", Indent.open); 238 assert(code.finalize() == "\t\t\t\t\t{\nb\n", code.finalize()); 239 } unittest { 240 auto code = CodeBuilder(0); 241 code.modifyLine = false; 242 code.put("{\n", Indent.open); 243 if(true) 244 code.rawPush(); 245 else 246 code.push("b\n", Indent.none); 247 code.pop(); 248 assert(code.finalize() == "{\n", code.finalize()); 249 } 250 251 252 /** 253 * Places the top stack item into the buffer. 254 */ 255 void pop() { 256 assert(!lower.empty(), "Can't pop empty buffer"); 257 258 foreach(op; lower.back()) 259 if(op.raw) 260 rawPut(op.text, op.indent, op.file, op.line); 261 else 262 put(op.text, op.indent, op.file, op.line); 263 264 lower.popBack(); 265 if(!__ctfe) assumeSafeAppend(lower); 266 } unittest { 267 auto code = CodeBuilder(0); 268 code.modifyLine = false; 269 code.put("{\n", Indent.open); 270 code.push("}\n"); 271 code.pop(); 272 code.put("b\n"); 273 assert(code.finalize() == "{\n}\nb\n", code.finalize()); 274 } 275 276 /** 277 */ 278 void push(CodeBuilder build) { 279 build.finalize(); 280 lower ~= build.upper; 281 } unittest { 282 // CodeBuilders can be added to each other 283 auto code = CodeBuilder(5); 284 code.modifyLine = false; 285 code.put("a\n", Indent.open); 286 code.put("b\n"); 287 code.put("c\n", Indent.close); 288 auto code2 = CodeBuilder(0); 289 code2.modifyLine = false; 290 code2.put("{\n", Indent.open); 291 code2.push("}\n", Indent.close); 292 code2.push(code); 293 assert(code2.finalize() == "{\n\ta\n\t\tb\n\tc\n}\n", code2.finalize()); 294 } 295 296 /** 297 * Returns the buffer, applying an code remaining on the stack. 298 */ 299 string finalize() { 300 while(!lower.empty) 301 pop(); 302 string ans; 303 int indentCount = indentCount; 304 foreach(op; upper.save) { 305 if(op.indent & Indent.close) indentCount--; 306 if(!op.raw) 307 ans ~= indented(indentCount); 308 ans ~= op.text; 309 if(op.indent & Indent.open) indentCount++; 310 } 311 return ans; 312 } 313 } 314 315 unittest { 316 // Compiletime capable 317 string run() { 318 auto code = CodeBuilder(1); 319 code.modifyLine = false; 320 code.put("Hello"); 321 code.rawPut(" World"); 322 return code.finalize(); 323 } 324 325 static assert(run() == " Hello World", run()); 326 } 327 328 /// Example usage 329 unittest { 330 auto indentCount = 0; 331 auto code = CodeBuilder(indentCount); 332 code.put("void main() {\n", Indent.open); 333 code.push("}\n"); 334 code.put("int a = 5;\n"); 335 code.put("multiply(a);\n"); 336 code.pop(); 337 338 code.put("\nvoid multiply(int v) {\n", Indent.open); 339 code.push("}\n"); 340 code.put("try {\n", Indent.open); 341 auto catchblock = CodeBuilder(1); 342 catchblock.put("} catch(Exception e) {\n", Indent.close | Indent.open); 343 catchblock.put("import std.stdio;\n"); 344 catchblock.put("writeln(`Exception is bad but I don't care.`);\n"); 345 catchblock.put("}\n", Indent.close); 346 code.push(catchblock); 347 348 code.put("return v * "); 349 code.rawPut(76.to!string ~ ";\n"); 350 }