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