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