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