1 module more.common; 2 3 import std.ascii; 4 import std.conv; 5 import std.range; 6 import std.stdio; 7 import std.string; 8 import std.bitmanip; 9 import std.format; 10 11 import core.exception; 12 13 import std.c.string : memmove; 14 15 version(unittest) 16 { 17 import std.array; 18 } 19 20 21 void implement(string feature = "", string file = __FILE__, int line = __LINE__) { 22 string msg = "not implemented"; 23 if(feature.length) { 24 msg = feature~' '~msg; 25 } 26 throw new Exception(msg, file, line); 27 } 28 29 float stdTimeMillis(long stdTime) 30 { 31 return cast(float)stdTime / 10000f; 32 } 33 string prettyTime(float millis) 34 { 35 if (millis < 0) return "-"~prettyTime(-millis); 36 37 if (millis < 1000) 38 return to!string(millis)~" millis"; 39 if (millis < 60000) 40 return to!string(millis / 1000)~" seconds"; 41 if (millis < 3600000) 42 return to!string(millis / 60000)~" minutes"; 43 if (millis < 86400000) 44 return to!string(millis / 3600000)~" hours"; 45 return to!string(millis / 86400000)~" days"; 46 } 47 48 template isChar(T) { 49 static if(is(T == char) || 50 is(T == const char) || 51 is(T == immutable char)) 52 enum isChar = true; 53 else 54 enum isChar = false; 55 } 56 57 template ArrayElementType(Type) 58 { 59 static if(is(Type : E[], E)) 60 alias ArrayElementType = E; 61 else 62 alias ArrayElementType = void; 63 } 64 template TypeTupleStrings(alias sep, T...) 65 { 66 static if(T.length == 0) { 67 immutable string TypeTupleStrings = ""; 68 } else static if(T.length == 1) { 69 immutable string TypeTupleStrings = T[0].stringof; 70 } else { 71 immutable string TypeTupleStrings = T[0].stringof ~ sep ~ TypeTupleStrings!(sep, T[1..$]); 72 } 73 } 74 template TupleRange(int from, int to) if (from <= to) { 75 static if (from >= to) { 76 alias TupleRange = TypeTuple!(); 77 } else { 78 alias TupleRange = TypeTuple!(from, TupleRange!(from + 1, to)); 79 } 80 } 81 82 public interface IDisposable 83 { 84 void dispose(); 85 } 86 87 88 enum outerBar = "========================================="; 89 enum innerBar = "-----------------------------------------"; 90 void startTest(string name) 91 { 92 writeln(outerBar); 93 writeln(name, ": Start"); 94 writeln(innerBar); 95 } 96 void endFailedTest(string name) 97 { 98 writeln(innerBar); 99 writeln(name, ": Failed"); 100 writeln(outerBar); 101 } 102 void endPassedTest(string name) 103 { 104 writeln(innerBar); 105 writeln(name, ": Passed"); 106 writeln(outerBar); 107 } 108 template scopedTest(string name) { 109 enum scopedTest = 110 "startTest(\""~name~"\");"~ 111 "scope(failure) {stdout.flush();endFailedTest(\""~name~"\");}"~ 112 "scope(success) endPassedTest(\""~name~"\");"; 113 } 114 void writeSection(string name) 115 { 116 writeln(innerBar); 117 writeln(name); 118 writeln(innerBar); 119 } 120 void assertEqual(string expected, string actual) pure 121 { 122 if(expected != actual) { 123 throw new AssertError(format("Expected %s Actual %s", 124 expected ? ('"' ~ expected ~ '"') : "<null>", 125 actual ? ('"' ~ actual ~ '"') : "<null>")); 126 } 127 } 128 129 template Unroll(alias CODE, alias N, alias SEP="") 130 { 131 enum NEW_CODE = replace(CODE, "%", "%1$d"); 132 enum Unroll = iota(N).map!(i => format(NEW_CODE, i)).join(SEP); 133 } 134 template UnrollTuple(alias CODE, alias SEP, T...) 135 { 136 enum NEW_CODE = replace(CODE, "%", "%1$d"); 137 enum UnrollTuple = T.map!(t => format(NEW_CODE, t)).join(SEP); 138 } 139 140 141 public alias DataHandler = void delegate(ubyte[] data); 142 public alias StringHandler = void delegate(string data); 143 public interface IDataHandler : IDisposable 144 { 145 void handleData(ubyte[] data); 146 } 147 148 string debugChar(char c) 149 { 150 switch(c) { 151 case '\r':return r"'\r'"; 152 case '\n':return r"'\n'"; 153 default: 154 if(isPrintable(c)) return "'" ~ c ~ "'"; 155 return to!string(cast(uint)c); 156 } 157 } 158 159 void dcpy(void* destination, const ubyte[] source) pure nothrow 160 { 161 (cast(ubyte*)destination)[0..source.length][] = source; 162 } 163 void dcpy(void* destination, const char[] source) pure nothrow 164 { 165 (cast(char*)destination)[0..source.length][] = source; 166 } 167 /+ 168 void dcpy(Source)(void * destination, Source source) pure nothrow 169 { 170 (cast(ubyte*)destination)[0..source.length][] = source; 171 } 172 +/ 173 174 /+ 175 void trimNewline(ref inout(char)[] line) { 176 // remove ending characters 177 while(true) { 178 if( line.length <= 0 || (line[$-1] != '\n' && line[$-1] != '\r') ) return; 179 line = line[0..$-1]; 180 } 181 } 182 +/ 183 184 void trimNewline(inout(char)[]* line) { 185 // remove ending characters 186 while(true) { 187 if( (*line).length <= 0 || ((*line)[$-1] != '\n' && (*line)[$-1] != '\r') ) return; 188 (*line) = (*line)[0..$-1]; 189 } 190 } 191 192 193 version(unittest_common) unittest 194 { 195 mixin(scopedTest!("trimNewline")); 196 197 void testTrimNewline(string s, string expected) { 198 //writef("'%s' => ", escape(s)); 199 trimNewline(&s); 200 //writefln("'%s'", escape(s)); 201 assert(expected == s); 202 } 203 204 testTrimNewline("", ""); 205 testTrimNewline("\r", ""); 206 testTrimNewline("\n", ""); 207 testTrimNewline("1234", "1234"); 208 testTrimNewline("abcd \n", "abcd "); 209 testTrimNewline("hello\r\r\r\n\n\r\r\n", "hello"); 210 211 } 212 213 214 void readFullSize(File file, char[] buffer) { 215 while(true) { 216 char[] lastRead = file.rawRead(buffer); 217 if(lastRead.length == buffer.length) return; 218 if(lastRead.length == 0) throw new Exception("File did not have enough data left to fill the buffer"); 219 buffer = buffer[lastRead.length..$]; 220 } 221 } 222 223 // returns true on success, false if the file reached EOF 224 bool tryReadFullSize(File file, char[] buffer) { 225 while(true) { 226 char[] lastRead = file.rawRead(buffer); 227 if(lastRead.length == buffer.length) return true; 228 if(lastRead.length == 0) return false; 229 buffer = buffer[lastRead.length..$]; 230 } 231 } 232 233 alias ubyte[] function(ubyte[] oldBuffer, size_t newLength, bool copy) Allocator; 234 alias void function(ubyte[] buffer) Deallocator; 235 236 ubyte[] defaultAllocator(ubyte[] oldBuffer, size_t newLength, bool copy) 237 { 238 oldBuffer.length = newLength; 239 return oldBuffer; 240 } 241 void defaultDeallocator(ubyte[] buffer) 242 { 243 // do nothing, the garbage collector handles it 244 } 245 alias CustomLineParser!(defaultAllocator, defaultDeallocator) LineParser; 246 247 template CustomLineParser(A...) 248 { 249 struct CustomLineParser 250 { 251 size_t expandLength; 252 253 ubyte[] buffer; 254 255 ubyte *lineStart; // Points to the start of the next line 256 size_t lineCheckOffset; // Offset from lineStart to the next character to check 257 size_t lineDataLimit; // Length from lineStart to the end of the data 258 259 public this(ubyte[] buffer, size_t expandLength = 256) 260 { 261 this.expandLength = expandLength; 262 263 this.buffer = buffer; 264 this.lineStart = buffer.ptr; 265 this.lineCheckOffset = 0; 266 this.lineDataLimit = 0; 267 } 268 @property public void put(Data)(Data data) if ( (*data.ptr).sizeof == 1 ) 269 { 270 debug writefln("[debug] ---> put(%4d bytes) : start %d check %d limit %d", 271 data.length, (lineStart - buffer.ptr), lineCheckOffset, lineDataLimit); 272 273 size_t newDataOffset = (lineStart - buffer.ptr) + lineDataLimit; 274 size_t bytesLeft = buffer.length - newDataOffset; 275 276 if(bytesLeft < data.length) { 277 size_t lineStartOffset = lineStart - buffer.ptr; 278 size_t defaultExpandLength = this.buffer.length + this.expandLength; 279 size_t neededLength = newDataOffset + data.length; 280 281 //this.buffer.length = (neededLength >= defaultExpandLength) ? neededLength : defaultExpandLength; 282 ubyte[] newBuffer = A[0](this.buffer, (neededLength >= defaultExpandLength) ? neededLength : defaultExpandLength, true); 283 A[1](this.buffer); 284 this.buffer = newBuffer; 285 286 lineStart = this.buffer.ptr + lineStartOffset; 287 } 288 289 dcpy(lineStart + lineDataLimit, data); 290 lineDataLimit += data.length; 291 292 debug writefln("[debug] <--- put : start %d check %d limit %d", 293 (lineStart - buffer.ptr), lineCheckOffset, lineDataLimit); 294 } 295 296 /// Returns null when it has parsed all the lines that have been added 297 public ubyte[] getLine() 298 { 299 debug writefln("[debug] ---> getLine : start %d check %d limit %d", 300 (lineStart - buffer.ptr), lineCheckOffset, lineDataLimit); 301 302 while(lineCheckOffset < lineDataLimit) { 303 debug { 304 char c = lineStart[lineCheckOffset]; 305 writefln("[debug] start[%s] = %s", lineCheckOffset, debugChar(c)); 306 } 307 if(lineStart[lineCheckOffset] == '\n') { 308 ubyte[] line; 309 if(lineCheckOffset > 0 && lineStart[lineCheckOffset - 1] == '\r') { 310 line = lineStart[0..lineCheckOffset - 1]; 311 } else { 312 line = lineStart[0..lineCheckOffset]; 313 } 314 lineCheckOffset++; 315 lineDataLimit -= lineCheckOffset; 316 if(lineDataLimit > 0) { 317 lineStart = lineStart + lineCheckOffset; 318 } else { 319 lineStart = buffer.ptr; 320 } 321 lineCheckOffset = 0; 322 323 debug writefln("[debug] <--- getLine line : start %d check %d limit %d", 324 (lineStart - buffer.ptr), lineCheckOffset, lineDataLimit); 325 return line; 326 } 327 lineCheckOffset++; 328 } 329 330 if(lineStart == buffer.ptr) { 331 debug writeln("[debug] No data"); 332 return null; 333 } 334 335 assert(lineDataLimit > 0); 336 337 // 338 // Move remaining data to the beginning of the buffer 339 // 340 debug writeln("[debug] Moving line data to beginning of buffer"); 341 memmove(buffer.ptr, lineStart, lineDataLimit); 342 lineStart = buffer.ptr; 343 debug { 344 foreach(i,c; lineStart[0..lineDataLimit]) { 345 writefln("[debug] line[%s] = %s", i, debugChar(c)); 346 } 347 } 348 debug writefln("[debug] <--- getLine no-line : start %d check %d limit %d", 349 (lineStart - buffer.ptr), lineCheckOffset, lineDataLimit); 350 return null; 351 } 352 } 353 354 } 355 version(unittest_common) unittest 356 { 357 mixin(scopedTest!("LineParser")); 358 359 void TestLineParser(LineParser lineParser) 360 { 361 lineParser.put("\n"); 362 assertEqual("", cast(string)lineParser.getLine()); 363 assert(!lineParser.getLine()); 364 365 lineParser.put("\r\n"); 366 assertEqual("", cast(string)lineParser.getLine()); 367 assert(!lineParser.getLine()); 368 369 lineParser.put("\r\r\n"); 370 assertEqual("\r", cast(string)lineParser.getLine()); 371 assert(!lineParser.getLine()); 372 373 lineParser.put("\n\n\n"); 374 assertEqual("", cast(string)lineParser.getLine()); 375 assertEqual("", cast(string)lineParser.getLine()); 376 assertEqual("", cast(string)lineParser.getLine()); 377 assert(!lineParser.getLine()); 378 379 lineParser.put("\r\r\r\n\r\n\n\r\r\r\n"); 380 assertEqual("\r\r", cast(string)lineParser.getLine()); 381 assertEqual("", cast(string)lineParser.getLine()); 382 assertEqual("", cast(string)lineParser.getLine()); 383 assertEqual("\r\r", cast(string)lineParser.getLine()); 384 assert(!lineParser.getLine()); 385 386 lineParser.put("abcd\n"); 387 assertEqual("abcd", cast(string)lineParser.getLine()); 388 assert(!lineParser.getLine()); 389 390 lineParser.put("abcd\r\n"); 391 assertEqual("abcd", cast(string)lineParser.getLine()); 392 assert(!lineParser.getLine()); 393 394 lineParser.put("abcd\nefgh\r\n"); 395 assertEqual("abcd", cast(string)lineParser.getLine()); 396 assertEqual("efgh", cast(string)lineParser.getLine()); 397 assert(!lineParser.getLine()); 398 399 lineParser.put("abcd\r\nefghijkl"); 400 assertEqual("abcd", cast(string)lineParser.getLine()); 401 assert(!lineParser.getLine()); 402 lineParser.put("\n"); 403 assertEqual("efghijkl", cast(string)lineParser.getLine()); 404 assert(!lineParser.getLine()); 405 406 lineParser.put("abcd\n"); 407 lineParser.put("abcd\r\n"); 408 lineParser.put("abcd\n"); 409 assertEqual("abcd", cast(string)lineParser.getLine()); 410 assertEqual("abcd", cast(string)lineParser.getLine()); 411 assertEqual("abcd", cast(string)lineParser.getLine()); 412 assert(!lineParser.getLine()); 413 414 lineParser.put("a"); 415 assert(!lineParser.getLine()); 416 lineParser.put("bc"); 417 assert(!lineParser.getLine()); 418 lineParser.put("d"); 419 assert(!lineParser.getLine()); 420 lineParser.put("\r\ntu"); 421 lineParser.put("v"); 422 assertEqual("abcd", cast(string)lineParser.getLine()); 423 assert(!lineParser.getLine()); 424 lineParser.put("\r"); 425 assert(!lineParser.getLine()); 426 lineParser.put("\n"); 427 assertEqual("tuv", cast(string)lineParser.getLine()); 428 assert(!lineParser.getLine()); 429 } 430 431 TestLineParser(LineParser([],1)); 432 TestLineParser(LineParser([0,0,0], 3)); 433 TestLineParser(LineParser(new ubyte[256])); 434 } 435 436 437 enum noEndingQuoteMessage = "found '\"' with no ending '\"'"; 438 439 /** 440 * Used to parse the fields in <i>line</i> to the <i>fields</i> sink. 441 * line is a single line without the line ending character. 442 * returns error message on error 443 */ 444 string tryParseFields(string comment = "#", T, C)(T fields, C[] line) if(isOutputRange!(T,C)) 445 { 446 size_t off = 0; 447 size_t startOfField; 448 449 Unqual!C c; 450 451 while(true) { 452 453 // Skip whitespace 454 while(true) { 455 if(off >= line.length) return null; 456 c = line[off]; 457 if(c != ' ' && c != '\t') break; 458 off++; 459 } 460 461 // check for comment 462 static if(comment != null && comment.length == 1) { 463 464 if(c == comment[0]) return null; 465 466 } else static if(comment != null && comment.length > 1) { 467 468 if(c == comment[0]) { 469 size_t i = 1; 470 while(true) { 471 if(off + i > line.length) break; 472 c = line[off + i]; 473 if(c != comment[i]) break; 474 475 i++; 476 if(i >= comment.length) return null; 477 } 478 479 c = comment[0]; // restore state 480 } 481 482 } 483 484 if(c == '"') { 485 off++; 486 startOfField = off; 487 while(true) { 488 if(off >= line.length) return noEndingQuoteMessage; 489 c = line[off]; 490 if(c == '"') { 491 fields.put(line[startOfField..off]); 492 break; 493 } 494 if(c == '\\') { 495 throw new Exception("Escaping string not implemented yet"); 496 } 497 off++; 498 } 499 } else { 500 startOfField = off; 501 while(true) { 502 off++; 503 if(off >= line.length) { 504 fields.put(line[startOfField..$]); 505 return null; 506 } 507 c = line[off]; 508 if(c == ' ' || c == '\t') { 509 fields.put(line[startOfField..off]); 510 break; 511 } 512 } 513 } 514 } 515 } 516 517 version(unittest_common) unittest 518 { 519 mixin(scopedTest!("tryParseFields")); 520 521 string line; 522 auto fields = appender!(string[])(); 523 524 line = "\"Missing ending quote"; 525 assert(tryParseFields(fields, line) == noEndingQuoteMessage); 526 527 line = "\"Missing ending quote\n"; 528 assert(tryParseFields(fields, line) == noEndingQuoteMessage); 529 530 line = "\"Missing ending quote\nOn next line \""; 531 assert(tryParseFields(fields, line) == noEndingQuoteMessage); 532 533 line = ""; 534 assert(tryParseFields(fields, line) == null); 535 } 536 537 538 void bigEndianSetUshort(ubyte* bytes, ushort value) 539 { 540 *bytes = cast(ubyte)(value >> 8); 541 *(bytes + 1) = cast(ubyte)(value ); 542 } 543 struct ArrayList(V) 544 { 545 public V[] array; 546 public size_t count; 547 548 this(size_t initialSize) 549 { 550 if(initialSize < 1) throw new Exception("Cannot give an initial size of 0"); 551 array = new V[initialSize]; 552 count = 0; 553 } 554 this(Range)(Range r) 555 { 556 array = new V[r.length]; 557 foreach(i, element; r) { 558 array[i] = element; 559 } 560 count = r.length; 561 } 562 void clear() 563 { 564 count = 0; 565 } 566 void checkSizeBeforeAdd() 567 { 568 if(count >= array.length) { 569 array.length *= 2; 570 } 571 } 572 public void put(V element) 573 { 574 checkSizeBeforeAdd(); 575 array[count++] = element; 576 } 577 public void forwardTo(Sink)(Sink sink) 578 { 579 foreach(i; 0..count) { 580 sink.add(array[i]); 581 } 582 } 583 public void removeAt(size_t index) 584 { 585 for(auto i = index; i < count - 1; i++) { 586 array[index] = array[index + 1]; 587 } 588 count--; 589 } 590 } 591 592 593 immutable string[] escapeTable = 594 [ 595 "\\0" , 596 "\\x01", 597 "\\x02", 598 "\\x03", 599 "\\x04", 600 "\\x05", 601 "\\x06", 602 "\\a", // Bell 603 "\\b", // Backspace 604 "\\t", 605 "\\n", 606 "\\v", // Vertical tab 607 "\\f", // Form feed 608 "\\r", 609 "\\x0E", 610 "\\x0F", 611 "\\x10", 612 "\\x11", 613 "\\x12", 614 "\\x13", 615 "\\x14", 616 "\\x15", 617 "\\x16", 618 "\\x17", 619 "\\x18", 620 "\\x19", 621 "\\x1A", 622 "\\x1B", 623 "\\x1C", 624 "\\x1D", 625 "\\x1E", 626 "\\x1F", 627 " ", // 628 "!", // 629 "\"", // 630 "#", // 631 "$", // 632 "%", // 633 "&", // 634 "'", // 635 "(", // 636 ")", // 637 "*", // 638 "+", // 639 ",", // 640 "-", // 641 ".", // 642 "/", // 643 "0", // 644 "1", // 645 "2", // 646 "3", // 647 "4", // 648 "5", // 649 "6", // 650 "7", // 651 "8", // 652 "9", // 653 ":", // 654 ";", // 655 "<", // 656 "=", // 657 ">", // 658 "?", // 659 "@", // 660 "A", // 661 "B", // 662 "C", // 663 "D", // 664 "E", // 665 "F", // 666 "G", // 667 "H", // 668 "I", // 669 "J", // 670 "K", // 671 "L", // 672 "M", // 673 "N", // 674 "O", // 675 "P", // 676 "Q", // 677 "R", // 678 "S", // 679 "T", // 680 "U", // 681 "V", // 682 "W", // 683 "X", // 684 "Y", // 685 "Z", // 686 "[", // 687 "\\", // 688 "]", // 689 "^", // 690 "_", // 691 "`", // 692 "a", // 693 "b", // 694 "c", // 695 "d", // 696 "e", // 697 "f", // 698 "g", // 699 "h", // 700 "i", // 701 "j", // 702 "k", // 703 "l", // 704 "m", // 705 "n", // 706 "o", // 707 "p", // 708 "q", // 709 "r", // 710 "s", // 711 "t", // 712 "u", // 713 "v", // 714 "w", // 715 "x", // 716 "y", // 717 "z", // 718 "{", // 719 "|", // 720 "}", // 721 "~", // 722 "\x7F", // 723 "\x80", "\x81", "\x82", "\x83", "\x84", "\x85", "\x86", "\x87", "\x88", "\x89", "\x8A", "\x8B", "\x8C", "\x8D", "\x8E", "\x8F", 724 "\x90", "\x91", "\x92", "\x93", "\x94", "\x95", "\x96", "\x97", "\x98", "\x99", "\x9A", "\x9B", "\x9C", "\x9D", "\x9E", "\x9F", 725 "\xA0", "\xA1", "\xA2", "\xA3", "\xA4", "\xA5", "\xA6", "\xA7", "\xA8", "\xA9", "\xAA", "\xAB", "\xAC", "\xAD", "\xAE", "\xAF", 726 "\xB0", "\xB1", "\xB2", "\xB3", "\xB4", "\xB5", "\xB6", "\xB7", "\xB8", "\xB9", "\xBA", "\xBB", "\xBC", "\xBD", "\xBE", "\xBF", 727 "\xC0", "\xC1", "\xC2", "\xC3", "\xC4", "\xC5", "\xC6", "\xC7", "\xC8", "\xC9", "\xCA", "\xCB", "\xCC", "\xCD", "\xCE", "\xCF", 728 "\xD0", "\xD1", "\xD2", "\xD3", "\xD4", "\xD5", "\xD6", "\xD7", "\xD8", "\xD9", "\xDA", "\xDB", "\xDC", "\xDD", "\xDE", "\xDF", 729 "\xE0", "\xE1", "\xE2", "\xE3", "\xE4", "\xE5", "\xE6", "\xE7", "\xE8", "\xE9", "\xEA", "\xEB", "\xEC", "\xED", "\xEE", "\xEF", 730 "\xF0", "\xF1", "\xF2", "\xF3", "\xF4", "\xF5", "\xF6", "\xF7", "\xF8", "\xF9", "\xFA", "\xFB", "\xFC", "\xFD", "\xFE", "\xFF", 731 ]; 732 string escape(char c) { 733 return escapeTable[c]; 734 } 735 string escape(dchar c) { 736 return (c < escapeTable.length) ? escapeTable[c] : to!string(c); 737 } 738 inout(char)[] escape(inout(char)[] str) pure { 739 size_t extra = 0; 740 foreach(c; str) { 741 if(c == '\r' || c == '\t' || c == '\n' || c == '\\') { 742 extra++; 743 } 744 } 745 746 if(extra == 0) return str; 747 748 char[] newString = new char[str.length + extra]; 749 size_t oldIndex, newIndex = 0; 750 for(oldIndex = 0; oldIndex < str.length; oldIndex++) { 751 auto c = str[oldIndex]; 752 switch(c) { 753 case '\r': 754 newString[newIndex++] = '\\'; 755 newString[newIndex++] = 'r'; 756 break; 757 case '\t': 758 newString[newIndex++] = '\\'; 759 newString[newIndex++] = 't'; 760 break; 761 case '\n': 762 newString[newIndex++] = '\\'; 763 newString[newIndex++] = 'n'; 764 break; 765 case '\\': 766 newString[newIndex++] = '\\'; 767 newString[newIndex++] = '\\'; 768 break; 769 default: 770 newString[newIndex++] = c; 771 } 772 } 773 774 assert(newIndex == newString.length); 775 776 return cast(inout(char)[])newString; 777 } 778 version(unittest_common) unittest 779 { 780 mixin(scopedTest!("escape")); 781 782 assert (char.max == escapeTable.length - 1); 783 784 for(auto c = ' '; c <= '~'; c++) { 785 assert(c == escape(c)[0]); 786 } 787 assert("\\0" == escape(0)); 788 assert("\\r" == escape('\r')); 789 assert("\\n" == escape('\n')); 790 assert("\\t" == escape('\t')); 791 792 793 assert("\\r" == escape("\r")); 794 assert("\\t" == escape("\t")); 795 assert("\\n" == escape("\n")); 796 assert("\\\\" == escape("\\")); 797 } 798 799 800 struct StdoutWriter 801 { 802 void put(const (char)[] str) { 803 write(str); 804 } 805 } 806 807 struct ReadBuffer(T) 808 { 809 const(T)* next; 810 const T* limit; 811 } 812 struct WriteBuffer(T) 813 { 814 T* next; 815 const T* limit; 816 this(T[] array) { 817 this.next = array.ptr; 818 this.limit = array.ptr + array.length; 819 } 820 T[] slice(T* start) { 821 return start[0..next-start]; 822 } 823 void put(const(T)[] array) { 824 if(next + array.length > limit) 825 throw new Exception(format("buffer has %s elements left but you tried to add %s", 826 limit - next, array.length)); 827 828 foreach(value; array) { 829 *next = value; 830 next++; 831 } 832 } 833 } 834 835 alias void delegate(const(char)[] msg) const Writer; 836 837 alias size_t delegate(char[] buffer) CharReader; 838 alias size_t delegate(ubyte[] buffer) DataReader; 839 840 struct FileCharReader 841 { 842 File file; 843 size_t read(char[] buffer) { 844 return file.rawRead(buffer).length; 845 } 846 } 847 848 struct AsciiBufferedInput 849 { 850 CharReader reader; 851 char[] buffer; 852 size_t start; 853 size_t limit; 854 855 this(inout(char)[] s) { 856 this.reader = &emptyRead; 857 this.buffer = new char[s.length + 1]; 858 this.buffer[0..$-1] = s; 859 this.start = 0; 860 this.limit = s.length; 861 } 862 private size_t emptyRead(char[] buffer) { return 0; } 863 864 char[] sliceSaved() { 865 return buffer[start..limit]; 866 } 867 868 void clear() { 869 start = 0; 870 limit = 0; 871 } 872 873 // returns true if there is data, false on EOF 874 bool readNoSave() { 875 start = 0; 876 limit = reader(buffer); 877 return limit > 0; 878 } 879 880 // returns true if read succeeded, false on EOF 881 bool read(size_t* offsetToShift) { 882 //writefln("[DEBUG] --> read(start=%s, limit=%s)", start, limit); 883 size_t leftover = limit - start; 884 885 if(leftover) { 886 if(leftover >= buffer.length) { 887 throw new Exception("Buffer not large enough"); 888 } 889 890 if(start > 0) { 891 memmove(buffer.ptr, buffer.ptr + start, leftover); 892 *offsetToShift -= leftover; 893 } 894 } 895 896 start = 0; 897 limit = leftover; 898 size_t readLength = reader(buffer[leftover..$]); 899 if(readLength == 0) { 900 //writefln("[DEBUG] <-- read(start=%s, length=%s) = false", start, length); 901 return false; 902 } 903 904 limit += readLength; 905 return true; 906 } 907 908 909 } 910 911 912 struct FormattedBinaryWriter 913 { 914 scope void delegate(const(char)[]) sink; 915 916 ubyte[] columnBuffer; 917 918 string offsetFormat; 919 mixin(bitfields! 920 (bool, "hex", 1, 921 bool, "text", 1, 922 void, "", 6)); 923 924 size_t cachedData; 925 926 uint offset; 927 928 this(scope void delegate(const(char)[]) sink, ubyte[] columnBuffer, 929 ubyte offsetTextWidth = 8, bool hex = true, bool text = true) { 930 this.sink = sink; 931 this.columnBuffer = columnBuffer; 932 933 if(offsetTextWidth == 0) { 934 offsetFormat = null; 935 } else { 936 offsetFormat = "%0"~to!string(offsetTextWidth)~"x"; 937 } 938 939 this.hex = hex; 940 this.text = text; 941 } 942 943 void writeAscii(byte b) { 944 if(b <= '~' && b >= ' ') { 945 sink((cast(char*)&b)[0..1]); 946 } else { 947 sink("."); 948 } 949 } 950 951 void writeRow(T)(T data) { 952 bool atFirst = true; 953 void prefix() { 954 if(atFirst) {atFirst = false; } 955 else { sink(" "); } 956 } 957 if(offsetFormat) { 958 prefix(); 959 formattedWrite(sink, offsetFormat, offset); 960 } 961 962 if(hex) { 963 prefix(); 964 foreach(b; data) { 965 formattedWrite(sink, " %02x", b); 966 } 967 } 968 969 if(text) { 970 prefix(); 971 foreach(b; data) { 972 writeAscii(b); 973 } 974 } 975 976 sink("\n"); 977 offset += columnBuffer.length; 978 } 979 void finish() { 980 if(cachedData > 0) { 981 bool atFirst = true; 982 void prefix() { 983 if(atFirst) {atFirst = false; } 984 else { sink(" "); } 985 } 986 if(offsetFormat) { 987 prefix(); 988 formattedWrite(sink, offsetFormat, offset); 989 } 990 991 if(hex) { 992 prefix(); 993 foreach(b; columnBuffer[0..cachedData]) { 994 formattedWrite(sink, " %02x", b); 995 } 996 foreach(b; cachedData..columnBuffer.length) { 997 sink(" "); 998 } 999 } 1000 1001 if(text) { 1002 prefix(); 1003 foreach(b; columnBuffer[0..cachedData]) { 1004 writeAscii(b); 1005 } 1006 } 1007 1008 sink("\n"); 1009 offset += columnBuffer.length; 1010 1011 cachedData = 0; 1012 } 1013 } 1014 void put(scope ubyte[] data) in { assert(data.length); } body { 1015 if(cachedData > 0) { 1016 auto combineLength = columnBuffer.length - cachedData; 1017 if(combineLength > data.length) { 1018 combineLength = data.length; 1019 } 1020 columnBuffer[cachedData..cachedData+combineLength] = data[]; 1021 writeRow(columnBuffer); 1022 data = data[combineLength..$]; 1023 } 1024 1025 while(data.length >= columnBuffer.length) { 1026 writeRow(data[0..columnBuffer.length]); 1027 data = data[columnBuffer.length..$]; 1028 } 1029 1030 if(data.length > 0) { 1031 columnBuffer[0..data.length][] = data; 1032 cachedData = data.length; 1033 } 1034 } 1035 } 1036 1037 1038 1039 // 1040 // Range Initializers 1041 // 1042 string arrayRange(char min, char max, string initializer) { 1043 string initializers = ""; 1044 for(char c = min; c < max; c++) { 1045 initializers ~= "'"~c~"': "~initializer~",\n"; 1046 } 1047 initializers ~= "'"~max~"': "~initializer; 1048 return initializers; 1049 } 1050 string rangeInitializers(string[] s...) { 1051 if(s.length % 2 != 0) assert(0, "must supply an even number of arguments to rangeInitializers"); 1052 string code = "["~rangeInitializersCurrent(s); 1053 //assert(0, code); // uncomment to see the code 1054 return code; 1055 } 1056 string rangeInitializersCurrent(string[] s) { 1057 string range = s[0]; 1058 if(range[0] == '\'') { 1059 if(range.length == 3 || (range.length == 4 && range[1] == '\\')) { 1060 if(range[$-1] != '\'') throw new Exception(format("a single-character range %s started with an apostrophe (') but did not end with one", range)); 1061 return range ~ ":" ~ s[1] ~ rangeInitializersNext(s); 1062 } 1063 } else { 1064 throw new Exception(format("range '%s' not supported", range)); 1065 } 1066 char min = range[1]; 1067 char max = range[5]; 1068 return arrayRange(min, max, s[1]) ~ rangeInitializersNext(s); 1069 } 1070 string rangeInitializersNext(string[] s...) { 1071 if(s.length <= 2) return "]"; 1072 return ",\n"~rangeInitializersCurrent(s[2..$]); 1073 } 1074 1075 1076 1077 struct StringByLine 1078 { 1079 string s; 1080 size_t startOfLineOffset; 1081 size_t endOfLineOffset; 1082 this(string s) @safe pure nothrow @nogc 1083 { 1084 this.s = s; 1085 this.startOfLineOffset = 0; 1086 this.endOfLineOffset = 0; 1087 popFront; 1088 } 1089 @property bool empty() pure nothrow @safe @nogc 1090 { 1091 return startOfLineOffset >= s.length; 1092 } 1093 @property auto front() pure nothrow @safe @nogc 1094 { 1095 return s[startOfLineOffset..endOfLineOffset]; 1096 } 1097 @property void popFront() pure nothrow @safe @nogc 1098 { 1099 if(startOfLineOffset < s.length) { 1100 startOfLineOffset = endOfLineOffset; 1101 while(true) { 1102 if(endOfLineOffset >= s.length) break; 1103 if(s[endOfLineOffset] == '\n') { 1104 endOfLineOffset++; 1105 break; 1106 } 1107 endOfLineOffset++; 1108 } 1109 } 1110 } 1111 } 1112 auto byLine(string s) pure nothrow @safe @nogc { 1113 return StringByLine(s); 1114 } 1115 version(unittest_common) unittest 1116 { 1117 mixin(scopedTest!("StringByLine")); 1118 1119 void test(string[] expectedStrings, string s, size_t testLine = __LINE__) 1120 { 1121 auto stringByLine = s.byLine(); 1122 1123 foreach(expectedString; expectedStrings) { 1124 if(stringByLine.empty) { 1125 writefln("Expected string '%s' but no more strings", escape(expectedString)); 1126 assert(0); 1127 } 1128 if(expectedString != stringByLine.front) { 1129 writefln("Expected: '%s'", escape(expectedString)); 1130 writefln("Actual : '%s'", escape(stringByLine.front)); 1131 assert(0); 1132 } 1133 stringByLine.popFront; 1134 } 1135 1136 if(!stringByLine.empty) { 1137 writefln("Expected no more strings but got another '%s'", escape(stringByLine.front)); 1138 assert(0); 1139 } 1140 } 1141 1142 test([], ""); 1143 test(["a"], "a"); 1144 test(["a\n"], "a\n"); 1145 test(["abc"], "abc"); 1146 test(["abc\n"], "abc\n"); 1147 1148 test(["abc\n", "123"], "abc\n123"); 1149 test(["abc\n", "123\n"], "abc\n123\n"); 1150 1151 } 1152 1153 1154 1155 enum BufferTooSmall 1156 { 1157 returnPartialData, 1158 throwException, 1159 resizeBuffer, 1160 } 1161 1162 /** 1163 The LinesChunker reads as many lines as it can. If the buffer runs out in the 1164 middle of a line it will return all the previous full lines. On the next 1165 read it will move the left over data to the beginning of the buffer and continue reading. 1166 If it cannot read a full line it will either return partial lines or it will throw an 1167 error depending on what the user specifies. 1168 Options on how to handle lines that are too long 1169 1. Return partial lines 1170 2. Resize the buffer to hold the entire line 1171 3. Throw an exception/cause an error 1172 */ 1173 struct LinesChunker 1174 { 1175 char[] buffer; 1176 BufferTooSmall tooSmall; 1177 private char[] leftOver; 1178 1179 this(char[] buffer, BufferTooSmall tooSmall) { 1180 this.buffer = buffer; 1181 this.tooSmall = tooSmall; 1182 } 1183 size_t read(CharReader reader) 1184 { 1185 // 1186 // Handle leftOver Data 1187 // 1188 size_t bufferOffset; 1189 if(leftOver.length == 0) { 1190 bufferOffset = 0; 1191 } else { 1192 // TODO: do I need this check? Can this ever happen? 1193 if(leftOver.ptr != buffer.ptr) { 1194 memmove(buffer.ptr, leftOver.ptr, leftOver.length); 1195 } 1196 bufferOffset = leftOver.length; 1197 leftOver = null; 1198 } 1199 1200 // 1201 // Read More Data 1202 // 1203 while(true) { 1204 if(bufferOffset >= buffer.length) { 1205 if(tooSmall == BufferTooSmall.returnPartialData) { 1206 return bufferOffset; 1207 } else if(tooSmall == BufferTooSmall.resizeBuffer) { 1208 throw new Exception("BufferTooSmall.resizeBuffer is not implemented in LinesChunker"); 1209 } 1210 throw new Exception(format("the current buffer of length %s is too small to hold the current line", buffer.length)); 1211 } 1212 1213 size_t readLength = reader(buffer[bufferOffset .. $]); 1214 if(readLength == 0) return bufferOffset; 1215 1216 auto totalLength = bufferOffset + readLength; 1217 auto i = totalLength - 1; 1218 while(true) { 1219 auto c = buffer[i]; 1220 if(c == '\n') { 1221 leftOver = buffer[i+1..totalLength]; 1222 return i+1; 1223 } 1224 if(i == bufferOffset) { 1225 break; 1226 } 1227 i--; 1228 } 1229 1230 bufferOffset = totalLength; 1231 } 1232 } 1233 } 1234 1235 version(unittest_common) 1236 { 1237 struct CustomChunks { 1238 string[] chunks; 1239 size_t chunkIndex; 1240 size_t read(char[] buffer) { 1241 if(chunkIndex >= chunks.length) return 0; 1242 auto chunk = chunks[chunkIndex++]; 1243 if(chunk.length > buffer.length) { 1244 assert(0, format("Chunk at index %s is %s bytes but the buffer is only %s", chunkIndex, chunk.length, buffer.length)); 1245 } 1246 buffer[0..chunk.length] = chunk; 1247 return chunk.length; 1248 } 1249 } 1250 } 1251 struct LinesReader 1252 { 1253 CharReader reader; 1254 LinesChunker chunker; 1255 size_t currentBytes; 1256 this(CharReader reader, char[] buffer, BufferTooSmall tooSmall) 1257 { 1258 this.reader = reader; 1259 this.chunker = LinesChunker(buffer, tooSmall); 1260 this.currentBytes = this.chunker.read(reader); 1261 } 1262 @property empty() 1263 { 1264 return currentBytes == 0; 1265 } 1266 @property char[] front() { 1267 return chunker.buffer[0..currentBytes]; 1268 } 1269 @property void popFront() { 1270 this.currentBytes = this.chunker.read(reader); 1271 } 1272 } 1273 auto byLines(CharReader reader, char[] buffer, BufferTooSmall tooSmall) { 1274 return LinesReader(reader, buffer, tooSmall); 1275 } 1276 auto byLines(File file, char[] buffer, BufferTooSmall tooSmall) { 1277 auto fileCharReader = FileCharReader(file); 1278 return LinesReader(&(fileCharReader.read), buffer, tooSmall); 1279 } 1280 1281 1282 version(unittest_common) unittest 1283 { 1284 mixin(scopedTest!("LinesChunker/LinesReader")); 1285 1286 CustomChunks customChunks; 1287 char[5] buffer5; 1288 char[256] buffer256; 1289 1290 void testLinesChunkerCustom(string[] expectedChunks, CharReader reader, char[] chunkerBuffer = buffer256, size_t testLine = __LINE__) 1291 { 1292 auto lineChunker = LinesChunker(chunkerBuffer, BufferTooSmall.throwException); 1293 1294 foreach(expectedChunk; expectedChunks) { 1295 size_t actualLength = lineChunker.read(reader); 1296 if(chunkerBuffer[0..actualLength] != expectedChunk) { 1297 writefln("Expected: '%s'", escape(expectedChunk)); 1298 writefln("Actual : '%s'", escape(chunkerBuffer[0..actualLength])); 1299 assert(0); 1300 } 1301 /+ 1302 debug { 1303 writefln(" 1304 } 1305 +/ 1306 } 1307 } 1308 void testLinesChunker(string[] expectedChunks, string[] customChunkStrings, char[] chunkerBuffer = buffer256, size_t testLine = __LINE__) 1309 { 1310 writefln("testLinesChunker %s %s", expectedChunks, customChunkStrings); 1311 1312 customChunks = CustomChunks(customChunkStrings); 1313 testLinesChunkerCustom(expectedChunks, &(customChunks.read), chunkerBuffer, testLine); 1314 } 1315 1316 1317 testLinesChunker(["a\n"], ["a\n"]); 1318 testLinesChunker(["a\n", "b"], ["a\nb"], buffer5); // NOTE: One would think that this should return one chunk, however, 1319 // This simulates the case when the reader returned the available 1320 // data so there's no way for the chunker to know if there is another newline 1321 // coming at the next read call so it just returns the currently known lines 1322 1323 testLinesChunker(["a\nb\r\nc\n"], ["a\nb\r\nc\n"]); 1324 testLinesChunker(["a\nb\r\nc\n", "d"], ["a\nb\r\nc\nd"]); 1325 testLinesChunker(["123\n", "123\n"], ["123\n1", "23\n"], buffer5); 1326 testLinesChunker(["123\n", "123"], ["123\n1", "23"], buffer5); 1327 1328 // 1329 // Test LinesReader 1330 // 1331 /+ 1332 void testByLines(string[] expectedChunks, string[] customChunkStrings, char[] cunkBuffer = buffer256, size_t testLine = __LINE__) 1333 { 1334 1335 } 1336 +/ 1337 customChunks = CustomChunks(["a\nb"]); 1338 foreach(lines; (&(customChunks.read)).byLines(buffer5, BufferTooSmall.throwException)) { 1339 writefln("lines: '%s'", escape(lines)); 1340 } 1341 } 1342 1343 1344 struct LineReader 1345 { 1346 CharReader reader; 1347 LinesChunker chunker; 1348 1349 size_t dataLength; 1350 char[] line; 1351 size_t endOfLineOffset; 1352 1353 this(CharReader reader, char[] buffer, BufferTooSmall tooSmall) { 1354 this.reader = reader; 1355 this.chunker = LinesChunker(buffer, tooSmall); 1356 1357 this.dataLength = this.chunker.read(reader); 1358 this.line = null; 1359 this.endOfLineOffset = 0; 1360 popFront(); 1361 } 1362 @property bool empty() 1363 { 1364 return line is null; 1365 } 1366 @property auto front() 1367 { 1368 return line; 1369 } 1370 @property void popFront() 1371 { 1372 if(endOfLineOffset >= dataLength) { 1373 1374 if(this.line is null) { 1375 //writefln("[DEBUG] LineReader.popFront no more data"); 1376 return; 1377 } 1378 this.dataLength = this.chunker.read(reader); 1379 if(this.dataLength == 0) { 1380 this.line = null; 1381 //writefln("[DEBUG] LineReader.popFront no more data"); 1382 return; 1383 } 1384 endOfLineOffset = 0; 1385 } 1386 1387 auto startOfNextLine = endOfLineOffset; 1388 while(true) { 1389 if(chunker.buffer[endOfLineOffset] == '\n') { 1390 endOfLineOffset++; 1391 line = chunker.buffer[startOfNextLine..endOfLineOffset]; 1392 break; 1393 } 1394 endOfLineOffset++; 1395 if(endOfLineOffset >= dataLength) { 1396 line = chunker.buffer[startOfNextLine..endOfLineOffset]; 1397 break; 1398 } 1399 } 1400 1401 //writefln("[DEBUG] LineReader.popFront '%s'", escape(line)); 1402 1403 } 1404 } 1405 1406 1407 auto byLine(CharReader reader, char[] buffer, BufferTooSmall tooSmall) { 1408 return LineReader(reader, buffer, tooSmall); 1409 } 1410 /* 1411 auto byLine(File file, char[] buffer, BufferTooSmall tooSmall) { 1412 auto fileCharReader = FileCharReader(file); 1413 return LinesReader(&(fileCharReader.read), buffer, tooSmall); 1414 } 1415 */ 1416 1417 version(unittest_common) unittest 1418 { 1419 mixin(scopedTest!("LineReader")); 1420 1421 char[256] buffer256; 1422 1423 void testLines(string data, size_t testLine = __LINE__) { 1424 CustomChunks customChunks; 1425 1426 for(auto chunkSize = 1; chunkSize <= data.length; chunkSize++) { 1427 1428 // Create Chunks 1429 string[] chunks; 1430 size_t offset; 1431 for(offset = 0; offset + chunkSize <= data.length; offset += chunkSize) { 1432 chunks ~= data[offset .. offset + chunkSize]; 1433 } 1434 if(data.length - offset > 0) { 1435 chunks ~= data[offset .. $]; 1436 } 1437 //writefln("ChunkSize %s Chunks %s", chunkSize, chunks); 1438 1439 customChunks = CustomChunks(chunks); 1440 auto lineReader = LineReader(&(customChunks.read), buffer256, BufferTooSmall.throwException); 1441 1442 //writefln("[DEBUG] lineReader.front = '%s'", lineReader.front); 1443 1444 1445 size_t lineNumber = 1; 1446 foreach(line; data.byLine()) { 1447 //writefln("[DEBUG] line %s '%s'", lineNumber, escape(line)); 1448 1449 if(lineReader.empty) { 1450 writefln("Expected line '%s' but no more lines", escape(line)); 1451 assert(0); 1452 } 1453 if(line != lineReader.front) { 1454 writefln("Expected: '%s'", escape(line)); 1455 writefln("Actual : '%s'", escape(lineReader.front)); 1456 assert(0); 1457 } 1458 lineReader.popFront; 1459 lineNumber++; 1460 } 1461 1462 if(!lineReader.empty) { 1463 writefln("Got extra line '%s' but expected no more lines", escape(lineReader.front)); 1464 assert(0); 1465 } 1466 1467 } 1468 } 1469 1470 //testLines("abc"); 1471 //testLines("abc\n"); 1472 testLines("abc\n1234\n\n"); 1473 1474 1475 }