1 2 // Copyright Tim Schendekehl 2023. 3 // Distributed under the Boost Software License, Version 1.0. 4 // (See accompanying file LICENSE_1_0.txt or copy at 5 // https://www.boost.org/LICENSE_1_0.txt) 6 7 module dparsergen.generator.codewriter; 8 import std.algorithm; 9 import std.array; 10 import std.conv; 11 import std.string; 12 13 struct CodeWriter 14 { 15 private char[] buffer; 16 size_t dataSize; 17 size_t indent; 18 string indentStr = "\t"; 19 string customIndent; 20 bool inLine; 21 size_t currentLine; 22 size_t currentOffset; 23 size_t lineStart; 24 25 void ensureAddable(size_t n) 26 { 27 if (dataSize + n > buffer.length) 28 { 29 size_t newSize = dataSize; 30 if (newSize < 1024) 31 newSize = 1024; 32 while (dataSize + n > newSize) 33 newSize *= 2; 34 buffer.length = newSize; 35 } 36 } 37 38 void put(string data) 39 { 40 ensureAddable(data.length); 41 buffer[dataSize .. dataSize + data.length] = data; 42 dataSize += data.length; 43 } 44 45 void put(char c) 46 { 47 ensureAddable(1); 48 buffer[dataSize] = c; 49 dataSize++; 50 } 51 52 const(char)[] data() const 53 { 54 return buffer[0 .. dataSize]; 55 } 56 57 void endLine() 58 { 59 if (!inLine) 60 { 61 lineStart = data.length; 62 } 63 put('\n'); 64 inLine = false; 65 currentLine++; 66 currentOffset = 0; 67 } 68 69 void startLine(bool afterNewline = false) 70 { 71 if (!inLine) 72 { 73 if (!afterNewline) 74 { 75 lineStart = data.length; 76 } 77 foreach (i; 0 .. indent) 78 put(indentStr); 79 currentOffset += indent; 80 put(customIndent); 81 currentOffset += customIndent.length; 82 inLine = true; 83 } 84 } 85 86 ref CodeWriter write(T...)(T args) return 87 { 88 bool afterNewline; 89 foreach (arg; args) 90 { 91 foreach (char c; text(arg)) 92 { 93 if (c == '\n') 94 { 95 endLine(); 96 afterNewline = true; 97 } 98 else 99 { 100 startLine(afterNewline); 101 put(c); 102 currentOffset++; 103 } 104 } 105 } 106 return this; 107 } 108 109 ref CodeWriter writeln(T...)(T args) return 110 { 111 write(args); 112 endLine(); 113 return this; 114 } 115 116 ref CodeWriter incIndent(size_t n = 1) return 117 { 118 indent += n; 119 return this; 120 } 121 122 ref CodeWriter decIndent(size_t n = 1, string func = __FUNCTION__, 123 string file = __FILE__, size_t line = __LINE__) return 124 { 125 assert(indent >= n, text("CodeWrite.decIndent indent=", indent, " n=", 126 n, " ", func, " (", file, ":", line, ")")); 127 indent -= n; 128 return this; 129 } 130 } 131 132 void splitInput(alias onNormalText, alias onMacro)(string input) 133 { 134 uint status; 135 size_t start; 136 size_t varstart; 137 foreach (i, char c; input) 138 { 139 if (status == 0) 140 { 141 if (c == '$') 142 { 143 status = 1; 144 varstart = i; 145 } 146 } 147 else if (status == 1) 148 { 149 if (c == '(') 150 { 151 status = 2; 152 } 153 else 154 { 155 status = 0; 156 } 157 } 158 else if (status == 2) 159 { 160 if (c == ')') 161 { 162 status = 0; 163 onNormalText(input[start .. varstart]); 164 onMacro(input[varstart + 2 .. i], ""); 165 start = i + 1; 166 } 167 if (c == '(') 168 { 169 status++; 170 } 171 } 172 else if (status > 2) 173 { 174 if (c == '(') 175 status++; 176 if (c == ')') 177 status--; 178 } 179 else 180 assert(false); 181 } 182 onNormalText(input[start .. $]); 183 } 184 185 string escapeDString(string s) 186 { 187 char[] buffer; 188 buffer.length = 2 * s.length; 189 size_t i; 190 foreach (char c; s) 191 { 192 if (c == '\"') 193 { 194 buffer[i] = '\\'; 195 i++; 196 buffer[i] = '\"'; 197 i++; 198 } 199 else if (c == '\t') 200 { 201 buffer[i] = '\\'; 202 i++; 203 buffer[i] = 't'; 204 i++; 205 } 206 else if (c == '\\') 207 { 208 buffer[i] = '\\'; 209 i++; 210 buffer[i] = '\\'; 211 i++; 212 } 213 else 214 { 215 buffer[i] = c; 216 i++; 217 } 218 } 219 return buffer[0 .. i].idup; 220 } 221 222 size_t startWhitespace(string s, size_t spacesPerTab = 4) 223 { 224 size_t numWS; 225 size_t spaces; 226 foreach (char c; s) 227 { 228 if (c == '\t') 229 { 230 numWS++; 231 spaces = 0; 232 } 233 else if (c == ' ') 234 { 235 spaces++; 236 if (spaces == spacesPerTab) 237 { 238 numWS++; 239 spaces = 0; 240 } 241 } 242 else 243 break; 244 } 245 return numWS; 246 } 247 248 string removeStartWhitespace(string s, size_t startWS, size_t spacesPerTab = 4) 249 { 250 size_t numWS; 251 size_t spaces; 252 foreach (i, char c; s) 253 { 254 if (numWS >= startWS) 255 break; 256 if (c == '\t') 257 { 258 numWS++; 259 s = s[1 .. $]; 260 spaces = 0; 261 } 262 else if (c == ' ') 263 { 264 spaces++; 265 if (spaces == spacesPerTab) 266 { 267 numWS++; 268 s = s[spaces .. $]; 269 spaces = 0; 270 } 271 } 272 else 273 break; 274 } 275 return s; 276 } 277 278 string intToStr(long l) 279 { 280 char[20] buffer; 281 size_t i; 282 bool negative; 283 if (l < 0) 284 { 285 negative = true; 286 l = -l; 287 } 288 while (l != 0) 289 { 290 i++; 291 buffer[$ - i] = '0' + l % 10; 292 l = l / 10; 293 } 294 if (negative) 295 { 296 i++; 297 buffer[$ - 1] = '-'; 298 } 299 return buffer[$ - i .. $].idup; 300 } 301 302 string genCode(string code, string str, string callingFunction = __FUNCTION__, 303 string callingFilename = __FILE__, size_t callingLine = __LINE__) 304 { 305 CodeWriter app; 306 string[] lines = str.splitLines; 307 if (lines[0] == "") 308 { 309 lines = lines[1 .. $]; 310 callingLine++; 311 app.put("\n"); 312 } 313 if (lines[$ - 1].all!((c) => c == '\t' || c == ' ')) 314 lines = lines[0 .. $ - 1]; 315 316 size_t minNumWhitespace = size_t.max; 317 foreach (i, l; lines) 318 { 319 if (l.all!((c) => c == '\t' || c == ' ')) 320 continue; 321 size_t numWhitespace = l.startWhitespace; 322 if (numWhitespace < minNumWhitespace) 323 minNumWhitespace = numWhitespace; 324 } 325 326 static struct IgnoreInfo 327 { 328 size_t indent; 329 size_t lineNr; 330 } 331 332 Appender!(IgnoreInfo[]) ignoreInfos; 333 334 size_t currentStartWhitespace = 0; 335 bool inCodeWriteStmt = false; 336 void startCode() 337 { 338 assert(!inCodeWriteStmt); 339 inCodeWriteStmt = true; 340 app.put(code); 341 } 342 343 void endCode() 344 { 345 if (!inCodeWriteStmt) 346 return; 347 inCodeWriteStmt = false; 348 app.put(text(";\n")); 349 } 350 351 void adjustStartWhitespace(size_t n, size_t debugLine, bool doEndCode = false, 352 bool doStartCode = false) 353 { 354 assert(n >= ignoreInfos.data.length, text("n=", n, " ignoreWhitespace=", 355 ignoreInfos.data.length, " line ", debugLine, ":\n", lines[debugLine])); 356 if (n < ignoreInfos.data.length) 357 { 358 n = ignoreInfos.data.length; 359 } 360 n -= ignoreInfos.data.length; 361 if (n < currentStartWhitespace) 362 { 363 if (!inCodeWriteStmt) 364 app.put(code); 365 app.put(text(".decIndent(", intToStr(currentStartWhitespace - n), ", \"", callingFunction, 366 "\", \"", callingFilename.escapeDString, "\", ", callingLine + debugLine, ")")); 367 if (!inCodeWriteStmt) 368 app.put(";"); 369 } 370 if (doEndCode) 371 endCode(); 372 if (doStartCode) 373 startCode(); 374 if (n > currentStartWhitespace) 375 { 376 if (!inCodeWriteStmt) 377 app.put(code); 378 app.put(text(".incIndent(", intToStr(n - currentStartWhitespace), ")")); 379 if (!inCodeWriteStmt) 380 app.put(";"); 381 } 382 currentStartWhitespace = n; 383 } 384 385 foreach (lineNr, l; lines) 386 { 387 if (l.startWhitespace < minNumWhitespace) 388 { 389 foreach (char c; l) 390 assert(c == '\t' || c == ' '); 391 endCode(); 392 startCode(); 393 app.put(text(".writeln()")); 394 continue; 395 } 396 auto line = l.removeStartWhitespace(minNumWhitespace); 397 auto st = line.startWhitespace; 398 line = line.removeStartWhitespace(st); 399 if (line.startsWith("$$")) 400 { 401 string errorMessage; 402 foreach (char c; line) 403 { 404 if (c == '}') 405 { 406 assert(ignoreInfos.data.length, text(callingFunction, " (", 407 callingFilename, ":", callingLine + lineNr, ")")); 408 if (st != ignoreInfos.data[$ - 1].indent) 409 { 410 errorMessage = text("Braces don't match: ", callingFunction, 411 " indent = ", ignoreInfos.data[$ - 1].indent, 412 " (", callingFilename, ":", 413 callingLine + ignoreInfos.data[$ - 1].lineNr, ")", " indent = ", 414 st, " (", callingFilename, ":", callingLine + lineNr, ")"); 415 st = ignoreInfos.data[$ - 1].indent; 416 } 417 ignoreInfos.shrinkTo(ignoreInfos.data.length - 1); 418 } 419 } 420 adjustStartWhitespace(st, lineNr); 421 endCode(); 422 if (errorMessage.length) 423 app.put(text("pragma(msg, \"", errorMessage.escapeDString, "\");")); 424 app.put(line[2 .. $]); 425 app.put("\n"); 426 foreach (char c; line) 427 { 428 if (c == '{') 429 ignoreInfos.put(IgnoreInfo(st, lineNr)); 430 } 431 } 432 else 433 { 434 adjustStartWhitespace(st, lineNr, true, true); 435 bool addNewLine = true; 436 if (line.endsWith(" _")) 437 { 438 addNewLine = false; 439 line = line[0 .. $ - 3]; 440 } 441 442 line.splitInput!( 443 (t) { app.put(text(".write(\"", t.escapeDString, "\")")); }, 444 (m, p) { app.put(text(".write(", m, ")")); }); 445 if (addNewLine) 446 app.put(text(".writeln()")); 447 } 448 } 449 adjustStartWhitespace(0, lines.length - 1, true, false); 450 451 return app.data.idup; 452 }