1 // Written in the D programming language 2 3 /** 4 * $(RED Deprecated: This module is considered out-dated and not up to Phobos' 5 * current standards.) 6 * 7 * Source: $(PHOBOSSRC std/_stream.d) 8 * Macros: 9 * WIKI = Phobos/StdStream 10 */ 11 12 /* 13 * Copyright (c) 2001-2005 14 * Pavel "EvilOne" Minayev 15 * with buffering and endian support added by Ben Hinkle 16 * with buffered readLine performance improvements by Dave Fladebo 17 * with opApply inspired by (and mostly copied from) Regan Heath 18 * with bug fixes and MemoryStream/SliceStream enhancements by Derick Eddington 19 * 20 * Permission to use, copy, modify, distribute and sell this software 21 * and its documentation for any purpose is hereby granted without fee, 22 * provided that the above copyright notice appear in all copies and 23 * that both that copyright notice and this permission notice appear 24 * in supporting documentation. Author makes no representations about 25 * the suitability of this software for any purpose. It is provided 26 * "as is" without express or implied warranty. 27 */ 28 module undead.stream; 29 30 import std.internal.cstring; 31 32 /* Class structure: 33 * InputStream interface for reading 34 * OutputStream interface for writing 35 * Stream abstract base of stream implementations 36 * File an OS file stream 37 * FilterStream a base-class for wrappers around another stream 38 * BufferedStream a buffered stream wrapping another stream 39 * BufferedFile a buffered File 40 * EndianStream a wrapper stream for swapping byte order and BOMs 41 * SliceStream a portion of another stream 42 * MemoryStream a stream entirely stored in main memory 43 * TArrayStream a stream wrapping an array-like buffer 44 */ 45 46 /// A base class for stream exceptions. 47 class StreamException: Exception { 48 /// Construct a StreamException with given error message. 49 this(string msg) { super(msg); } 50 } 51 52 /// Thrown when unable to read data from Stream. 53 class ReadException: StreamException { 54 /// Construct a ReadException with given error message. 55 this(string msg) { super(msg); } 56 } 57 58 /// Thrown when unable to write data to Stream. 59 class WriteException: StreamException { 60 /// Construct a WriteException with given error message. 61 this(string msg) { super(msg); } 62 } 63 64 /// Thrown when unable to move Stream pointer. 65 class SeekException: StreamException { 66 /// Construct a SeekException with given error message. 67 this(string msg) { super(msg); } 68 } 69 70 // seek whence... 71 enum SeekPos { 72 Set, 73 Current, 74 End 75 } 76 77 private { 78 import std.conv; 79 import std.algorithm; 80 import std.ascii; 81 //import std.format; 82 import std.system; // for Endian enumeration 83 import std.utf; 84 import undead.utf; 85 import core.bitop; // for bswap 86 import core.vararg; 87 static import std.file; 88 import undead.internal.file; 89 import undead.doformat; 90 } 91 92 /// InputStream is the interface for readable streams. 93 94 interface InputStream { 95 96 /*** 97 * Read exactly size bytes into the buffer. 98 * 99 * Throws a ReadException if it is not correct. 100 */ 101 void readExact(void* buffer, size_t size); 102 103 /*** 104 * Read a block of data big enough to fill the given array buffer. 105 * 106 * Returns: the actual number of bytes read. Unfilled bytes are not modified. 107 */ 108 size_t read(ubyte[] buffer); 109 110 /*** 111 * Read a basic type or counted string. 112 * 113 * Throw a ReadException if it could not be read. 114 * Outside of byte, ubyte, and char, the format is 115 * implementation-specific and should not be used except as opposite actions 116 * to write. 117 */ 118 void read(out byte x); 119 void read(out ubyte x); /// ditto 120 void read(out short x); /// ditto 121 void read(out ushort x); /// ditto 122 void read(out int x); /// ditto 123 void read(out uint x); /// ditto 124 void read(out long x); /// ditto 125 void read(out ulong x); /// ditto 126 void read(out float x); /// ditto 127 void read(out double x); /// ditto 128 void read(out real x); /// ditto 129 void read(out char x); /// ditto 130 void read(out wchar x); /// ditto 131 void read(out dchar x); /// ditto 132 133 // reads a string, written earlier by write() 134 void read(out char[] s); /// ditto 135 136 // reads a Unicode string, written earlier by write() 137 void read(out wchar[] s); /// ditto 138 139 /*** 140 * Read a line that is terminated with some combination of carriage return and 141 * line feed or end-of-file. 142 * 143 * The terminators are not included. The wchar version 144 * is identical. The optional buffer parameter is filled (reallocating 145 * it if necessary) and a slice of the result is returned. 146 */ 147 char[] readLine(); 148 char[] readLine(char[] result); /// ditto 149 wchar[] readLineW(); /// ditto 150 wchar[] readLineW(wchar[] result); /// ditto 151 152 /*** 153 * Overload foreach statements to read the stream line by line and call the 154 * supplied delegate with each line or with each line with line number. 155 * 156 * The string passed in line may be reused between calls to the delegate. 157 * Line numbering starts at 1. 158 * Breaking out of the foreach will leave the stream 159 * position at the beginning of the next line to be read. 160 * For example, to echo a file line-by-line with line numbers run: 161 * ------------------------------------ 162 * Stream file = new BufferedFile("sample.txt"); 163 * foreach(ulong n, char[] line; file) 164 * { 165 * writefln("line %d: %s", n, line); 166 * } 167 * file.close(); 168 * ------------------------------------ 169 */ 170 171 // iterate through the stream line-by-line 172 int opApply(scope int delegate(ref char[] line) dg); 173 int opApply(scope int delegate(ref ulong n, ref char[] line) dg); /// ditto 174 int opApply(scope int delegate(ref wchar[] line) dg); /// ditto 175 int opApply(scope int delegate(ref ulong n, ref wchar[] line) dg); /// ditto 176 177 /// Read a string of the given length, 178 /// throwing ReadException if there was a problem. 179 char[] readString(size_t length); 180 181 /*** 182 * Read a string of the given length, throwing ReadException if there was a 183 * problem. 184 * 185 * The file format is implementation-specific and should not be used 186 * except as opposite actions to <b>write</b>. 187 */ 188 189 wchar[] readStringW(size_t length); 190 191 192 /*** 193 * Read and return the next character in the stream. 194 * 195 * This is the only method that will handle ungetc properly. 196 * getcw's format is implementation-specific. 197 * If EOF is reached then getc returns char.init and getcw returns wchar.init. 198 */ 199 200 char getc(); 201 wchar getcw(); /// ditto 202 203 /*** 204 * Push a character back onto the stream. 205 * 206 * They will be returned in first-in last-out order from getc/getcw. 207 * Only has effect on further calls to getc() and getcw(). 208 */ 209 char ungetc(char c); 210 wchar ungetcw(wchar c); /// ditto 211 212 /*** 213 * Scan a string from the input using a similar form to C's scanf 214 * and <a href="std_format.html">std.format</a>. 215 * 216 * An argument of type string is interpreted as a format string. 217 * All other arguments must be pointer types. 218 * If a format string is not present a default will be supplied computed from 219 * the base type of the pointer type. An argument of type string* is filled 220 * (possibly with appending characters) and a slice of the result is assigned 221 * back into the argument. For example the following readf statements 222 * are equivalent: 223 * -------------------------- 224 * int x; 225 * double y; 226 * string s; 227 * file.readf(&x, " hello ", &y, &s); 228 * file.readf("%d hello %f %s", &x, &y, &s); 229 * file.readf("%d hello %f", &x, &y, "%s", &s); 230 * -------------------------- 231 */ 232 int vreadf(TypeInfo[] arguments, va_list args); 233 int readf(...); /// ditto 234 235 /// Retrieve the number of bytes available for immediate reading. 236 @property size_t available(); 237 238 /*** 239 * Return whether the current file position is the same as the end of the 240 * file. 241 * 242 * This does not require actually reading past the end, as with stdio. For 243 * non-seekable streams this might only return true after attempting to read 244 * past the end. 245 */ 246 247 @property bool eof(); 248 249 @property bool isOpen(); /// Return true if the stream is currently open. 250 } 251 252 /// Interface for writable streams. 253 interface OutputStream { 254 255 /*** 256 * Write exactly size bytes from buffer, or throw a WriteException if that 257 * could not be done. 258 */ 259 void writeExact(const void* buffer, size_t size); 260 261 /*** 262 * Write as much of the buffer as possible, 263 * returning the number of bytes written. 264 */ 265 size_t write(const(ubyte)[] buffer); 266 267 /*** 268 * Write a basic type. 269 * 270 * Outside of byte, ubyte, and char, the format is implementation-specific 271 * and should only be used in conjunction with read. 272 * Throw WriteException on error. 273 */ 274 void write(byte x); 275 void write(ubyte x); /// ditto 276 void write(short x); /// ditto 277 void write(ushort x); /// ditto 278 void write(int x); /// ditto 279 void write(uint x); /// ditto 280 void write(long x); /// ditto 281 void write(ulong x); /// ditto 282 void write(float x); /// ditto 283 void write(double x); /// ditto 284 void write(real x); /// ditto 285 void write(char x); /// ditto 286 void write(wchar x); /// ditto 287 void write(dchar x); /// ditto 288 289 /*** 290 * Writes a string, together with its length. 291 * 292 * The format is implementation-specific 293 * and should only be used in conjunction with read. 294 * Throw WriteException on error. 295 */ 296 void write(const(char)[] s); 297 void write(const(wchar)[] s); /// ditto 298 299 /*** 300 * Write a line of text, 301 * appending the line with an operating-system-specific line ending. 302 * 303 * Throws WriteException on error. 304 */ 305 void writeLine(const(char)[] s); 306 307 /*** 308 * Write a line of text, 309 * appending the line with an operating-system-specific line ending. 310 * 311 * The format is implementation-specific. 312 * Throws WriteException on error. 313 */ 314 void writeLineW(const(wchar)[] s); 315 316 /*** 317 * Write a string of text. 318 * 319 * Throws WriteException if it could not be fully written. 320 */ 321 void writeString(const(char)[] s); 322 323 /*** 324 * Write a string of text. 325 * 326 * The format is implementation-specific. 327 * Throws WriteException if it could not be fully written. 328 */ 329 void writeStringW(const(wchar)[] s); 330 331 /*** 332 * Print a formatted string into the stream using printf-style syntax, 333 * returning the number of bytes written. 334 */ 335 size_t vprintf(const(char)[] format, va_list args); 336 size_t printf(const(char)[] format, ...); /// ditto 337 338 /*** 339 * Print a formatted string into the stream using writef-style syntax. 340 * References: <a href="std_format.html">std.format</a>. 341 * Returns: self to chain with other stream commands like flush. 342 */ 343 OutputStream writef(...); 344 OutputStream writefln(...); /// ditto 345 OutputStream writefx(TypeInfo[] arguments, va_list argptr, int newline = false); /// ditto 346 347 void flush(); /// Flush pending output if appropriate. 348 void close(); /// Close the stream, flushing output if appropriate. 349 @property bool isOpen(); /// Return true if the stream is currently open. 350 } 351 352 353 /*** 354 * Stream is the base abstract class from which the other stream classes derive. 355 * 356 * Stream's byte order is the format native to the computer. 357 * 358 * Reading: 359 * These methods require that the readable flag be set. 360 * Problems with reading result in a ReadException being thrown. 361 * Stream implements the InputStream interface in addition to the 362 * readBlock method. 363 * 364 * Writing: 365 * These methods require that the writeable flag be set. Problems with writing 366 * result in a WriteException being thrown. Stream implements the OutputStream 367 * interface in addition to the following methods: 368 * writeBlock 369 * copyFrom 370 * copyFrom 371 * 372 * Seeking: 373 * These methods require that the seekable flag be set. 374 * Problems with seeking result in a SeekException being thrown. 375 * seek, seekSet, seekCur, seekEnd, position, size, toString, toHash 376 */ 377 378 // not really abstract, but its instances will do nothing useful 379 class Stream : InputStream, OutputStream { 380 private import std.string, std.digest.crc, core.stdc.stdlib, core.stdc.stdio; 381 382 // stream abilities 383 bool readable = false; /// Indicates whether this stream can be read from. 384 bool writeable = false; /// Indicates whether this stream can be written to. 385 bool seekable = false; /// Indicates whether this stream can be sought within. 386 protected bool isopen = true; /// Indicates whether this stream is open. 387 388 protected bool readEOF = false; /** Indicates whether this stream is at eof 389 * after the last read attempt. 390 */ 391 392 protected bool prevCr = false; /** For a non-seekable stream indicates that 393 * the last readLine or readLineW ended on a 394 * '\r' character. 395 */ 396 397 this() {} 398 399 /*** 400 * Read up to size bytes into the buffer and return the number of bytes 401 * actually read. A return value of 0 indicates end-of-file. 402 */ 403 abstract size_t readBlock(void* buffer, size_t size); 404 405 // reads block of data of specified size, 406 // throws ReadException on error 407 void readExact(void* buffer, size_t size) { 408 for(;;) { 409 if (!size) return; 410 size_t readsize = readBlock(buffer, size); // return 0 on eof 411 if (readsize == 0) break; 412 buffer += readsize; 413 size -= readsize; 414 } 415 if (size != 0) 416 throw new ReadException("not enough data in stream"); 417 } 418 419 // reads block of data big enough to fill the given 420 // array, returns actual number of bytes read 421 size_t read(ubyte[] buffer) { 422 return readBlock(buffer.ptr, buffer.length); 423 } 424 425 // read a single value of desired type, 426 // throw ReadException on error 427 void read(out byte x) { readExact(&x, x.sizeof); } 428 void read(out ubyte x) { readExact(&x, x.sizeof); } 429 void read(out short x) { readExact(&x, x.sizeof); } 430 void read(out ushort x) { readExact(&x, x.sizeof); } 431 void read(out int x) { readExact(&x, x.sizeof); } 432 void read(out uint x) { readExact(&x, x.sizeof); } 433 void read(out long x) { readExact(&x, x.sizeof); } 434 void read(out ulong x) { readExact(&x, x.sizeof); } 435 void read(out float x) { readExact(&x, x.sizeof); } 436 void read(out double x) { readExact(&x, x.sizeof); } 437 void read(out real x) { readExact(&x, x.sizeof); } 438 void read(out char x) { readExact(&x, x.sizeof); } 439 void read(out wchar x) { readExact(&x, x.sizeof); } 440 void read(out dchar x) { readExact(&x, x.sizeof); } 441 442 // reads a string, written earlier by write() 443 void read(out char[] s) { 444 size_t len; 445 read(len); 446 s = readString(len); 447 } 448 449 // reads a Unicode string, written earlier by write() 450 void read(out wchar[] s) { 451 size_t len; 452 read(len); 453 s = readStringW(len); 454 } 455 456 // reads a line, terminated by either CR, LF, CR/LF, or EOF 457 char[] readLine() { 458 return readLine(null); 459 } 460 461 // reads a line, terminated by either CR, LF, CR/LF, or EOF 462 // reusing the memory in buffer if result will fit and otherwise 463 // allocates a new string 464 char[] readLine(char[] result) { 465 size_t strlen = 0; 466 char ch = getc(); 467 while (readable) { 468 switch (ch) { 469 case '\r': 470 if (seekable) { 471 ch = getc(); 472 if (ch != '\n') 473 ungetc(ch); 474 } else { 475 prevCr = true; 476 } 477 goto case; 478 case '\n': 479 case char.init: 480 result.length = strlen; 481 return result; 482 483 default: 484 if (strlen < result.length) { 485 result[strlen] = ch; 486 } else { 487 result ~= ch; 488 } 489 strlen++; 490 } 491 ch = getc(); 492 } 493 result.length = strlen; 494 return result; 495 } 496 497 // reads a Unicode line, terminated by either CR, LF, CR/LF, 498 // or EOF; pretty much the same as the above, working with 499 // wchars rather than chars 500 wchar[] readLineW() { 501 return readLineW(null); 502 } 503 504 // reads a Unicode line, terminated by either CR, LF, CR/LF, 505 // or EOF; 506 // fills supplied buffer if line fits and otherwise allocates a new string. 507 wchar[] readLineW(wchar[] result) { 508 size_t strlen = 0; 509 wchar c = getcw(); 510 while (readable) { 511 switch (c) { 512 case '\r': 513 if (seekable) { 514 c = getcw(); 515 if (c != '\n') 516 ungetcw(c); 517 } else { 518 prevCr = true; 519 } 520 goto case; 521 case '\n': 522 case wchar.init: 523 result.length = strlen; 524 return result; 525 526 default: 527 if (strlen < result.length) { 528 result[strlen] = c; 529 } else { 530 result ~= c; 531 } 532 strlen++; 533 } 534 c = getcw(); 535 } 536 result.length = strlen; 537 return result; 538 } 539 540 // iterate through the stream line-by-line - due to Regan Heath 541 int opApply(scope int delegate(ref char[] line) dg) { 542 int res = 0; 543 char[128] buf; 544 while (!eof) { 545 char[] line = readLine(buf); 546 res = dg(line); 547 if (res) break; 548 } 549 return res; 550 } 551 552 // iterate through the stream line-by-line with line count and string 553 int opApply(scope int delegate(ref ulong n, ref char[] line) dg) { 554 int res = 0; 555 ulong n = 1; 556 char[128] buf; 557 while (!eof) { 558 auto line = readLine(buf); 559 res = dg(n,line); 560 if (res) break; 561 n++; 562 } 563 return res; 564 } 565 566 // iterate through the stream line-by-line with wchar[] 567 int opApply(scope int delegate(ref wchar[] line) dg) { 568 int res = 0; 569 wchar[128] buf; 570 while (!eof) { 571 auto line = readLineW(buf); 572 res = dg(line); 573 if (res) break; 574 } 575 return res; 576 } 577 578 // iterate through the stream line-by-line with line count and wchar[] 579 int opApply(scope int delegate(ref ulong n, ref wchar[] line) dg) { 580 int res = 0; 581 ulong n = 1; 582 wchar[128] buf; 583 while (!eof) { 584 auto line = readLineW(buf); 585 res = dg(n,line); 586 if (res) break; 587 n++; 588 } 589 return res; 590 } 591 592 // reads a string of given length, throws 593 // ReadException on error 594 char[] readString(size_t length) { 595 char[] result = new char[length]; 596 readExact(result.ptr, length); 597 return result; 598 } 599 600 // reads a Unicode string of given length, throws 601 // ReadException on error 602 wchar[] readStringW(size_t length) { 603 auto result = new wchar[length]; 604 readExact(result.ptr, result.length * wchar.sizeof); 605 return result; 606 } 607 608 // unget buffer 609 private wchar[] unget; 610 final bool ungetAvailable() { return unget.length > 1; } 611 612 // reads and returns next character from the stream, 613 // handles characters pushed back by ungetc() 614 // returns char.init on eof. 615 char getc() { 616 char c; 617 if (prevCr) { 618 prevCr = false; 619 c = getc(); 620 if (c != '\n') 621 return c; 622 } 623 if (unget.length > 1) { 624 c = cast(char)unget[unget.length - 1]; 625 unget.length = unget.length - 1; 626 } else { 627 readBlock(&c,1); 628 } 629 return c; 630 } 631 632 // reads and returns next Unicode character from the 633 // stream, handles characters pushed back by ungetc() 634 // returns wchar.init on eof. 635 wchar getcw() { 636 wchar c; 637 if (prevCr) { 638 prevCr = false; 639 c = getcw(); 640 if (c != '\n') 641 return c; 642 } 643 if (unget.length > 1) { 644 c = unget[unget.length - 1]; 645 unget.length = unget.length - 1; 646 } else { 647 void* buf = &c; 648 size_t n = readBlock(buf,2); 649 if (n == 1 && readBlock(buf+1,1) == 0) 650 throw new ReadException("not enough data in stream"); 651 } 652 return c; 653 } 654 655 // pushes back character c into the stream; only has 656 // effect on further calls to getc() and getcw() 657 char ungetc(char c) { 658 if (c == c.init) return c; 659 // first byte is a dummy so that we never set length to 0 660 if (unget.length == 0) 661 unget.length = 1; 662 unget ~= c; 663 return c; 664 } 665 666 // pushes back Unicode character c into the stream; only 667 // has effect on further calls to getc() and getcw() 668 wchar ungetcw(wchar c) { 669 if (c == c.init) return c; 670 // first byte is a dummy so that we never set length to 0 671 if (unget.length == 0) 672 unget.length = 1; 673 unget ~= c; 674 return c; 675 } 676 677 int vreadf(TypeInfo[] arguments, va_list args) { 678 string fmt; 679 int j = 0; 680 int count = 0, i = 0; 681 char c; 682 bool firstCharacter = true; 683 while ((j < arguments.length || i < fmt.length) && !eof) { 684 if(firstCharacter) { 685 c = getc(); 686 firstCharacter = false; 687 } 688 if (fmt.length == 0 || i == fmt.length) { 689 i = 0; 690 if (arguments[j] is typeid(string) || arguments[j] is typeid(char[]) 691 || arguments[j] is typeid(const(char)[])) { 692 fmt = va_arg!(string)(args); 693 j++; 694 continue; 695 } else if (arguments[j] is typeid(int*) || 696 arguments[j] is typeid(byte*) || 697 arguments[j] is typeid(short*) || 698 arguments[j] is typeid(long*)) { 699 fmt = "%d"; 700 } else if (arguments[j] is typeid(uint*) || 701 arguments[j] is typeid(ubyte*) || 702 arguments[j] is typeid(ushort*) || 703 arguments[j] is typeid(ulong*)) { 704 fmt = "%d"; 705 } else if (arguments[j] is typeid(float*) || 706 arguments[j] is typeid(double*) || 707 arguments[j] is typeid(real*)) { 708 fmt = "%f"; 709 } else if (arguments[j] is typeid(char[]*) || 710 arguments[j] is typeid(wchar[]*) || 711 arguments[j] is typeid(dchar[]*)) { 712 fmt = "%s"; 713 } else if (arguments[j] is typeid(char*)) { 714 fmt = "%c"; 715 } 716 } 717 if (fmt[i] == '%') { // a field 718 i++; 719 bool suppress = false; 720 if (fmt[i] == '*') { // suppress assignment 721 suppress = true; 722 i++; 723 } 724 // read field width 725 int width = 0; 726 while (isDigit(fmt[i])) { 727 width = width * 10 + (fmt[i] - '0'); 728 i++; 729 } 730 if (width == 0) 731 width = -1; 732 // skip any modifier if present 733 if (fmt[i] == 'h' || fmt[i] == 'l' || fmt[i] == 'L') 734 i++; 735 // check the typechar and act accordingly 736 switch (fmt[i]) { 737 case 'd': // decimal/hexadecimal/octal integer 738 case 'D': 739 case 'u': 740 case 'U': 741 case 'o': 742 case 'O': 743 case 'x': 744 case 'X': 745 case 'i': 746 case 'I': 747 { 748 while (isWhite(c)) { 749 c = getc(); 750 count++; 751 } 752 bool neg = false; 753 if (c == '-') { 754 neg = true; 755 c = getc(); 756 count++; 757 } else if (c == '+') { 758 c = getc(); 759 count++; 760 } 761 char ifmt = cast(char)(fmt[i] | 0x20); 762 if (ifmt == 'i') { // undetermined base 763 if (c == '0') { // octal or hex 764 c = getc(); 765 count++; 766 if (c == 'x' || c == 'X') { // hex 767 ifmt = 'x'; 768 c = getc(); 769 count++; 770 } else { // octal 771 ifmt = 'o'; 772 } 773 } 774 else // decimal 775 ifmt = 'd'; 776 } 777 long n = 0; 778 switch (ifmt) 779 { 780 case 'd': // decimal 781 case 'u': { 782 while (isDigit(c) && width) { 783 n = n * 10 + (c - '0'); 784 width--; 785 c = getc(); 786 count++; 787 } 788 } break; 789 790 case 'o': { // octal 791 while (isOctalDigit(c) && width) { 792 n = n * 8 + (c - '0'); 793 width--; 794 c = getc(); 795 count++; 796 } 797 } break; 798 799 case 'x': { // hexadecimal 800 while (isHexDigit(c) && width) { 801 n *= 0x10; 802 if (isDigit(c)) 803 n += c - '0'; 804 else 805 n += 0xA + (c | 0x20) - 'a'; 806 width--; 807 c = getc(); 808 count++; 809 } 810 } break; 811 812 default: 813 assert(0); 814 } 815 if (neg) 816 n = -n; 817 if (arguments[j] is typeid(int*)) { 818 int* p = va_arg!(int*)(args); 819 *p = cast(int)n; 820 } else if (arguments[j] is typeid(short*)) { 821 short* p = va_arg!(short*)(args); 822 *p = cast(short)n; 823 } else if (arguments[j] is typeid(byte*)) { 824 byte* p = va_arg!(byte*)(args); 825 *p = cast(byte)n; 826 } else if (arguments[j] is typeid(long*)) { 827 long* p = va_arg!(long*)(args); 828 *p = n; 829 } else if (arguments[j] is typeid(uint*)) { 830 uint* p = va_arg!(uint*)(args); 831 *p = cast(uint)n; 832 } else if (arguments[j] is typeid(ushort*)) { 833 ushort* p = va_arg!(ushort*)(args); 834 *p = cast(ushort)n; 835 } else if (arguments[j] is typeid(ubyte*)) { 836 ubyte* p = va_arg!(ubyte*)(args); 837 *p = cast(ubyte)n; 838 } else if (arguments[j] is typeid(ulong*)) { 839 ulong* p = va_arg!(ulong*)(args); 840 *p = cast(ulong)n; 841 } 842 j++; 843 i++; 844 } break; 845 846 case 'f': // float 847 case 'F': 848 case 'e': 849 case 'E': 850 case 'g': 851 case 'G': 852 { 853 while (isWhite(c)) { 854 c = getc(); 855 count++; 856 } 857 bool neg = false; 858 if (c == '-') { 859 neg = true; 860 c = getc(); 861 count++; 862 } else if (c == '+') { 863 c = getc(); 864 count++; 865 } 866 real r = 0; 867 while (isDigit(c) && width) { 868 r = r * 10 + (c - '0'); 869 width--; 870 c = getc(); 871 count++; 872 } 873 if (width && c == '.') { 874 width--; 875 c = getc(); 876 count++; 877 double frac = 1; 878 while (isDigit(c) && width) { 879 r = r * 10 + (c - '0'); 880 frac *= 10; 881 width--; 882 c = getc(); 883 count++; 884 } 885 r /= frac; 886 } 887 if (width && (c == 'e' || c == 'E')) { 888 width--; 889 c = getc(); 890 count++; 891 if (width) { 892 bool expneg = false; 893 if (c == '-') { 894 expneg = true; 895 width--; 896 c = getc(); 897 count++; 898 } else if (c == '+') { 899 width--; 900 c = getc(); 901 count++; 902 } 903 real exp = 0; 904 while (isDigit(c) && width) { 905 exp = exp * 10 + (c - '0'); 906 width--; 907 c = getc(); 908 count++; 909 } 910 if (expneg) { 911 while (exp--) 912 r /= 10; 913 } else { 914 while (exp--) 915 r *= 10; 916 } 917 } 918 } 919 if(width && (c == 'n' || c == 'N')) { 920 width--; 921 c = getc(); 922 count++; 923 if(width && (c == 'a' || c == 'A')) { 924 width--; 925 c = getc(); 926 count++; 927 if(width && (c == 'n' || c == 'N')) { 928 width--; 929 c = getc(); 930 count++; 931 r = real.nan; 932 } 933 } 934 } 935 if(width && (c == 'i' || c == 'I')) { 936 width--; 937 c = getc(); 938 count++; 939 if(width && (c == 'n' || c == 'N')) { 940 width--; 941 c = getc(); 942 count++; 943 if(width && (c == 'f' || c == 'F')) { 944 width--; 945 c = getc(); 946 count++; 947 r = real.infinity; 948 } 949 } 950 } 951 if (neg) 952 r = -r; 953 if (arguments[j] is typeid(float*)) { 954 float* p = va_arg!(float*)(args); 955 *p = r; 956 } else if (arguments[j] is typeid(double*)) { 957 double* p = va_arg!(double*)(args); 958 *p = r; 959 } else if (arguments[j] is typeid(real*)) { 960 real* p = va_arg!(real*)(args); 961 *p = r; 962 } 963 j++; 964 i++; 965 } break; 966 967 case 's': { // string 968 while (isWhite(c)) { 969 c = getc(); 970 count++; 971 } 972 char[] s; 973 char[]* p; 974 size_t strlen; 975 if (arguments[j] is typeid(char[]*)) { 976 p = va_arg!(char[]*)(args); 977 s = *p; 978 } 979 while (!isWhite(c) && c != char.init) { 980 if (strlen < s.length) { 981 s[strlen] = c; 982 } else { 983 s ~= c; 984 } 985 strlen++; 986 c = getc(); 987 count++; 988 } 989 s = s[0 .. strlen]; 990 if (arguments[j] is typeid(char[]*)) { 991 *p = s; 992 } else if (arguments[j] is typeid(char*)) { 993 s ~= 0; 994 auto q = va_arg!(char*)(args); 995 q[0 .. s.length] = s[]; 996 } else if (arguments[j] is typeid(wchar[]*)) { 997 auto q = va_arg!(const(wchar)[]*)(args); 998 *q = toUTF16(s); 999 } else if (arguments[j] is typeid(dchar[]*)) { 1000 auto q = va_arg!(const(dchar)[]*)(args); 1001 *q = toUTF32(s); 1002 } 1003 j++; 1004 i++; 1005 } break; 1006 1007 case 'c': { // character(s) 1008 char* s = va_arg!(char*)(args); 1009 if (width < 0) 1010 width = 1; 1011 else 1012 while (isWhite(c)) { 1013 c = getc(); 1014 count++; 1015 } 1016 while (width-- && !eof) { 1017 *(s++) = c; 1018 c = getc(); 1019 count++; 1020 } 1021 j++; 1022 i++; 1023 } break; 1024 1025 case 'n': { // number of chars read so far 1026 int* p = va_arg!(int*)(args); 1027 *p = count; 1028 j++; 1029 i++; 1030 } break; 1031 1032 default: // read character as is 1033 goto nws; 1034 } 1035 } else if (isWhite(fmt[i])) { // skip whitespace 1036 while (isWhite(c)) 1037 c = getc(); 1038 i++; 1039 } else { // read character as is 1040 nws: 1041 if (fmt[i] != c) 1042 break; 1043 c = getc(); 1044 i++; 1045 } 1046 } 1047 ungetc(c); 1048 return count; 1049 } 1050 1051 int readf(...) { 1052 return vreadf(_arguments, _argptr); 1053 } 1054 1055 // returns estimated number of bytes available for immediate reading 1056 @property size_t available() { return 0; } 1057 1058 /*** 1059 * Write up to size bytes from buffer in the stream, returning the actual 1060 * number of bytes that were written. 1061 */ 1062 abstract size_t writeBlock(const void* buffer, size_t size); 1063 1064 // writes block of data of specified size, 1065 // throws WriteException on error 1066 void writeExact(const void* buffer, size_t size) { 1067 const(void)* p = buffer; 1068 for(;;) { 1069 if (!size) return; 1070 size_t writesize = writeBlock(p, size); 1071 if (writesize == 0) break; 1072 p += writesize; 1073 size -= writesize; 1074 } 1075 if (size != 0) 1076 throw new WriteException("unable to write to stream"); 1077 } 1078 1079 // writes the given array of bytes, returns 1080 // actual number of bytes written 1081 size_t write(const(ubyte)[] buffer) { 1082 return writeBlock(buffer.ptr, buffer.length); 1083 } 1084 1085 // write a single value of desired type, 1086 // throw WriteException on error 1087 void write(byte x) { writeExact(&x, x.sizeof); } 1088 void write(ubyte x) { writeExact(&x, x.sizeof); } 1089 void write(short x) { writeExact(&x, x.sizeof); } 1090 void write(ushort x) { writeExact(&x, x.sizeof); } 1091 void write(int x) { writeExact(&x, x.sizeof); } 1092 void write(uint x) { writeExact(&x, x.sizeof); } 1093 void write(long x) { writeExact(&x, x.sizeof); } 1094 void write(ulong x) { writeExact(&x, x.sizeof); } 1095 void write(float x) { writeExact(&x, x.sizeof); } 1096 void write(double x) { writeExact(&x, x.sizeof); } 1097 void write(real x) { writeExact(&x, x.sizeof); } 1098 void write(char x) { writeExact(&x, x.sizeof); } 1099 void write(wchar x) { writeExact(&x, x.sizeof); } 1100 void write(dchar x) { writeExact(&x, x.sizeof); } 1101 1102 // writes a string, together with its length 1103 void write(const(char)[] s) { 1104 write(s.length); 1105 writeString(s); 1106 } 1107 1108 // writes a Unicode string, together with its length 1109 void write(const(wchar)[] s) { 1110 write(s.length); 1111 writeStringW(s); 1112 } 1113 1114 // writes a line, throws WriteException on error 1115 void writeLine(const(char)[] s) { 1116 writeString(s); 1117 version (Windows) 1118 writeString("\r\n"); 1119 else version (Mac) 1120 writeString("\r"); 1121 else 1122 writeString("\n"); 1123 } 1124 1125 // writes a Unicode line, throws WriteException on error 1126 void writeLineW(const(wchar)[] s) { 1127 writeStringW(s); 1128 version (Windows) 1129 writeStringW("\r\n"); 1130 else version (Mac) 1131 writeStringW("\r"); 1132 else 1133 writeStringW("\n"); 1134 } 1135 1136 // writes a string, throws WriteException on error 1137 void writeString(const(char)[] s) { 1138 writeExact(s.ptr, s.length); 1139 } 1140 1141 // writes a Unicode string, throws WriteException on error 1142 void writeStringW(const(wchar)[] s) { 1143 writeExact(s.ptr, s.length * wchar.sizeof); 1144 } 1145 1146 // writes data to stream using vprintf() syntax, 1147 // returns number of bytes written 1148 size_t vprintf(const(char)[] format, va_list args) { 1149 // shamelessly stolen from OutBuffer, 1150 // by Walter's permission 1151 char[1024] buffer; 1152 char* p = buffer.ptr; 1153 // Can't use `tempCString()` here as it will result in compilation error: 1154 // "cannot mix core.std.stdlib.alloca() and exception handling". 1155 auto f = toStringz(format); 1156 size_t psize = buffer.length; 1157 size_t count; 1158 while (true) { 1159 version (Windows) { 1160 count = vsnprintf(p, psize, f, args); 1161 if (count != -1) 1162 break; 1163 psize *= 2; 1164 p = cast(char*) alloca(psize); 1165 } else version (Posix) { 1166 count = vsnprintf(p, psize, f, args); 1167 if (count == -1) 1168 psize *= 2; 1169 else if (count >= psize) 1170 psize = count + 1; 1171 else 1172 break; 1173 p = cast(char*) alloca(psize); 1174 } else 1175 throw new Exception("unsupported platform"); 1176 } 1177 writeString(p[0 .. count]); 1178 return count; 1179 } 1180 1181 // writes data to stream using printf() syntax, 1182 // returns number of bytes written 1183 size_t printf(const(char)[] format, ...) { 1184 va_list ap; 1185 va_start(ap, format); 1186 auto result = vprintf(format, ap); 1187 va_end(ap); 1188 return result; 1189 } 1190 1191 private void doFormatCallback(dchar c) { 1192 char[4] buf; 1193 auto b = undead.utf.toUTF8(buf, c); 1194 writeString(b); 1195 } 1196 1197 // writes data to stream using writef() syntax, 1198 OutputStream writef(...) { 1199 return writefx(_arguments,_argptr,0); 1200 } 1201 1202 // writes data with trailing newline 1203 OutputStream writefln(...) { 1204 return writefx(_arguments,_argptr,1); 1205 } 1206 1207 // writes data with optional trailing newline 1208 OutputStream writefx(TypeInfo[] arguments, va_list argptr, int newline=false) { 1209 doFormat(&doFormatCallback,arguments,argptr); 1210 if (newline) 1211 writeLine(""); 1212 return this; 1213 } 1214 1215 /*** 1216 * Copies all data from s into this stream. 1217 * This may throw ReadException or WriteException on failure. 1218 * This restores the file position of s so that it is unchanged. 1219 */ 1220 void copyFrom(Stream s) { 1221 if (seekable) { 1222 ulong pos = s.position; 1223 s.position = 0; 1224 copyFrom(s, s.size); 1225 s.position = pos; 1226 } else { 1227 ubyte[128] buf; 1228 while (!s.eof) { 1229 size_t m = s.readBlock(buf.ptr, buf.length); 1230 writeExact(buf.ptr, m); 1231 } 1232 } 1233 } 1234 1235 /*** 1236 * Copy a specified number of bytes from the given stream into this one. 1237 * This may throw ReadException or WriteException on failure. 1238 * Unlike the previous form, this doesn't restore the file position of s. 1239 */ 1240 void copyFrom(Stream s, ulong count) { 1241 ubyte[128] buf; 1242 while (count > 0) { 1243 size_t n = cast(size_t)(count<buf.length ? count : buf.length); 1244 s.readExact(buf.ptr, n); 1245 writeExact(buf.ptr, n); 1246 count -= n; 1247 } 1248 } 1249 1250 /*** 1251 * Change the current position of the stream. whence is either SeekPos.Set, in 1252 which case the offset is an absolute index from the beginning of the stream, 1253 SeekPos.Current, in which case the offset is a delta from the current 1254 position, or SeekPos.End, in which case the offset is a delta from the end of 1255 the stream (negative or zero offsets only make sense in that case). This 1256 returns the new file position. 1257 */ 1258 abstract ulong seek(long offset, SeekPos whence); 1259 1260 /*** 1261 * Aliases for their normal seek counterparts. 1262 */ 1263 ulong seekSet(long offset) { return seek (offset, SeekPos.Set); } 1264 ulong seekCur(long offset) { return seek (offset, SeekPos.Current); } /// ditto 1265 ulong seekEnd(long offset) { return seek (offset, SeekPos.End); } /// ditto 1266 1267 /*** 1268 * Sets file position. Equivalent to calling seek(pos, SeekPos.Set). 1269 */ 1270 @property void position(ulong pos) { seek(cast(long)pos, SeekPos.Set); } 1271 1272 /*** 1273 * Returns current file position. Equivalent to seek(0, SeekPos.Current). 1274 */ 1275 @property ulong position() { return seek(0, SeekPos.Current); } 1276 1277 /*** 1278 * Retrieve the size of the stream in bytes. 1279 * The stream must be seekable or a SeekException is thrown. 1280 */ 1281 @property ulong size() { 1282 assertSeekable(); 1283 ulong pos = position, result = seek(0, SeekPos.End); 1284 position = pos; 1285 return result; 1286 } 1287 1288 // returns true if end of stream is reached, false otherwise 1289 @property bool eof() { 1290 // for unseekable streams we only know the end when we read it 1291 if (readEOF && !ungetAvailable()) 1292 return true; 1293 else if (seekable) 1294 return position == size; 1295 else 1296 return false; 1297 } 1298 1299 // returns true if the stream is open 1300 @property bool isOpen() { return isopen; } 1301 1302 // flush the buffer if writeable 1303 void flush() { 1304 if (unget.length > 1) 1305 unget.length = 1; // keep at least 1 so that data ptr stays 1306 } 1307 1308 // close the stream somehow; the default just flushes the buffer 1309 void close() { 1310 if (isopen) 1311 flush(); 1312 readEOF = prevCr = isopen = readable = writeable = seekable = false; 1313 } 1314 1315 /*** 1316 * Read the entire stream and return it as a string. 1317 * If the stream is not seekable the contents from the current position to eof 1318 * is read and returned. 1319 */ 1320 override string toString() { 1321 if (!readable) 1322 return super.toString(); 1323 try 1324 { 1325 size_t pos; 1326 size_t rdlen; 1327 size_t blockSize; 1328 char[] result; 1329 if (seekable) { 1330 ulong orig_pos = position; 1331 scope(exit) position = orig_pos; 1332 position = 0; 1333 blockSize = cast(size_t)size; 1334 result = new char[blockSize]; 1335 while (blockSize > 0) { 1336 rdlen = readBlock(&result[pos], blockSize); 1337 pos += rdlen; 1338 blockSize -= rdlen; 1339 } 1340 } else { 1341 blockSize = 4096; 1342 result = new char[blockSize]; 1343 while ((rdlen = readBlock(&result[pos], blockSize)) > 0) { 1344 pos += rdlen; 1345 blockSize += rdlen; 1346 result.length = result.length + blockSize; 1347 } 1348 } 1349 return cast(string) result[0 .. pos]; 1350 } 1351 catch (Throwable) 1352 { 1353 return super.toString(); 1354 } 1355 } 1356 1357 /*** 1358 * Get a hash of the stream by reading each byte and using it in a CRC-32 1359 * checksum. 1360 */ 1361 override size_t toHash() @trusted { 1362 if (!readable || !seekable) 1363 return super.toHash(); 1364 try 1365 { 1366 ulong pos = position; 1367 scope(exit) position = pos; 1368 CRC32 crc; 1369 crc.start(); 1370 position = 0; 1371 ulong len = size; 1372 for (ulong i = 0; i < len; i++) 1373 { 1374 ubyte c; 1375 read(c); 1376 crc.put(c); 1377 } 1378 1379 union resUnion 1380 { 1381 size_t hash; 1382 ubyte[4] crcVal; 1383 } 1384 resUnion res; 1385 res.crcVal = crc.finish(); 1386 return res.hash; 1387 } 1388 catch (Throwable) 1389 { 1390 return super.toHash(); 1391 } 1392 } 1393 1394 // helper for checking that the stream is readable 1395 final protected void assertReadable() { 1396 if (!readable) 1397 throw new ReadException("Stream is not readable"); 1398 } 1399 // helper for checking that the stream is writeable 1400 final protected void assertWriteable() { 1401 if (!writeable) 1402 throw new WriteException("Stream is not writeable"); 1403 } 1404 // helper for checking that the stream is seekable 1405 final protected void assertSeekable() { 1406 if (!seekable) 1407 throw new SeekException("Stream is not seekable"); 1408 } 1409 1410 unittest { // unit test for Issue 3363 1411 import std.stdio; 1412 immutable fileName = undead.internal.file.deleteme ~ "-issue3363.txt"; 1413 auto w = std.stdio.File(fileName, "w"); 1414 scope (exit) std.file.remove(fileName); 1415 w.write("one two three"); 1416 w.close(); 1417 auto r = std.stdio.File(fileName, "r"); 1418 const(char)[] constChar; 1419 string str; 1420 char[] chars; 1421 r.readf("%s %s %s", &constChar, &str, &chars); 1422 assert (constChar == "one", constChar); 1423 assert (str == "two", str); 1424 assert (chars == "three", chars); 1425 } 1426 1427 unittest { //unit tests for Issue 1668 1428 void tryFloatRoundtrip(float x, string fmt = "", string pad = "") { 1429 auto s = new MemoryStream(); 1430 s.writef(fmt, x, pad); 1431 s.position = 0; 1432 1433 float f; 1434 assert(s.readf(&f)); 1435 assert(x == f || (x != x && f != f)); //either equal or both NaN 1436 } 1437 1438 tryFloatRoundtrip(1.0); 1439 tryFloatRoundtrip(1.0, "%f"); 1440 tryFloatRoundtrip(1.0, "", " "); 1441 tryFloatRoundtrip(1.0, "%f", " "); 1442 1443 tryFloatRoundtrip(3.14); 1444 tryFloatRoundtrip(3.14, "%f"); 1445 tryFloatRoundtrip(3.14, "", " "); 1446 tryFloatRoundtrip(3.14, "%f", " "); 1447 1448 float nan = float.nan; 1449 tryFloatRoundtrip(nan); 1450 tryFloatRoundtrip(nan, "%f"); 1451 tryFloatRoundtrip(nan, "", " "); 1452 tryFloatRoundtrip(nan, "%f", " "); 1453 1454 float inf = 1.0/0.0; 1455 tryFloatRoundtrip(inf); 1456 tryFloatRoundtrip(inf, "%f"); 1457 tryFloatRoundtrip(inf, "", " "); 1458 tryFloatRoundtrip(inf, "%f", " "); 1459 1460 tryFloatRoundtrip(-inf); 1461 tryFloatRoundtrip(-inf,"%f"); 1462 tryFloatRoundtrip(-inf, "", " "); 1463 tryFloatRoundtrip(-inf, "%f", " "); 1464 } 1465 } 1466 1467 /*** 1468 * A base class for streams that wrap a source stream with additional 1469 * functionality. 1470 * 1471 * The method implementations forward read/write/seek calls to the 1472 * source stream. A FilterStream can change the position of the source stream 1473 * arbitrarily and may not keep the source stream state in sync with the 1474 * FilterStream, even upon flushing and closing the FilterStream. It is 1475 * recommended to not make any assumptions about the state of the source position 1476 * and read/write state after a FilterStream has acted upon it. Specifc subclasses 1477 * of FilterStream should document how they modify the source stream and if any 1478 * invariants hold true between the source and filter. 1479 */ 1480 class FilterStream : Stream { 1481 private Stream s; // source stream 1482 1483 /// Property indicating when this stream closes to close the source stream as 1484 /// well. 1485 /// Defaults to true. 1486 bool nestClose = true; 1487 1488 /// Construct a FilterStream for the given source. 1489 this(Stream source) { 1490 s = source; 1491 resetSource(); 1492 } 1493 1494 // source getter/setter 1495 1496 /*** 1497 * Get the current source stream. 1498 */ 1499 final Stream source(){return s;} 1500 1501 /*** 1502 * Set the current source stream. 1503 * 1504 * Setting the source stream closes this stream before attaching the new 1505 * source. Attaching an open stream reopens this stream and resets the stream 1506 * state. 1507 */ 1508 void source(Stream s) { 1509 close(); 1510 this.s = s; 1511 resetSource(); 1512 } 1513 1514 /*** 1515 * Indicates the source stream changed state and that this stream should reset 1516 * any readable, writeable, seekable, isopen and buffering flags. 1517 */ 1518 void resetSource() { 1519 if (s !is null) { 1520 readable = s.readable; 1521 writeable = s.writeable; 1522 seekable = s.seekable; 1523 isopen = s.isOpen; 1524 } else { 1525 readable = writeable = seekable = false; 1526 isopen = false; 1527 } 1528 readEOF = prevCr = false; 1529 } 1530 1531 // read from source 1532 override size_t readBlock(void* buffer, size_t size) { 1533 size_t res = s.readBlock(buffer,size); 1534 readEOF = res == 0; 1535 return res; 1536 } 1537 1538 // write to source 1539 override size_t writeBlock(const void* buffer, size_t size) { 1540 return s.writeBlock(buffer,size); 1541 } 1542 1543 // close stream 1544 override void close() { 1545 if (isopen) { 1546 super.close(); 1547 if (nestClose) 1548 s.close(); 1549 } 1550 } 1551 1552 // seek on source 1553 override ulong seek(long offset, SeekPos whence) { 1554 readEOF = false; 1555 return s.seek(offset,whence); 1556 } 1557 1558 override @property size_t available() { return s.available; } 1559 override void flush() { super.flush(); s.flush(); } 1560 } 1561 1562 /*** 1563 * This subclass is for buffering a source stream. 1564 * 1565 * A buffered stream must be 1566 * closed explicitly to ensure the final buffer content is written to the source 1567 * stream. The source stream position is changed according to the block size so 1568 * reading or writing to the BufferedStream may not change the source stream 1569 * position by the same amount. 1570 */ 1571 class BufferedStream : FilterStream { 1572 ubyte[] buffer; // buffer, if any 1573 size_t bufferCurPos; // current position in buffer 1574 size_t bufferLen; // amount of data in buffer 1575 bool bufferDirty = false; 1576 size_t bufferSourcePos; // position in buffer of source stream position 1577 ulong streamPos; // absolute position in source stream 1578 1579 /* Example of relationship between fields: 1580 * 1581 * s ...01234567890123456789012EOF 1582 * buffer |-- --| 1583 * bufferCurPos | 1584 * bufferLen |-- --| 1585 * bufferSourcePos | 1586 * 1587 */ 1588 1589 invariant() { 1590 assert(bufferSourcePos <= bufferLen); 1591 assert(bufferCurPos <= bufferLen); 1592 assert(bufferLen <= buffer.length); 1593 } 1594 1595 enum size_t DefaultBufferSize = 8192; 1596 1597 /*** 1598 * Create a buffered stream for the stream source with the buffer size 1599 * bufferSize. 1600 */ 1601 this(Stream source, size_t bufferSize = DefaultBufferSize) { 1602 super(source); 1603 if (bufferSize) 1604 buffer = new ubyte[bufferSize]; 1605 } 1606 1607 override protected void resetSource() { 1608 super.resetSource(); 1609 streamPos = 0; 1610 bufferLen = bufferSourcePos = bufferCurPos = 0; 1611 bufferDirty = false; 1612 } 1613 1614 // reads block of data of specified size using any buffered data 1615 // returns actual number of bytes read 1616 override size_t readBlock(void* result, size_t len) { 1617 if (len == 0) return 0; 1618 1619 assertReadable(); 1620 1621 ubyte* outbuf = cast(ubyte*)result; 1622 size_t readsize = 0; 1623 1624 if (bufferCurPos + len < bufferLen) { 1625 // buffer has all the data so copy it 1626 outbuf[0 .. len] = buffer[bufferCurPos .. bufferCurPos+len]; 1627 bufferCurPos += len; 1628 readsize = len; 1629 goto ExitRead; 1630 } 1631 1632 readsize = bufferLen - bufferCurPos; 1633 if (readsize > 0) { 1634 // buffer has some data so copy what is left 1635 outbuf[0 .. readsize] = buffer[bufferCurPos .. bufferLen]; 1636 outbuf += readsize; 1637 bufferCurPos += readsize; 1638 len -= readsize; 1639 } 1640 1641 flush(); 1642 1643 if (len >= buffer.length) { 1644 // buffer can't hold the data so fill output buffer directly 1645 size_t siz = super.readBlock(outbuf, len); 1646 readsize += siz; 1647 streamPos += siz; 1648 } else { 1649 // read a new block into buffer 1650 bufferLen = super.readBlock(buffer.ptr, buffer.length); 1651 if (bufferLen < len) len = bufferLen; 1652 outbuf[0 .. len] = buffer[0 .. len]; 1653 bufferSourcePos = bufferLen; 1654 streamPos += bufferLen; 1655 bufferCurPos = len; 1656 readsize += len; 1657 } 1658 1659 ExitRead: 1660 return readsize; 1661 } 1662 1663 // write block of data of specified size 1664 // returns actual number of bytes written 1665 override size_t writeBlock(const void* result, size_t len) { 1666 assertWriteable(); 1667 1668 ubyte* buf = cast(ubyte*)result; 1669 size_t writesize = 0; 1670 1671 if (bufferLen == 0) { 1672 // buffer is empty so fill it if possible 1673 if ((len < buffer.length) && (readable)) { 1674 // read in data if the buffer is currently empty 1675 bufferLen = s.readBlock(buffer.ptr, buffer.length); 1676 bufferSourcePos = bufferLen; 1677 streamPos += bufferLen; 1678 1679 } else if (len >= buffer.length) { 1680 // buffer can't hold the data so write it directly and exit 1681 writesize = s.writeBlock(buf,len); 1682 streamPos += writesize; 1683 goto ExitWrite; 1684 } 1685 } 1686 1687 if (bufferCurPos + len <= buffer.length) { 1688 // buffer has space for all the data so copy it and exit 1689 buffer[bufferCurPos .. bufferCurPos+len] = buf[0 .. len]; 1690 bufferCurPos += len; 1691 bufferLen = bufferCurPos > bufferLen ? bufferCurPos : bufferLen; 1692 writesize = len; 1693 bufferDirty = true; 1694 goto ExitWrite; 1695 } 1696 1697 writesize = buffer.length - bufferCurPos; 1698 if (writesize > 0) { 1699 // buffer can take some data 1700 buffer[bufferCurPos .. buffer.length] = buf[0 .. writesize]; 1701 bufferCurPos = bufferLen = buffer.length; 1702 buf += writesize; 1703 len -= writesize; 1704 bufferDirty = true; 1705 } 1706 1707 assert(bufferCurPos == buffer.length); 1708 assert(bufferLen == buffer.length); 1709 1710 flush(); 1711 1712 writesize += writeBlock(buf,len); 1713 1714 ExitWrite: 1715 return writesize; 1716 } 1717 1718 override ulong seek(long offset, SeekPos whence) { 1719 assertSeekable(); 1720 1721 if ((whence != SeekPos.Current) || 1722 (offset + bufferCurPos < 0) || 1723 (offset + bufferCurPos >= bufferLen)) { 1724 flush(); 1725 streamPos = s.seek(offset,whence); 1726 } else { 1727 bufferCurPos += offset; 1728 } 1729 readEOF = false; 1730 return streamPos-bufferSourcePos+bufferCurPos; 1731 } 1732 1733 // Buffered readLine - Dave Fladebo 1734 // reads a line, terminated by either CR, LF, CR/LF, or EOF 1735 // reusing the memory in buffer if result will fit, otherwise 1736 // will reallocate (using concatenation) 1737 template TreadLine(T) { 1738 T[] readLine(T[] inBuffer) 1739 { 1740 size_t lineSize = 0; 1741 bool haveCR = false; 1742 T c = '\0'; 1743 size_t idx = 0; 1744 ubyte* pc = cast(ubyte*)&c; 1745 1746 L0: 1747 for(;;) { 1748 size_t start = bufferCurPos; 1749 L1: 1750 foreach(ubyte b; buffer[start .. bufferLen]) { 1751 bufferCurPos++; 1752 pc[idx] = b; 1753 if(idx < T.sizeof - 1) { 1754 idx++; 1755 continue L1; 1756 } else { 1757 idx = 0; 1758 } 1759 if(c == '\n' || haveCR) { 1760 if(haveCR && c != '\n') bufferCurPos--; 1761 break L0; 1762 } else { 1763 if(c == '\r') { 1764 haveCR = true; 1765 } else { 1766 if(lineSize < inBuffer.length) { 1767 inBuffer[lineSize] = c; 1768 } else { 1769 inBuffer ~= c; 1770 } 1771 lineSize++; 1772 } 1773 } 1774 } 1775 flush(); 1776 size_t res = super.readBlock(buffer.ptr, buffer.length); 1777 if(!res) break L0; // EOF 1778 bufferSourcePos = bufferLen = res; 1779 streamPos += res; 1780 } 1781 return inBuffer[0 .. lineSize]; 1782 } 1783 } // template TreadLine(T) 1784 1785 override char[] readLine(char[] inBuffer) { 1786 if (ungetAvailable()) 1787 return super.readLine(inBuffer); 1788 else 1789 return TreadLine!(char).readLine(inBuffer); 1790 } 1791 alias readLine = Stream.readLine; 1792 1793 override wchar[] readLineW(wchar[] inBuffer) { 1794 if (ungetAvailable()) 1795 return super.readLineW(inBuffer); 1796 else 1797 return TreadLine!(wchar).readLine(inBuffer); 1798 } 1799 alias readLineW = Stream.readLineW; 1800 1801 override void flush() 1802 out { 1803 assert(bufferCurPos == 0); 1804 assert(bufferSourcePos == 0); 1805 assert(bufferLen == 0); 1806 } 1807 do { 1808 if (writeable && bufferDirty) { 1809 if (bufferSourcePos != 0 && seekable) { 1810 // move actual file pointer to front of buffer 1811 streamPos = s.seek(-bufferSourcePos, SeekPos.Current); 1812 } 1813 // write buffer out 1814 bufferSourcePos = s.writeBlock(buffer.ptr, bufferLen); 1815 if (bufferSourcePos != bufferLen) { 1816 throw new WriteException("Unable to write to stream"); 1817 } 1818 } 1819 super.flush(); 1820 long diff = cast(long)bufferCurPos-bufferSourcePos; 1821 if (diff != 0 && seekable) { 1822 // move actual file pointer to current position 1823 streamPos = s.seek(diff, SeekPos.Current); 1824 } 1825 // reset buffer data to be empty 1826 bufferSourcePos = bufferCurPos = bufferLen = 0; 1827 bufferDirty = false; 1828 } 1829 1830 // returns true if end of stream is reached, false otherwise 1831 override @property bool eof() { 1832 if ((buffer.length == 0) || !readable) { 1833 return super.eof; 1834 } 1835 // some simple tests to avoid flushing 1836 if (ungetAvailable() || bufferCurPos != bufferLen) 1837 return false; 1838 if (bufferLen == buffer.length) 1839 flush(); 1840 size_t res = super.readBlock(&buffer[bufferLen],buffer.length-bufferLen); 1841 bufferSourcePos += res; 1842 bufferLen += res; 1843 streamPos += res; 1844 return readEOF; 1845 } 1846 1847 // returns size of stream 1848 override @property ulong size() { 1849 if (bufferDirty) flush(); 1850 return s.size; 1851 } 1852 1853 // returns estimated number of bytes available for immediate reading 1854 override @property size_t available() { 1855 return bufferLen - bufferCurPos; 1856 } 1857 } 1858 1859 /// An exception for File errors. 1860 class StreamFileException: StreamException { 1861 /// Construct a StreamFileException with given error message. 1862 this(string msg) { super(msg); } 1863 } 1864 1865 /// An exception for errors during File.open. 1866 class OpenException: StreamFileException { 1867 /// Construct an OpenFileException with given error message. 1868 this(string msg) { super(msg); } 1869 } 1870 1871 /// Specifies the $(LREF File) access mode used when opening the file. 1872 enum FileMode { 1873 In = 1, /// Opens the file for reading. 1874 Out = 2, /// Opens the file for writing. 1875 OutNew = 6, /// Opens the file for writing, creates a new file if it doesn't exist. 1876 Append = 10 /// Opens the file for writing, appending new data to the end of the file. 1877 } 1878 1879 version (Windows) { 1880 private import core.sys.windows.windows; 1881 extern (Windows) { 1882 void FlushFileBuffers(HANDLE hFile); 1883 DWORD GetFileType(HANDLE hFile); 1884 } 1885 } 1886 version (Posix) { 1887 private import core.sys.posix.fcntl; 1888 private import core.sys.posix.unistd; 1889 alias HANDLE = int; 1890 } 1891 1892 /// This subclass is for unbuffered file system streams. 1893 class File: Stream { 1894 1895 version (Windows) { 1896 private HANDLE hFile; 1897 } 1898 version (Posix) { 1899 private HANDLE hFile = -1; 1900 } 1901 1902 this() { 1903 super(); 1904 version (Windows) { 1905 hFile = null; 1906 } 1907 version (Posix) { 1908 hFile = -1; 1909 } 1910 isopen = false; 1911 } 1912 1913 // opens existing handle; use with care! 1914 this(HANDLE hFile, FileMode mode) { 1915 super(); 1916 this.hFile = hFile; 1917 readable = cast(bool)(mode & FileMode.In); 1918 writeable = cast(bool)(mode & FileMode.Out); 1919 version(Windows) { 1920 seekable = GetFileType(hFile) == 1; // FILE_TYPE_DISK 1921 } else { 1922 auto result = lseek(hFile, 0, 0); 1923 seekable = (result != ~0); 1924 } 1925 } 1926 1927 /*** 1928 * Create the stream with no open file, an open file in read mode, or an open 1929 * file with explicit file mode. 1930 * mode, if given, is a combination of FileMode.In 1931 * (indicating a file that can be read) and FileMode.Out (indicating a file 1932 * that can be written). 1933 * Opening a file for reading that doesn't exist will error. 1934 * Opening a file for writing that doesn't exist will create the file. 1935 * The FileMode.OutNew mode will open the file for writing and reset the 1936 * length to zero. 1937 * The FileMode.Append mode will open the file for writing and move the 1938 * file position to the end of the file. 1939 */ 1940 this(string filename, FileMode mode = FileMode.In) 1941 { 1942 this(); 1943 open(filename, mode); 1944 } 1945 1946 1947 /*** 1948 * Open a file for the stream, in an identical manner to the constructors. 1949 * If an error occurs an OpenException is thrown. 1950 */ 1951 void open(string filename, FileMode mode = FileMode.In) { 1952 close(); 1953 int access, share, createMode; 1954 parseMode(mode, access, share, createMode); 1955 seekable = true; 1956 readable = cast(bool)(mode & FileMode.In); 1957 writeable = cast(bool)(mode & FileMode.Out); 1958 version (Windows) { 1959 hFile = CreateFileW(filename.tempCString!wchar(), access, share, 1960 null, createMode, 0, null); 1961 isopen = hFile != INVALID_HANDLE_VALUE; 1962 } 1963 version (Posix) { 1964 hFile = core.sys.posix.fcntl.open(filename.tempCString(), access | createMode, share); 1965 isopen = hFile != -1; 1966 } 1967 if (!isopen) 1968 throw new OpenException(cast(string) ("Cannot open or create file '" 1969 ~ filename ~ "'")); 1970 else if ((mode & FileMode.Append) == FileMode.Append) 1971 seekEnd(0); 1972 } 1973 1974 private void parseMode(int mode, 1975 out int access, 1976 out int share, 1977 out int createMode) { 1978 version (Windows) { 1979 share |= FILE_SHARE_READ | FILE_SHARE_WRITE; 1980 if (mode & FileMode.In) { 1981 access |= GENERIC_READ; 1982 createMode = OPEN_EXISTING; 1983 } 1984 if (mode & FileMode.Out) { 1985 access |= GENERIC_WRITE; 1986 createMode = OPEN_ALWAYS; // will create if not present 1987 } 1988 if ((mode & FileMode.OutNew) == FileMode.OutNew) { 1989 createMode = CREATE_ALWAYS; // resets file 1990 } 1991 } 1992 version (Posix) { 1993 share = octal!666; 1994 if (mode & FileMode.In) { 1995 access = O_RDONLY; 1996 } 1997 if (mode & FileMode.Out) { 1998 createMode = O_CREAT; // will create if not present 1999 access = O_WRONLY; 2000 } 2001 if (access == (O_WRONLY | O_RDONLY)) { 2002 access = O_RDWR; 2003 } 2004 if ((mode & FileMode.OutNew) == FileMode.OutNew) { 2005 access |= O_TRUNC; // resets file 2006 } 2007 } 2008 } 2009 2010 /// Create a file for writing. 2011 void create(string filename) { 2012 create(filename, FileMode.OutNew); 2013 } 2014 2015 /// ditto 2016 void create(string filename, FileMode mode) { 2017 close(); 2018 open(filename, mode | FileMode.OutNew); 2019 } 2020 2021 /// Close the current file if it is open; otherwise it does nothing. 2022 override void close() { 2023 if (isopen) { 2024 super.close(); 2025 if (hFile) { 2026 version (Windows) { 2027 CloseHandle(hFile); 2028 hFile = null; 2029 } else version (Posix) { 2030 core.sys.posix.unistd.close(hFile); 2031 hFile = -1; 2032 } 2033 } 2034 } 2035 } 2036 2037 // destructor, closes file if still opened 2038 ~this() { close(); } 2039 2040 version (Windows) { 2041 // returns size of stream 2042 override @property ulong size() { 2043 assertSeekable(); 2044 uint sizehi; 2045 uint sizelow = GetFileSize(hFile,&sizehi); 2046 return (cast(ulong)sizehi << 32) + sizelow; 2047 } 2048 } 2049 2050 override size_t readBlock(void* buffer, size_t size) { 2051 assertReadable(); 2052 version (Windows) { 2053 auto dwSize = to!DWORD(size); 2054 ReadFile(hFile, buffer, dwSize, &dwSize, null); 2055 size = dwSize; 2056 } else version (Posix) { 2057 size = core.sys.posix.unistd.read(hFile, buffer, size); 2058 if (size == -1) 2059 size = 0; 2060 } 2061 readEOF = (size == 0); 2062 return size; 2063 } 2064 2065 override size_t writeBlock(const void* buffer, size_t size) { 2066 assertWriteable(); 2067 version (Windows) { 2068 auto dwSize = to!DWORD(size); 2069 WriteFile(hFile, buffer, dwSize, &dwSize, null); 2070 size = dwSize; 2071 } else version (Posix) { 2072 size = core.sys.posix.unistd.write(hFile, buffer, size); 2073 if (size == -1) 2074 size = 0; 2075 } 2076 return size; 2077 } 2078 2079 override ulong seek(long offset, SeekPos rel) { 2080 assertSeekable(); 2081 version (Windows) { 2082 int hi = cast(int)(offset>>32); 2083 uint low = SetFilePointer(hFile, cast(int)offset, &hi, rel); 2084 if ((low == INVALID_SET_FILE_POINTER) && (GetLastError() != 0)) 2085 throw new SeekException("unable to move file pointer"); 2086 ulong result = (cast(ulong)hi << 32) + low; 2087 } else version (Posix) { 2088 auto result = lseek(hFile, cast(off_t)offset, rel); 2089 if (result == cast(typeof(result))-1) 2090 throw new SeekException("unable to move file pointer"); 2091 } 2092 readEOF = false; 2093 return cast(ulong)result; 2094 } 2095 2096 /*** 2097 * For a seekable file returns the difference of the size and position and 2098 * otherwise returns 0. 2099 */ 2100 2101 override @property size_t available() { 2102 if (seekable) { 2103 ulong lavail = size - position; 2104 if (lavail > size_t.max) lavail = size_t.max; 2105 return cast(size_t)lavail; 2106 } 2107 return 0; 2108 } 2109 2110 // OS-specific property, just in case somebody wants 2111 // to mess with underlying API 2112 HANDLE handle() { return hFile; } 2113 2114 // run a few tests 2115 unittest { 2116 File file = new File; 2117 int i = 666; 2118 auto stream_file = undead.internal.file.deleteme ~ "-stream.$$$"; 2119 file.create(stream_file); 2120 // should be ok to write 2121 assert(file.writeable); 2122 file.writeLine("Testing stream.d:"); 2123 file.writeString("Hello, world!"); 2124 file.write(i); 2125 // string#1 + string#2 + int should give exacly that 2126 version (Windows) 2127 assert(file.position == 19 + 13 + 4); 2128 version (Posix) 2129 assert(file.position == 18 + 13 + 4); 2130 // we must be at the end of file 2131 assert(file.eof); 2132 file.close(); 2133 // no operations are allowed when file is closed 2134 assert(!file.readable && !file.writeable && !file.seekable); 2135 file.open(stream_file); 2136 // should be ok to read 2137 assert(file.readable); 2138 assert(file.available == file.size); 2139 char[] line = file.readLine(); 2140 char[] exp = "Testing stream.d:".dup; 2141 assert(line[0] == 'T'); 2142 assert(line.length == exp.length); 2143 assert(!std.algorithm.cmp(line, "Testing stream.d:")); 2144 // jump over "Hello, " 2145 file.seek(7, SeekPos.Current); 2146 version (Windows) 2147 assert(file.position == 19 + 7); 2148 version (Posix) 2149 assert(file.position == 18 + 7); 2150 assert(!std.algorithm.cmp(file.readString(6), "world!")); 2151 i = 0; file.read(i); 2152 assert(i == 666); 2153 // string#1 + string#2 + int should give exacly that 2154 version (Windows) 2155 assert(file.position == 19 + 13 + 4); 2156 version (Posix) 2157 assert(file.position == 18 + 13 + 4); 2158 // we must be at the end of file 2159 assert(file.eof); 2160 file.close(); 2161 file.open(stream_file,FileMode.OutNew | FileMode.In); 2162 file.writeLine("Testing stream.d:"); 2163 file.writeLine("Another line"); 2164 file.writeLine(""); 2165 file.writeLine("That was blank"); 2166 file.position = 0; 2167 char[][] lines; 2168 foreach(char[] fileLine; file) { 2169 lines ~= fileLine.dup; 2170 } 2171 assert( lines.length == 4 ); 2172 assert( lines[0] == "Testing stream.d:"); 2173 assert( lines[1] == "Another line"); 2174 assert( lines[2] == ""); 2175 assert( lines[3] == "That was blank"); 2176 file.position = 0; 2177 lines = new char[][4]; 2178 foreach(ulong n, char[] fileLine; file) { 2179 lines[cast(size_t)(n-1)] = fileLine.dup; 2180 } 2181 assert( lines[0] == "Testing stream.d:"); 2182 assert( lines[1] == "Another line"); 2183 assert( lines[2] == ""); 2184 assert( lines[3] == "That was blank"); 2185 file.close(); 2186 std.file.remove(stream_file); 2187 } 2188 } 2189 2190 /*** 2191 * This subclass is for buffered file system streams. 2192 * 2193 * It is a convenience class for wrapping a File in a BufferedStream. 2194 * A buffered stream must be closed explicitly to ensure the final buffer 2195 * content is written to the file. 2196 */ 2197 class BufferedFile: BufferedStream { 2198 2199 /// opens file for reading 2200 this() { super(new File()); } 2201 2202 /// opens file in requested mode and buffer size 2203 this(string filename, FileMode mode = FileMode.In, 2204 size_t bufferSize = DefaultBufferSize) { 2205 super(new File(filename,mode),bufferSize); 2206 } 2207 2208 /// opens file for reading with requested buffer size 2209 this(File file, size_t bufferSize = DefaultBufferSize) { 2210 super(file,bufferSize); 2211 } 2212 2213 /// opens existing handle; use with care! 2214 this(HANDLE hFile, FileMode mode, size_t buffersize = DefaultBufferSize) { 2215 super(new File(hFile,mode),buffersize); 2216 } 2217 2218 /// opens file in requested mode 2219 void open(string filename, FileMode mode = FileMode.In) { 2220 File sf = cast(File)s; 2221 sf.open(filename,mode); 2222 resetSource(); 2223 } 2224 2225 /// creates file in requested mode 2226 void create(string filename, FileMode mode = FileMode.OutNew) { 2227 File sf = cast(File)s; 2228 sf.create(filename,mode); 2229 resetSource(); 2230 } 2231 2232 // run a few tests same as File 2233 unittest { 2234 BufferedFile file = new BufferedFile; 2235 int i = 666; 2236 auto stream_file = undead.internal.file.deleteme ~ "-stream.$$$"; 2237 file.create(stream_file); 2238 // should be ok to write 2239 assert(file.writeable); 2240 file.writeLine("Testing stream.d:"); 2241 file.writeString("Hello, world!"); 2242 file.write(i); 2243 // string#1 + string#2 + int should give exacly that 2244 version (Windows) 2245 assert(file.position == 19 + 13 + 4); 2246 version (Posix) 2247 assert(file.position == 18 + 13 + 4); 2248 // we must be at the end of file 2249 assert(file.eof); 2250 long oldsize = cast(long)file.size; 2251 file.close(); 2252 // no operations are allowed when file is closed 2253 assert(!file.readable && !file.writeable && !file.seekable); 2254 file.open(stream_file); 2255 // should be ok to read 2256 assert(file.readable); 2257 // test getc/ungetc and size 2258 char c1 = file.getc(); 2259 file.ungetc(c1); 2260 assert( file.size == oldsize ); 2261 assert(!std.algorithm.cmp(file.readLine(), "Testing stream.d:")); 2262 // jump over "Hello, " 2263 file.seek(7, SeekPos.Current); 2264 version (Windows) 2265 assert(file.position == 19 + 7); 2266 version (Posix) 2267 assert(file.position == 18 + 7); 2268 assert(!std.algorithm.cmp(file.readString(6), "world!")); 2269 i = 0; file.read(i); 2270 assert(i == 666); 2271 // string#1 + string#2 + int should give exacly that 2272 version (Windows) 2273 assert(file.position == 19 + 13 + 4); 2274 version (Posix) 2275 assert(file.position == 18 + 13 + 4); 2276 // we must be at the end of file 2277 assert(file.eof); 2278 file.close(); 2279 std.file.remove(stream_file); 2280 } 2281 2282 } 2283 2284 /// UTF byte-order-mark signatures 2285 enum BOM { 2286 UTF8, /// UTF-8 2287 UTF16LE, /// UTF-16 Little Endian 2288 UTF16BE, /// UTF-16 Big Endian 2289 UTF32LE, /// UTF-32 Little Endian 2290 UTF32BE, /// UTF-32 Big Endian 2291 } 2292 2293 private enum int NBOMS = 5; 2294 immutable Endian[NBOMS] BOMEndian = 2295 [ std.system.endian, 2296 Endian.littleEndian, Endian.bigEndian, 2297 Endian.littleEndian, Endian.bigEndian 2298 ]; 2299 2300 immutable ubyte[][NBOMS] ByteOrderMarks = 2301 [ [0xEF, 0xBB, 0xBF], 2302 [0xFF, 0xFE], 2303 [0xFE, 0xFF], 2304 [0xFF, 0xFE, 0x00, 0x00], 2305 [0x00, 0x00, 0xFE, 0xFF] 2306 ]; 2307 2308 2309 /*** 2310 * This subclass wraps a stream with big-endian or little-endian byte order 2311 * swapping. 2312 * 2313 * UTF Byte-Order-Mark (BOM) signatures can be read and deduced or 2314 * written. 2315 * Note that an EndianStream should not be used as the source of another 2316 * FilterStream since a FilterStream call the source with byte-oriented 2317 * read/write requests and the EndianStream will not perform any byte swapping. 2318 * The EndianStream reads and writes binary data (non-getc functions) in a 2319 * one-to-one 2320 * manner with the source stream so the source stream's position and state will be 2321 * kept in sync with the EndianStream if only non-getc functions are called. 2322 */ 2323 class EndianStream : FilterStream { 2324 2325 Endian endian; /// Endianness property of the source stream. 2326 2327 /*** 2328 * Create the endian stream for the source stream source with endianness end. 2329 * The default endianness is the native byte order. 2330 * The Endian type is defined 2331 * in the std.system module. 2332 */ 2333 this(Stream source, Endian end = std.system.endian) { 2334 super(source); 2335 endian = end; 2336 } 2337 2338 /*** 2339 * Return -1 if no BOM and otherwise read the BOM and return it. 2340 * 2341 * If there is no BOM or if bytes beyond the BOM are read then the bytes read 2342 * are pushed back onto the ungetc buffer or ungetcw buffer. 2343 * Pass ungetCharSize == 2 to use 2344 * ungetcw instead of ungetc when no BOM is present. 2345 */ 2346 int readBOM(int ungetCharSize = 1) { 2347 ubyte[4] BOM_buffer; 2348 int n = 0; // the number of read bytes 2349 int result = -1; // the last match or -1 2350 for (int i=0; i < NBOMS; ++i) { 2351 int j; 2352 immutable ubyte[] bom = ByteOrderMarks[i]; 2353 for (j=0; j < bom.length; ++j) { 2354 if (n <= j) { // have to read more 2355 if (eof) 2356 break; 2357 readExact(&BOM_buffer[n++],1); 2358 } 2359 if (BOM_buffer[j] != bom[j]) 2360 break; 2361 } 2362 if (j == bom.length) // found a match 2363 result = i; 2364 } 2365 ptrdiff_t m = 0; 2366 if (result != -1) { 2367 endian = BOMEndian[result]; // set stream endianness 2368 m = ByteOrderMarks[result].length; 2369 } 2370 if ((ungetCharSize == 1 && result == -1) || (result == BOM.UTF8)) { 2371 while (n-- > m) 2372 ungetc(BOM_buffer[n]); 2373 } else { // should eventually support unget for dchar as well 2374 if (n & 1) // make sure we have an even number of bytes 2375 readExact(&BOM_buffer[n++],1); 2376 while (n > m) { 2377 n -= 2; 2378 wchar cw = *(cast(wchar*)&BOM_buffer[n]); 2379 fixBO(&cw,2); 2380 ungetcw(cw); 2381 } 2382 } 2383 return result; 2384 } 2385 2386 /*** 2387 * Correct the byte order of buffer to match native endianness. 2388 * size must be even. 2389 */ 2390 final void fixBO(const(void)* buffer, size_t size) { 2391 if (endian != std.system.endian) { 2392 ubyte* startb = cast(ubyte*)buffer; 2393 uint* start = cast(uint*)buffer; 2394 switch (size) { 2395 case 0: break; 2396 case 2: { 2397 ubyte x = *startb; 2398 *startb = *(startb+1); 2399 *(startb+1) = x; 2400 break; 2401 } 2402 case 4: { 2403 *start = bswap(*start); 2404 break; 2405 } 2406 default: { 2407 uint* end = cast(uint*)(buffer + size - uint.sizeof); 2408 while (start < end) { 2409 uint x = bswap(*start); 2410 *start = bswap(*end); 2411 *end = x; 2412 ++start; 2413 --end; 2414 } 2415 startb = cast(ubyte*)start; 2416 ubyte* endb = cast(ubyte*)end; 2417 auto len = uint.sizeof - (startb - endb); 2418 if (len > 0) 2419 fixBO(startb,len); 2420 } 2421 } 2422 } 2423 } 2424 2425 /*** 2426 * Correct the byte order of the given buffer in blocks of the given size and 2427 * repeated the given number of times. 2428 * size must be even. 2429 */ 2430 final void fixBlockBO(void* buffer, uint size, size_t repeat) { 2431 while (repeat--) { 2432 fixBO(buffer,size); 2433 buffer += size; 2434 } 2435 } 2436 2437 override void read(out byte x) { readExact(&x, x.sizeof); } 2438 override void read(out ubyte x) { readExact(&x, x.sizeof); } 2439 override void read(out short x) { readExact(&x, x.sizeof); fixBO(&x,x.sizeof); } 2440 override void read(out ushort x) { readExact(&x, x.sizeof); fixBO(&x,x.sizeof); } 2441 override void read(out int x) { readExact(&x, x.sizeof); fixBO(&x,x.sizeof); } 2442 override void read(out uint x) { readExact(&x, x.sizeof); fixBO(&x,x.sizeof); } 2443 override void read(out long x) { readExact(&x, x.sizeof); fixBO(&x,x.sizeof); } 2444 override void read(out ulong x) { readExact(&x, x.sizeof); fixBO(&x,x.sizeof); } 2445 override void read(out float x) { readExact(&x, x.sizeof); fixBO(&x,x.sizeof); } 2446 override void read(out double x) { readExact(&x, x.sizeof); fixBO(&x,x.sizeof); } 2447 override void read(out real x) { readExact(&x, x.sizeof); fixBO(&x,x.sizeof); } 2448 override void read(out char x) { readExact(&x, x.sizeof); } 2449 override void read(out wchar x) { readExact(&x, x.sizeof); fixBO(&x,x.sizeof); } 2450 override void read(out dchar x) { readExact(&x, x.sizeof); fixBO(&x,x.sizeof); } 2451 2452 override wchar getcw() { 2453 wchar c; 2454 if (prevCr) { 2455 prevCr = false; 2456 c = getcw(); 2457 if (c != '\n') 2458 return c; 2459 } 2460 if (unget.length > 1) { 2461 c = unget[unget.length - 1]; 2462 unget.length = unget.length - 1; 2463 } else { 2464 void* buf = &c; 2465 size_t n = readBlock(buf,2); 2466 if (n == 1 && readBlock(buf+1,1) == 0) 2467 throw new ReadException("not enough data in stream"); 2468 fixBO(&c,c.sizeof); 2469 } 2470 return c; 2471 } 2472 2473 override wchar[] readStringW(size_t length) { 2474 wchar[] result = new wchar[length]; 2475 readExact(result.ptr, length * wchar.sizeof); 2476 fixBlockBO(result.ptr, wchar.sizeof, length); 2477 return result; 2478 } 2479 2480 /// Write the specified BOM b to the source stream. 2481 void writeBOM(BOM b) { 2482 immutable ubyte[] bom = ByteOrderMarks[b]; 2483 writeBlock(bom.ptr, bom.length); 2484 } 2485 2486 override void write(byte x) { writeExact(&x, x.sizeof); } 2487 override void write(ubyte x) { writeExact(&x, x.sizeof); } 2488 override void write(short x) { fixBO(&x,x.sizeof); writeExact(&x, x.sizeof); } 2489 override void write(ushort x) { fixBO(&x,x.sizeof); writeExact(&x, x.sizeof); } 2490 override void write(int x) { fixBO(&x,x.sizeof); writeExact(&x, x.sizeof); } 2491 override void write(uint x) { fixBO(&x,x.sizeof); writeExact(&x, x.sizeof); } 2492 override void write(long x) { fixBO(&x,x.sizeof); writeExact(&x, x.sizeof); } 2493 override void write(ulong x) { fixBO(&x,x.sizeof); writeExact(&x, x.sizeof); } 2494 override void write(float x) { fixBO(&x,x.sizeof); writeExact(&x, x.sizeof); } 2495 override void write(double x) { fixBO(&x,x.sizeof); writeExact(&x, x.sizeof); } 2496 override void write(real x) { fixBO(&x,x.sizeof); writeExact(&x, x.sizeof); } 2497 override void write(char x) { writeExact(&x, x.sizeof); } 2498 override void write(wchar x) { fixBO(&x,x.sizeof); writeExact(&x, x.sizeof); } 2499 override void write(dchar x) { fixBO(&x,x.sizeof); writeExact(&x, x.sizeof); } 2500 2501 override void writeStringW(const(wchar)[] str) { 2502 foreach(wchar cw;str) { 2503 fixBO(&cw,2); 2504 s.writeExact(&cw, 2); 2505 } 2506 } 2507 2508 override @property bool eof() { return s.eof && !ungetAvailable(); } 2509 override @property ulong size() { return s.size; } 2510 2511 unittest { 2512 MemoryStream m; 2513 m = new MemoryStream (); 2514 EndianStream em = new EndianStream(m,Endian.bigEndian); 2515 uint x = 0x11223344; 2516 em.write(x); 2517 assert( m.data[0] == 0x11 ); 2518 assert( m.data[1] == 0x22 ); 2519 assert( m.data[2] == 0x33 ); 2520 assert( m.data[3] == 0x44 ); 2521 em.position = 0; 2522 ushort x2 = 0x5566; 2523 em.write(x2); 2524 assert( m.data[0] == 0x55 ); 2525 assert( m.data[1] == 0x66 ); 2526 em.position = 0; 2527 static ubyte[12] x3 = [1,2,3,4,5,6,7,8,9,10,11,12]; 2528 em.fixBO(x3.ptr,12); 2529 if (std.system.endian == Endian.littleEndian) { 2530 assert( x3[0] == 12 ); 2531 assert( x3[1] == 11 ); 2532 assert( x3[2] == 10 ); 2533 assert( x3[4] == 8 ); 2534 assert( x3[5] == 7 ); 2535 assert( x3[6] == 6 ); 2536 assert( x3[8] == 4 ); 2537 assert( x3[9] == 3 ); 2538 assert( x3[10] == 2 ); 2539 assert( x3[11] == 1 ); 2540 } 2541 em.endian = Endian.littleEndian; 2542 em.write(x); 2543 assert( m.data[0] == 0x44 ); 2544 assert( m.data[1] == 0x33 ); 2545 assert( m.data[2] == 0x22 ); 2546 assert( m.data[3] == 0x11 ); 2547 em.position = 0; 2548 em.write(x2); 2549 assert( m.data[0] == 0x66 ); 2550 assert( m.data[1] == 0x55 ); 2551 em.position = 0; 2552 em.fixBO(x3.ptr,12); 2553 if (std.system.endian == Endian.bigEndian) { 2554 assert( x3[0] == 12 ); 2555 assert( x3[1] == 11 ); 2556 assert( x3[2] == 10 ); 2557 assert( x3[4] == 8 ); 2558 assert( x3[5] == 7 ); 2559 assert( x3[6] == 6 ); 2560 assert( x3[8] == 4 ); 2561 assert( x3[9] == 3 ); 2562 assert( x3[10] == 2 ); 2563 assert( x3[11] == 1 ); 2564 } 2565 em.writeBOM(BOM.UTF8); 2566 assert( m.position == 3 ); 2567 assert( m.data[0] == 0xEF ); 2568 assert( m.data[1] == 0xBB ); 2569 assert( m.data[2] == 0xBF ); 2570 em.writeString ("Hello, world"); 2571 em.position = 0; 2572 assert( m.position == 0 ); 2573 assert( em.readBOM() == BOM.UTF8 ); 2574 assert( m.position == 3 ); 2575 assert( em.getc() == 'H' ); 2576 em.position = 0; 2577 em.writeBOM(BOM.UTF16BE); 2578 assert( m.data[0] == 0xFE ); 2579 assert( m.data[1] == 0xFF ); 2580 em.position = 0; 2581 em.writeBOM(BOM.UTF16LE); 2582 assert( m.data[0] == 0xFF ); 2583 assert( m.data[1] == 0xFE ); 2584 em.position = 0; 2585 em.writeString ("Hello, world"); 2586 em.position = 0; 2587 assert( em.readBOM() == -1 ); 2588 assert( em.getc() == 'H' ); 2589 assert( em.getc() == 'e' ); 2590 assert( em.getc() == 'l' ); 2591 assert( em.getc() == 'l' ); 2592 em.position = 0; 2593 } 2594 } 2595 2596 /*** 2597 * Parameterized subclass that wraps an array-like buffer with a stream 2598 * interface. 2599 * 2600 * The type Buffer must support the length property, opIndex and opSlice. 2601 * Compile in release mode when directly instantiating a TArrayStream to avoid 2602 * link errors. 2603 */ 2604 class TArrayStream(Buffer): Stream { 2605 Buffer buf; // current data 2606 ulong len; // current data length 2607 ulong cur; // current file position 2608 2609 /// Create the stream for the the buffer buf. Non-copying. 2610 this(Buffer buf) { 2611 super (); 2612 this.buf = buf; 2613 this.len = buf.length; 2614 readable = writeable = seekable = true; 2615 } 2616 2617 // ensure subclasses don't violate this 2618 invariant() { 2619 assert(len <= buf.length); 2620 assert(cur <= len); 2621 } 2622 2623 override size_t readBlock(void* buffer, size_t size) { 2624 assertReadable(); 2625 ubyte* cbuf = cast(ubyte*) buffer; 2626 if (len - cur < size) 2627 size = cast(size_t)(len - cur); 2628 ubyte[] ubuf = cast(ubyte[])buf[cast(size_t)cur .. cast(size_t)(cur + size)]; 2629 cbuf[0 .. size] = ubuf[]; 2630 cur += size; 2631 return size; 2632 } 2633 2634 override size_t writeBlock(const void* buffer, size_t size) { 2635 assertWriteable(); 2636 ubyte* cbuf = cast(ubyte*) buffer; 2637 ulong blen = buf.length; 2638 if (cur + size > blen) 2639 size = cast(size_t)(blen - cur); 2640 ubyte[] ubuf = cast(ubyte[])buf[cast(size_t)cur .. cast(size_t)(cur + size)]; 2641 ubuf[] = cbuf[0 .. size]; 2642 cur += size; 2643 if (cur > len) 2644 len = cur; 2645 return size; 2646 } 2647 2648 override ulong seek(long offset, SeekPos rel) { 2649 assertSeekable(); 2650 long scur; // signed to saturate to 0 properly 2651 2652 switch (rel) { 2653 case SeekPos.Set: scur = offset; break; 2654 case SeekPos.Current: scur = cast(long)(cur + offset); break; 2655 case SeekPos.End: scur = cast(long)(len + offset); break; 2656 default: 2657 assert(0); 2658 } 2659 2660 if (scur < 0) 2661 cur = 0; 2662 else if (scur > len) 2663 cur = len; 2664 else 2665 cur = cast(ulong)scur; 2666 2667 return cur; 2668 } 2669 2670 override @property size_t available () { return cast(size_t)(len - cur); } 2671 2672 /// Get the current memory data in total. 2673 @property ubyte[] data() { 2674 if (len > size_t.max) 2675 throw new StreamException("Stream too big"); 2676 const(void)[] res = buf[0 .. cast(size_t)len]; 2677 return cast(ubyte[])res; 2678 } 2679 2680 override string toString() { 2681 // assume data is UTF8 2682 return to!(string)(cast(char[])data); 2683 } 2684 } 2685 2686 /* Test the TArrayStream */ 2687 unittest { 2688 char[100] buf; 2689 TArrayStream!(char[]) m; 2690 2691 m = new TArrayStream!(char[]) (buf); 2692 assert (m.isOpen); 2693 m.writeString ("Hello, world"); 2694 assert (m.position == 12); 2695 assert (m.available == 88); 2696 assert (m.seekSet (0) == 0); 2697 assert (m.available == 100); 2698 assert (m.seekCur (4) == 4); 2699 assert (m.available == 96); 2700 assert (m.seekEnd (-8) == 92); 2701 assert (m.available == 8); 2702 assert (m.size == 100); 2703 assert (m.seekSet (4) == 4); 2704 assert (m.readString (4) == "o, w"); 2705 m.writeString ("ie"); 2706 assert (buf[0..12] == "Hello, wield"); 2707 assert (m.position == 10); 2708 assert (m.available == 90); 2709 assert (m.size == 100); 2710 m.seekSet (0); 2711 assert (m.printf ("Answer is %d", 42) == 12); 2712 assert (buf[0..12] == "Answer is 42"); 2713 } 2714 2715 /// This subclass reads and constructs an array of bytes in memory. 2716 class MemoryStream: TArrayStream!(ubyte[]) { 2717 2718 /// Create the output buffer and setup for reading, writing, and seeking. 2719 // clear to an empty buffer. 2720 this() { this(cast(ubyte[]) null); } 2721 2722 /*** 2723 * Create the output buffer and setup for reading, writing, and seeking. 2724 * Load it with specific input data. 2725 */ 2726 this(ubyte[] buf) { super (buf); } 2727 this(byte[] buf) { this(cast(ubyte[]) buf); } /// ditto 2728 this(char[] buf) { this(cast(ubyte[]) buf); } /// ditto 2729 2730 /// Ensure the stream can write count extra bytes from cursor position without an allocation. 2731 void reserve(size_t count) { 2732 if (cur + count > buf.length) 2733 buf.length = cast(uint)((cur + count) * 2); 2734 } 2735 2736 override size_t writeBlock(const void* buffer, size_t size) { 2737 reserve(size); 2738 return super.writeBlock(buffer,size); 2739 } 2740 2741 unittest { 2742 MemoryStream m; 2743 2744 m = new MemoryStream (); 2745 assert (m.isOpen); 2746 m.writeString ("Hello, world"); 2747 assert (m.position == 12); 2748 assert (m.seekSet (0) == 0); 2749 assert (m.available == 12); 2750 assert (m.seekCur (4) == 4); 2751 assert (m.available == 8); 2752 assert (m.seekEnd (-8) == 4); 2753 assert (m.available == 8); 2754 assert (m.size == 12); 2755 assert (m.readString (4) == "o, w"); 2756 m.writeString ("ie"); 2757 assert (cast(char[]) m.data == "Hello, wield"); 2758 m.seekEnd (0); 2759 m.writeString ("Foo"); 2760 assert (m.position == 15); 2761 assert (m.available == 0); 2762 m.writeString ("Foo foo foo foo foo foo foo"); 2763 assert (m.position == 42); 2764 m.position = 0; 2765 assert (m.available == 42); 2766 m.writef("%d %d %s",100,345,"hello"); 2767 auto str = m.toString(); 2768 assert (str[0..13] == "100 345 hello", str[0 .. 13]); 2769 assert (m.available == 29); 2770 assert (m.position == 13); 2771 2772 MemoryStream m2; 2773 m.position = 3; 2774 m2 = new MemoryStream (); 2775 m2.writeString("before"); 2776 m2.copyFrom(m,10); 2777 str = m2.toString(); 2778 assert (str[0..16] == "before 345 hello"); 2779 m2.position = 3; 2780 m2.copyFrom(m); 2781 auto str2 = m.toString(); 2782 str = m2.toString(); 2783 assert (str == ("bef" ~ str2)); 2784 } 2785 } 2786 2787 import std.mmfile; 2788 2789 /*** 2790 * This subclass wraps a memory-mapped file with the stream API. 2791 * See std.mmfile module. 2792 */ 2793 class MmFileStream : TArrayStream!(MmFile) { 2794 2795 /// Create stream wrapper for file. 2796 this(MmFile file) { 2797 super (file); 2798 MmFile.Mode mode = file.mode(); 2799 writeable = mode > MmFile.Mode.read; 2800 } 2801 2802 override void flush() { 2803 if (isopen) { 2804 super.flush(); 2805 buf.flush(); 2806 } 2807 } 2808 2809 override void close() { 2810 if (isopen) { 2811 super.close(); 2812 buf.destroy(); 2813 buf = null; 2814 } 2815 } 2816 } 2817 2818 unittest { 2819 auto test_file = undead.internal.file.deleteme ~ "-testing.txt"; 2820 MmFile mf = new MmFile(test_file,MmFile.Mode.readWriteNew,100,null); 2821 MmFileStream m; 2822 m = new MmFileStream (mf); 2823 m.writeString ("Hello, world"); 2824 assert (m.position == 12); 2825 assert (m.seekSet (0) == 0); 2826 assert (m.seekCur (4) == 4); 2827 assert (m.seekEnd (-8) == 92); 2828 assert (m.size == 100); 2829 assert (m.seekSet (4)); 2830 assert (m.readString (4) == "o, w"); 2831 m.writeString ("ie"); 2832 ubyte[] dd = m.data; 2833 assert ((cast(char[]) dd)[0 .. 12] == "Hello, wield"); 2834 m.position = 12; 2835 m.writeString ("Foo"); 2836 assert (m.position == 15); 2837 m.writeString ("Foo foo foo foo foo foo foo"); 2838 assert (m.position == 42); 2839 m.close(); 2840 mf = new MmFile(test_file); 2841 m = new MmFileStream (mf); 2842 assert (!m.writeable); 2843 char[] str = m.readString(12); 2844 assert (str == "Hello, wield"); 2845 m.close(); 2846 std.file.remove(test_file); 2847 } 2848 2849 2850 /*** 2851 * This subclass slices off a portion of another stream, making seeking relative 2852 * to the boundaries of the slice. 2853 * 2854 * It could be used to section a large file into a 2855 * set of smaller files, such as with tar archives. Reading and writing a 2856 * SliceStream does not modify the position of the source stream if it is 2857 * seekable. 2858 */ 2859 class SliceStream : FilterStream { 2860 private { 2861 ulong pos; // our position relative to low 2862 ulong low; // low stream offset. 2863 ulong high; // high stream offset. 2864 bool bounded; // upper-bounded by high. 2865 } 2866 2867 /*** 2868 * Indicate both the source stream to use for reading from and the low part of 2869 * the slice. 2870 * 2871 * The high part of the slice is dependent upon the end of the source 2872 * stream, so that if you write beyond the end it resizes the stream normally. 2873 */ 2874 this (Stream s, ulong low) 2875 in { 2876 assert (low <= s.size); 2877 } 2878 do { 2879 super(s); 2880 this.low = low; 2881 this.high = 0; 2882 this.bounded = false; 2883 } 2884 2885 /*** 2886 * Indicate the high index as well. 2887 * 2888 * Attempting to read or write past the high 2889 * index results in the end being clipped off. 2890 */ 2891 this (Stream s, ulong low, ulong high) 2892 in { 2893 assert (low <= high); 2894 assert (high <= s.size); 2895 } 2896 do { 2897 super(s); 2898 this.low = low; 2899 this.high = high; 2900 this.bounded = true; 2901 } 2902 2903 invariant() { 2904 if (bounded) 2905 assert (pos <= high - low); 2906 else 2907 // size() does not appear to be const, though it should be 2908 assert (pos <= (cast()s).size - low); 2909 } 2910 2911 override size_t readBlock (void *buffer, size_t size) { 2912 assertReadable(); 2913 if (bounded && size > high - low - pos) 2914 size = cast(size_t)(high - low - pos); 2915 ulong bp = s.position; 2916 if (seekable) 2917 s.position = low + pos; 2918 size_t ret = super.readBlock(buffer, size); 2919 if (seekable) { 2920 pos = s.position - low; 2921 s.position = bp; 2922 } 2923 return ret; 2924 } 2925 2926 override size_t writeBlock (const void *buffer, size_t size) { 2927 assertWriteable(); 2928 if (bounded && size > high - low - pos) 2929 size = cast(size_t)(high - low - pos); 2930 ulong bp = s.position; 2931 if (seekable) 2932 s.position = low + pos; 2933 size_t ret = s.writeBlock(buffer, size); 2934 if (seekable) { 2935 pos = s.position - low; 2936 s.position = bp; 2937 } 2938 return ret; 2939 } 2940 2941 override ulong seek(long offset, SeekPos rel) { 2942 assertSeekable(); 2943 long spos; 2944 2945 switch (rel) { 2946 case SeekPos.Set: 2947 spos = offset; 2948 break; 2949 case SeekPos.Current: 2950 spos = cast(long)(pos + offset); 2951 break; 2952 case SeekPos.End: 2953 if (bounded) 2954 spos = cast(long)(high - low + offset); 2955 else 2956 spos = cast(long)(s.size - low + offset); 2957 break; 2958 default: 2959 assert(0); 2960 } 2961 2962 if (spos < 0) 2963 pos = 0; 2964 else if (bounded && spos > high - low) 2965 pos = high - low; 2966 else if (!bounded && spos > s.size - low) 2967 pos = s.size - low; 2968 else 2969 pos = cast(ulong)spos; 2970 2971 readEOF = false; 2972 return pos; 2973 } 2974 2975 override @property size_t available() { 2976 size_t res = s.available; 2977 ulong bp = s.position; 2978 if (bp <= pos+low && pos+low <= bp+res) { 2979 if (!bounded || bp+res <= high) 2980 return cast(size_t)(bp + res - pos - low); 2981 else if (high <= bp+res) 2982 return cast(size_t)(high - pos - low); 2983 } 2984 return 0; 2985 } 2986 2987 unittest { 2988 MemoryStream m; 2989 SliceStream s; 2990 2991 m = new MemoryStream ((cast(char[])"Hello, world").dup); 2992 s = new SliceStream (m, 4, 8); 2993 assert (s.size == 4); 2994 assert (m.position == 0); 2995 assert (s.position == 0); 2996 assert (m.available == 12); 2997 assert (s.available == 4); 2998 2999 assert (s.writeBlock (cast(char *) "Vroom", 5) == 4); 3000 assert (m.position == 0); 3001 assert (s.position == 4); 3002 assert (m.available == 12); 3003 assert (s.available == 0); 3004 assert (s.seekEnd (-2) == 2); 3005 assert (s.available == 2); 3006 assert (s.seekEnd (2) == 4); 3007 assert (s.available == 0); 3008 assert (m.position == 0); 3009 assert (m.available == 12); 3010 3011 m.seekEnd(0); 3012 m.writeString("\nBlaho"); 3013 assert (m.position == 18); 3014 assert (m.available == 0); 3015 assert (s.position == 4); 3016 assert (s.available == 0); 3017 3018 s = new SliceStream (m, 4); 3019 assert (s.size == 14); 3020 assert (s.toString () == "Vrooorld\nBlaho"); 3021 s.seekEnd (0); 3022 assert (s.available == 0); 3023 3024 s.writeString (", etcetera."); 3025 assert (s.position == 25); 3026 assert (s.seekSet (0) == 0); 3027 assert (s.size == 25); 3028 assert (m.position == 18); 3029 assert (m.size == 29); 3030 assert (m.toString() == "HellVrooorld\nBlaho, etcetera."); 3031 } 3032 }