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