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