egra: even more comments
[iv.d.git] / _obsolete_dont_use / stream.d
blob08b3c95efe20139eec7d80d03aad32bf0ca7816e
1 /* Invisible Vector Library
2 * coded by Ketmar // Invisible Vector <ketmar@ketmar.no-ip.org>
3 * Understanding is not required. Only obedience.
5 * This program is free software: you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation, version 3 of the License ONLY.
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
14 * You should have received a copy of the GNU General Public License
15 * along with this program. If not, see <http://www.gnu.org/licenses/>.
17 // severely outdated i/o stream interface
18 // use iv.vfs instead
19 module iv.stream /*is aliced*/;
21 import iv.alice;
22 import std.conv : ConvOverflowException;
23 import std.traits : isMutable;
24 public import core.stdc.stdio : SEEK_SET, SEEK_CUR, SEEK_END;
27 // ////////////////////////////////////////////////////////////////////////// //
28 import iv.exex : MyException, ExceptionCtor;
30 mixin(MyException!"StreamException");
33 // ////////////////////////////////////////////////////////////////////////// //
34 enum isReadableStream(T) = is(typeof((inout int=0) {
35 auto t = T.init;
36 ubyte[1] b;
37 auto v = cast(void[])b;
38 t.rawRead(v);
39 }));
41 enum isWriteableStream(T) = is(typeof((inout int=0) {
42 auto t = T.init;
43 ubyte[1] b;
44 t.rawWrite(cast(void[])b);
45 }));
47 enum isCloseableStream(T) = is(typeof((inout int=0) {
48 auto t = T.init;
49 t.close();
50 }));
52 enum isRWStream(T) = isReadableStream!T && isWriteableStream!T;
54 template isSeekableStream(T) {
55 enum isSeekableStream = is(typeof((inout int=0) {
56 import core.stdc.stdio : SEEK_END;
57 auto t = T.init;
58 t.seek(0, SEEK_END);
59 ulong pos = t.tell;
60 }));
63 // bad name!
64 enum isClosableStream(T) = is(typeof((inout int=0) {
65 auto t = T.init;
66 t.close();
67 }));
69 // bad name!
70 enum streamHasEOF(T) = is(typeof((inout int=0) {
71 auto t = T.init;
72 bool n = t.eof;
73 }));
75 enum streamHasSeek(T) = is(typeof((inout int=0) {
76 import core.stdc.stdio : SEEK_END;
77 auto t = T.init;
78 t.seek(0, SEEK_END);
79 }));
81 enum streamHasTell(T) = is(typeof((inout int=0) {
82 auto t = T.init;
83 ulong pos = t.tell;
84 }));
86 enum streamHasName(T) = is(typeof((inout int=0) {
87 auto t = T.init;
88 string n = t.name;
89 }));
91 enum streamHasSize(T) = is(typeof((inout int=0) {
92 auto t = T.init;
93 ulong pos = t.size;
94 }));
96 enum streamHasIsOpen(T) = is(typeof((inout int=0) {
97 auto t = T.init;
98 bool op = t.isOpen;
99 }));
101 enum streamHasDetach(T) = is(typeof((inout int=0) {
102 auto t = T.init;
103 op = t.detach;
104 }));
107 version(unittest_stream)
108 unittest {
109 import std.stdio;
110 static assert(isReadableStream!File);
111 static assert(isWriteableStream!File);
112 static assert(isRWStream!File);
113 static assert(isSeekableStream!File);
114 static assert(streamHasEOF!File);
115 static assert(streamHasSeek!File);
116 static assert(streamHasTell!File);
117 static assert(streamHasName!File);
118 static assert(streamHasSize!File);
119 struct S {}
120 static assert(!isReadableStream!S);
121 static assert(!isWriteableStream!S);
122 static assert(!isRWStream!S);
123 static assert(!isSeekableStream!S);
124 static assert(!streamHasEOF!S);
125 static assert(!streamHasSeek!S);
126 static assert(!streamHasTell!S);
127 static assert(!streamHasName!S);
128 static assert(!streamHasSize!S);
132 // ////////////////////////////////////////////////////////////////////////// //
133 T[] rawReadExact(TF, T)(auto ref TF fl, T[] buf)
134 if (isReadableStream!TF && isMutable!T)
136 import std.exception : enforce;
137 auto res = fl.rawRead(cast(void[])buf);
138 enforce(res.length == T.sizeof*buf.length, "reading error");
139 return buf;
143 // just for convience
144 void rawWriteExact(TF, T) (auto ref TF fl, in T[] buf)
145 if (isWriteableStream!TF)
147 fl.rawWrite(cast(void[])buf);
151 // ////////////////////////////////////////////////////////////////////////// //
152 private enum goodEndianness(string s) = (s == "LE" || s == "le" || s == "BE" || s == "be");
155 private template isLittleEndianness(string s) if (goodEndianness!s) {
156 enum isLittleEndianness = (s == "LE" || s == "le");
159 private template isSystemEndianness(string s) if (goodEndianness!s) {
160 version(LittleEndian) {
161 enum isSystemEndianness = (s == "LE" || s == "le");
162 } else {
163 enum isSystemEndianness = (s == "BE" || s == "be");
168 // ////////////////////////////////////////////////////////////////////////// //
169 // usage: write!ubyte(fl, 10)
170 void writeInt(TD, string es="LE", T, TF) (auto ref TF fl, T n) @trusted
171 if (goodEndianness!es && isWriteableStream!TF && __traits(isIntegral, TD) && __traits(isIntegral, T))
173 static assert(T.sizeof <= 8);
174 static assert(TD.sizeof <= 8);
175 static if (__traits(isUnsigned, TD)) {
176 // TD is unsigned
177 static if (!__traits(isUnsigned, T)) {
178 if (n < 0) throw new ConvOverflowException("writeInt overflow");
180 } else {
181 // TD is signed
182 static if (!__traits(isUnsigned, T)) {
183 if (n < TD.min) throw new ConvOverflowException("writeInt overflow");
186 if (n > TD.max) throw new ConvOverflowException("writeInt overflow");
187 auto v = cast(TD)n;
188 static if (isSystemEndianness!es) {
189 fl.rawWriteExact((&v)[0..1]);
190 } else {
191 ubyte[TD.sizeof] b = void;
192 version(LittleEndian) {
193 // convert to big-endian
194 foreach_reverse (immutable idx; 0..TD.sizeof) {
195 b[idx] = v&0xff;
196 v >>= 8;
198 } else {
199 // convert to little-endian
200 foreach (immutable idx; 0..TD.sizeof) {
201 b[idx] = v&0xff;
202 v >>= 8;
205 fl.rawWriteExact(b);
210 // usage: read!ubyte(fl)
211 T readInt(T, string es="LE", TF) (auto ref TF fl) @trusted
212 if (goodEndianness!es && isReadableStream!TF && __traits(isIntegral, T))
214 static assert(T.sizeof <= 8);
215 static if (isSystemEndianness!es) {
216 T v = void;
217 fl.rawReadExact((&v)[0..1]);
218 return v;
219 } else {
220 ubyte[T.sizeof] b = void;
221 fl.rawReadExact(b);
222 ulong v = 0;
223 version(LittleEndian) {
224 // convert from big-endian
225 foreach (immutable idx; 0..T.sizeof) {
226 v <<= 8;
227 v |= b[idx];
229 } else {
230 // conver from little-endian
231 foreach_reverse (immutable idx; 0..T.sizeof) {
232 v <<= 8;
233 v |= b[idx];
236 return cast(T)v;
241 private enum reverseBytesMixin = "
242 foreach (idx; 0..b.length/2) {
243 ubyte t = b[idx];
244 b[idx] = b[b.length-idx-1];
245 b[b.length-idx-1] = t;
250 void writeFloat(TD, string es="LE", T, TF) (auto ref TF fl, T n) @trusted
251 if (goodEndianness!es && isWriteableStream!TF && __traits(isFloating, TD) && (__traits(isFloating, T) || __traits(isIntegral, T)))
253 static assert(TD.sizeof <= 8);
254 static if (__traits(isIntegral, T)) {
255 static assert(T.sizeof <= 8);
256 writeFloat!(TD, TD)(fl, cast(TD)n);
257 } else static if (__traits(isFloating, T)) {
258 static assert(T.sizeof <= 8);
259 auto v = cast(TD)n;
260 static if (isSystemEndianness!es) {
261 fl.rawWriteExact((&v)[0..1]);
262 } else {
263 import core.stdc.string : memcpy;
264 ubyte[TD.sizeof] b = void;
265 memcpy(b.ptr, &v, TD.sizeof);
266 mixin(reverseBytesMixin);
267 fl.rawWriteExact(b);
269 } else {
270 static assert(0);
275 TD readFloat(TD, string es="LE", TF) (auto ref TF fl) @trusted
276 if (goodEndianness!es && isReadableStream!TF && __traits(isFloating, TD))
278 static assert(TD.sizeof <= 8);
279 TD v = void;
280 static if (isSystemEndianness!es) {
281 fl.rawReadExact((&v)[0..1]);
282 } else {
283 import core.stdc.string : memcpy;
284 ubyte[TD.sizeof] b = void;
285 fl.rawReadExact(b);
286 mixin(reverseBytesMixin);
287 memcpy(&v, b.ptr, TD.sizeof);
289 return v;
293 void writeNum(TD, string es="LE", T, TF) (auto ref TF fl, T n) @trusted if (__traits(isIntegral, TD)) { writeInt!(TD, es, T, TF)(fl, n); }
294 void writeNum(TD, string es="LE", T, TF) (auto ref TF fl, T n) @trusted if (__traits(isFloating, TD)) { writeFloat!(TD, es, T, TF)(fl, n); }
296 TD readNum(TD, string es="LE", TF) (auto ref TF fl) @trusted if (__traits(isIntegral, TD)) { return readInt!(TD, es, TF)(fl); }
297 TD readNum(TD, string es="LE", TF) (auto ref TF fl) @trusted if (__traits(isFloating, TD)) { return readFloat!(TD, es, TF)(fl); }
300 // ////////////////////////////////////////////////////////////////////////// //
301 void writeVULong(TF) (auto ref TF fl, ulong v) if (isWriteableStream!TF) {
302 ubyte[16] buf = void; // actually, 10 is enough ;-)
303 usize pos = 1; // anyway
304 // now write as signed
305 if (v == 0x8000_0000_0000_0000uL) {
306 // special (negative zero)
307 buf[0] = 0x80;
308 } else {
309 if (v&0x8000_0000_0000_0000uL) {
310 v = (v^~0uL)+1; // negate v
311 buf[0] = 0x80;
312 } else {
313 buf[0] = 0;
315 buf[0] |= v&0x3f;
316 v >>= 6;
317 if (v > 0) buf[0] |= 0x40;
318 while (v != 0) {
319 buf[pos] = v&0x7f;
320 v >>= 7;
321 if (v > 0) buf[pos] |= 0x80;
322 ++pos;
325 fl.rawWriteExact(buf[0..pos]);
329 ulong readVULong(TF) (auto ref TF fl) @trusted if (isWriteableStream!TF) {
330 ulong v = 0;
331 ubyte[1] c = void;
332 // first byte contains sign flag
333 fl.rawReadExact(c);
334 if (c[0] == 0x80) return 0x8000_0000_0000_0000uL; // special (negative zero)
335 bool neg = (c[0]&0x80) != 0;
336 v = c[0]&0x3f;
337 c[0] <<= 1;
338 // 63/7 == 9, so we can shift at most 56==(7*8) bits
339 ubyte shift = 6;
340 while (c[0]&0x80) {
341 if (shift > 62) throw new ConvOverflowException("readVULong overflow");
342 fl.rawReadExact(c);
343 ulong n = c[0]&0x7f;
344 if (shift == 62 && n > 1) throw new ConvOverflowException("readVULong overflow");
345 n <<= shift;
346 v |= n;
347 shift += 7;
349 if (neg) v = (v^~0uL)+1; // negate v
350 return v;
354 // write variable-length signed integer
355 void writeVInt(T, TF) (auto ref TF fl, T n) @trusted if (isWriteableStream!TF && __traits(isIntegral, T)) {
356 static assert(T.sizeof <= 8);
357 static if (__traits(isUnsigned, T)) {
358 // output type is unsigned
359 writeVULong(fl, n);
360 } else {
361 // output type is signed
362 writeVULong(fl, cast(ulong)(cast(long)n));
367 // read variable-length integer
368 T readVInt(T, TF) (auto ref TF fl) @trusted if (isReadableStream!TF && __traits(isIntegral, T)) {
369 static assert(T.sizeof <= 8);
370 ulong v = readVULong(fl);
371 static if (__traits(isUnsigned, T)) {
372 // output type is unsigned
373 static if (!is(T == ulong)) {
374 if (v > T.max) throw new ConvOverflowException("readVInt overflow");
376 } else {
377 // output type is signed
378 static if (!is(T == long)) {
379 if (cast(long)v < T.min || cast(long)v > T.max) throw new ConvOverflowException("readVInt overflow");
382 return cast(T)v;
386 // ////////////////////////////////////////////////////////////////////////// //
387 // slow, no recoding
388 string readZString(TF) (auto ref TF fl, bool* eolhit=null, usize maxSize=1024*1024) @trusted if (isReadableStream!TF) {
389 import std.array : appender;
390 bool eh;
391 if (eolhit is null) eolhit = &eh;
392 *eolhit = false;
393 if (maxSize == 0) return null;
394 auto res = appender!string();
395 for (;;) {
396 ubyte ch = fl.readNum!ubyte();
397 if (ch == 0) { *eolhit = true; break; }
398 if (maxSize == 0) break;
399 res.put(cast(char)ch);
400 --maxSize;
402 return res.data;
406 // ////////////////////////////////////////////////////////////////////////// //
407 // slow, no recoding
408 // eolhit will be set on EOF too
409 string readLine(TF) (auto ref TF fl, bool* eolhit=null, usize maxSize=1024*1024) @trusted if (isReadableStream!TF) {
410 import std.array : appender;
411 bool eh;
412 if (eolhit is null) eolhit = &eh;
413 *eolhit = false;
414 if (maxSize == 0) return null;
415 auto res = appender!string();
416 for (;;) {
417 static if (streamHasEOF!TF) if (fl.eof) { *eolhit = true; break; }
418 ubyte ch = fl.readNum!ubyte();
419 if (ch == '\r') {
420 static if (streamHasEOF!TF) if (fl.eof) { *eolhit = true; break; }
421 ch = fl.readNum!ubyte();
422 if (ch == '\n') { *eolhit = true; break; }
423 if (maxSize == 0) break;
424 res.put('\n');
425 } else if (ch == '\n') {
426 *eolhit = true;
427 break;
429 if (maxSize == 0) break;
430 res.put(cast(char)ch);
431 --maxSize;
433 return res.data;
437 // ////////////////////////////////////////////////////////////////////////// //
438 public struct MemoryStream {
439 private:
440 import core.stdc.stdio : SEEK_SET, SEEK_CUR, SEEK_END;
442 ubyte[] data;
443 uint curpos;
445 public:
446 @property ubyte[] bytes () @safe pure nothrow @nogc { return data; }
448 @property uint size () const @safe pure nothrow @nogc { return cast(uint)data.length; } //FIXME: x86_64
449 @property uint tell () const @safe pure nothrow @nogc { return curpos; }
451 //TODO: check for overflow
452 void seek (long offset, int origin=SEEK_SET) @trusted {
453 if (origin == SEEK_CUR) {
454 offset += curpos;
455 } else if (origin == SEEK_END) {
456 offset = cast(long)data.length+offset;
458 if (offset < 0 || offset > data.length) throw new StreamException("invalid offset");
459 curpos = cast(uint)offset;
462 T[] rawRead(T)(T[] buf) @trusted nothrow @nogc if (isMutable!T) {
463 if (buf.length > 0) {
464 //TODO: check for overflow
465 usize rlen = (data.length-curpos)/T.sizeof;
466 if (rlen > buf.length) rlen = buf.length;
467 if (rlen) {
468 import core.stdc.string : memcpy;
469 auto src = cast(const(ubyte)*)data.ptr;
470 auto dest = cast(ubyte*)buf.ptr;
471 memcpy(cast(ubyte*)buf.ptr, data.ptr+curpos, rlen*T.sizeof);
472 curpos += rlen*T.sizeof;
474 return buf[0..rlen];
475 } else {
476 return buf;
480 void rawWrite(T) (in T[] buf) @trusted nothrow {
481 if (buf.length != 0) {
482 //TODO: check for overflow
483 import core.stdc.string : memcpy;
484 // fix size
485 usize bsz = T.sizeof*buf.length;
486 usize nsz = curpos+bsz;
487 if (nsz > data.length) data.length = nsz;
488 // copy data
489 memcpy(data.ptr+curpos, cast(const(void*))buf.ptr, bsz);
490 curpos += bsz;
494 @property bool eof () const @trusted pure nothrow @nogc { return (curpos >= data.length); }
496 void close () @safe pure nothrow @nogc {
497 curpos = 0;
498 data = null;
503 static assert(isReadableStream!MemoryStream);
504 static assert(isWriteableStream!MemoryStream);
505 static assert(isRWStream!MemoryStream);
506 static assert(isSeekableStream!MemoryStream);
507 static assert(isClosableStream!MemoryStream);
508 static assert(streamHasEOF!MemoryStream);
509 static assert(streamHasSeek!MemoryStream);
510 static assert(streamHasTell!MemoryStream);
513 // ////////////////////////////////////////////////////////////////////////// //
514 public struct MemoryStreamRO {
515 private:
516 import core.stdc.stdio : SEEK_SET, SEEK_CUR, SEEK_END;
518 const(ubyte)[] data;
519 uint curpos;
521 public:
522 this (const(void)[] adata) @trusted nothrow @nogc {
523 data = cast(typeof(data))adata;
526 @property const(ubyte)[] bytes () @safe pure nothrow @nogc { return data; }
528 @property uint size () const @safe pure nothrow @nogc { return cast(uint)data.length; } //FIXME: x86_64
529 @property uint tell () const @safe pure nothrow @nogc { return curpos; }
531 //TODO: check for overflow
532 void seek (long offset, int origin=SEEK_SET) @trusted {
533 if (origin == SEEK_CUR) {
534 offset += curpos;
535 } else if (origin == SEEK_END) {
536 offset = cast(long)data.length+offset;
538 if (offset < 0 || offset > data.length) throw new StreamException("invalid offset");
539 curpos = cast(uint)offset;
542 T[] rawRead(T)(T[] buf) @trusted nothrow @nogc if (isMutable!T) {
543 if (buf.length > 0) {
544 //TODO: check for overflow
545 usize rlen = (data.length-curpos)/T.sizeof;
546 if (rlen > buf.length) rlen = buf.length;
547 if (rlen) {
548 import core.stdc.string : memcpy;
549 auto src = cast(const(ubyte)*)data.ptr;
550 auto dest = cast(ubyte*)buf.ptr;
551 memcpy(cast(ubyte*)buf.ptr, data.ptr+curpos, rlen*T.sizeof);
552 curpos += rlen*T.sizeof;
554 return buf[0..rlen];
555 } else {
556 return buf;
560 @property bool eof () const @trusted pure nothrow @nogc { return (curpos >= data.length); }
562 void close () @safe pure nothrow @nogc {
563 curpos = 0;
564 data = null;
569 static assert(isReadableStream!MemoryStreamRO);
570 static assert(!isWriteableStream!MemoryStreamRO);
571 static assert(!isRWStream!MemoryStreamRO);
572 static assert(isSeekableStream!MemoryStreamRO);
573 static assert(isClosableStream!MemoryStreamRO);
574 static assert(streamHasEOF!MemoryStreamRO);
575 static assert(streamHasSeek!MemoryStreamRO);
576 static assert(streamHasTell!MemoryStreamRO);
577 static assert(streamHasSize!MemoryStreamRO);
580 // ////////////////////////////////////////////////////////////////////////// //
581 version(unittest_stream) {
582 import std.stdio;
584 private void dump (const(ubyte)[] data, File fl=stdout) @trusted {
585 for (usize ofs = 0; ofs < data.length; ofs += 16) {
586 writef("%04X:", ofs);
587 foreach (immutable i; 0..16) {
588 if (i == 8) write(' ');
589 if (ofs+i < data.length) writef(" %02X", data[ofs+i]); else write(" ");
591 write(" ");
592 foreach (immutable i; 0..16) {
593 if (ofs+i >= data.length) break;
594 if (i == 8) write(' ');
595 ubyte b = data[ofs+i];
596 if (b <= 32 || b >= 127) write('.'); else writef("%c", cast(char)b);
598 writeln();
604 version(unittest_stream)
605 unittest {
607 auto ms = new MemoryStream();
608 ms.rawWrite(cast(ubyte[])"hello");
609 assert(ms.data == cast(ubyte[])"hello");
610 //dump(ms.data);
611 ushort[3] d;
612 ms.seek(0);
613 assert(ms.rawRead(d).length == 2);
614 assert(d == [0x6568, 0x6c6c, 0]);
615 //dump(cast(ubyte[])d);
618 auto ms = new MemoryStream();
619 wchar[] a = ['\u0401', '\u0280', '\u089e'];
620 ms.rawWrite(a);
621 //dump(ms.data);
624 auto ms = /*new*/ MemoryStreamRO(cast(const(void)[])"hello");
625 assert(ms.data == cast(ubyte[])"hello");
630 version(unittest_stream)
631 unittest {
632 import std.exception : enforce;
635 auto fl = new MemoryStream();
637 fl.seek(0);
638 fl.writeVInt(1);
639 fl.writeVInt(-1);
640 fl.writeVInt(128000);
641 fl.writeVInt(-32000);
643 //dump(fl.data);
644 assert(fl.data == [0x01,0x81,0x40,0xD0,0x0F,0xC0,0xF4,0x03]);
646 try {
647 fl.seek(0);
648 assert(fl.readVInt!byte() == 1);
649 assert(fl.readVInt!byte() == -1);
650 assert(fl.readVInt!int() == 128000);
651 assert(fl.readVInt!int() == -32000);
652 assert(fl.tell() == fl.size);
653 } catch (StreamException) {
654 enforce(false, "FUCK! RANGE!");
657 try {
658 fl.seek(0);
659 assert(fl.readVInt!byte() == 1);
660 assert(fl.readVInt!byte() == -1);
661 assert(fl.readVInt!int() == 128000);
662 assert(fl.readVInt!short() == -32000);
663 assert(fl.tell() == fl.size);
664 } catch (StreamException) {
665 enforce(false, "FUCK! RANGE!");
668 auto cnt = 0;
669 try {
670 fl.seek(0);
671 assert(fl.readVInt!byte() == 1);
672 ++cnt;
673 assert(fl.readVInt!byte() == -1);
674 ++cnt;
675 assert(fl.readVInt!byte() == 128000);
676 ++cnt;
677 assert(fl.readVInt!byte() == -32000);
678 ++cnt;
679 assert(fl.tell() == fl.size);
680 ++cnt;
681 enforce(false, "SHIT!");
682 } catch (Exception) {
683 enforce(cnt == 2, "FUCK RANGE");
686 cnt = 0;
687 try {
688 fl.seek(0);
689 assert(fl.readVInt!byte() == 1);
690 ++cnt;
691 assert(fl.readVInt!ubyte() == -1);
692 ++cnt;
693 assert(fl.readVInt!byte() == 128000);
694 ++cnt;
695 assert(fl.readVInt!byte() == -32000);
696 ++cnt;
697 assert(fl.tell() == fl.size);
698 ++cnt;
699 enforce(false, "SHIT!");
700 } catch (Exception) {
701 enforce(cnt == 1, "FUCK RANGE");
704 cnt = 0;
705 try {
706 fl.seek(0);
707 assert(fl.readVInt!byte() == 1);
708 ++cnt;
709 assert(fl.readVInt!byte() == -1);
710 ++cnt;
711 assert(fl.readVInt!int() == 128000);
712 ++cnt;
713 assert(fl.readVInt!ushort() == -32000);
714 ++cnt;
715 assert(fl.tell() == fl.size);
716 ++cnt;
717 enforce(false, "SHIT!");
718 } catch (Exception) {
719 enforce(cnt == 3, "FUCK RANGE");
723 version(LittleEndian) {
725 auto fl = new MemoryStream();
726 fl.writeInt!long(1);
727 fl.writeInt!(long, "BE")(1);
728 fl.writeNum!long(1);
729 fl.writeNum!long(-2);
730 fl.writeNum!(long, "BE")(1);
731 fl.writeNum!(long, "BE")(-2);
732 fl.writeNum!float(1.0f);
733 fl.writeNum!(float, "BE")(1.0f);
734 //dump(fl.data);
735 assert(fl.data == [
736 0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
737 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,
738 0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
739 0xFE,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
740 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,
741 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFE,
742 0x00,0x00,0x80,0x3F, 0x3F,0x80,0x00,0x00,
745 fl.seek(0);
746 assert(fl.readInt!long() == 1);
747 assert(fl.readInt!(long, "BE")() == 1);
748 assert(fl.readNum!long() == 1);
749 assert(fl.readNum!long() == -2);
750 assert(fl.readNum!(long, "BE")() == 1);
751 assert(fl.readNum!(long, "BE")() == -2);
752 assert(fl.readNum!float() == 1.0f);
753 assert(fl.readNum!(float, "BE")() == 1.0f);
754 assert(fl.tell() == fl.size);
755 //writeln("IO: done");
760 auto fl = new MemoryStream();
761 fl.rawWrite("loves\nalice\n");
762 fl.seek(0);
763 bool eol = false;
764 assert(fl.readLine(&eol) == "loves" && eol == true && !fl.eof);
765 assert(fl.readLine(&eol) == "alice" && eol == true && fl.eof);
769 auto fl = new MemoryStream();
770 fl.rawWrite("loves\0alice\0");
771 fl.seek(0);
772 bool eol = false;
773 assert(fl.readZString(&eol) == "loves" && eol == true && !fl.eof);
774 assert(fl.readZString(&eol) == "alice" && eol == true && fl.eof);
779 // ////////////////////////////////////////////////////////////////////////// //
780 private class PartialStreamROData {
781 private:
782 ulong rc = 1;
783 immutable bool gcrange; // do `GC.removeRange()`?
784 immutable long start;
785 immutable long size;
786 long curpos; // current position
787 usize gcroot; // allocated memory that must be free()d
789 this (long astart, long asize, usize agcroot, bool arange) @safe nothrow @nogc
790 in {
791 assert(astart >= 0);
792 assert(asize >= 0);
794 body {
795 gcrange = arange;
796 start = astart;
797 size = asize;
798 gcroot = agcroot;
799 //{ import std.stdio; stderr.writefln("0x%08x: ctor, rc=%s", gcroot, rc); }
802 // this should never be called
803 ~this () @safe nothrow @nogc {
804 if (rc != 0) assert(0); // the thing that should not be
805 assert(0); // why we are here?!
808 void incRef () @safe nothrow @nogc {
809 if (++rc == 0) assert(0); // hey, this is definitely a bug!
810 //{ import std.stdio; stderr.writefln("0x%08x: incRef, rc=%s", gcroot, rc); }
813 // return true if this class is dead
814 bool decRef () {
815 if (rc-- == 0) assert(0); // hey, this is definitely a bug!
816 //{ import std.stdio; stderr.writefln("0x%08x: decRef, rc=%s", gcroot, rc); }
817 if (rc == 0) {
818 import core.memory : GC;
819 import core.stdc.stdlib : free;
820 clear(); // finalize stream
821 if (gcroot == 0) assert(0); // the thing that should not be
822 // remove roots
823 if (gcrange) {
824 GC.removeRange(cast(void*)gcroot);
825 GC.removeRoot(cast(void*)gcroot);
827 // free allocated memory
828 free(cast(void*)gcroot);
829 // just in case
830 //{ import std.stdio; stderr.writefln("0x%08x: dead, rc=%s", gcroot, rc); }
831 gcroot = 0;
832 return true;
833 } else {
834 return false;
838 protected:
839 abstract void clear ();
840 abstract void[] read (void[] buf);
844 private final class PartialStreamDataImpl(ST) : PartialStreamROData {
845 ST stream;
847 this(ST) (auto ref ST astrm, long astart, long asize, usize agcroot, bool arange) {
848 super(astart, asize, agcroot, arange);
849 stream = astrm;
852 protected:
853 override void clear () { stream = stream.init; }
855 override void[] read (void[] buf) {
856 assert(curpos >= 0 && curpos <= size);
857 usize len = buf.length;
858 if (len > size-curpos) len = cast(usize)(size-curpos);
859 if (len > 0) {
860 stream.seek(start+curpos);
861 auto res = stream.rawRead(buf[0..len]);
862 curpos += res.length;
863 return res;
864 } else {
865 return buf[0..0];
871 public struct PartialStreamRO {
872 private:
873 PartialStreamROData mStData;
875 void initialize(ST) (auto ref ST astrm, long astart, long asize)
876 if (isReadableStream!ST && isSeekableStream!ST)
878 if (astart < 0) astart = astrm.tell;
879 if (astart < 0) throw new StreamException("invalid partial stream parameters");
880 if (asize < 0) {
881 astrm.seek(0, SEEK_END);
882 asize = astrm.tell;
883 if (asize < 0) throw new StreamException("invalid partial stream parameters");
884 if (astart > asize) throw new StreamException("invalid partial stream parameters");
885 asize -= astart;
887 astrm.seek(astart);
888 // and now... rock-a-rolla!
890 // actually, we shouldn't use malloc() here, 'cause we can have alot of
891 // free memory in GC and no memory for malloc(), but... let's be realistic:
892 // we aren't aiming at constrained systems
893 import core.exception : onOutOfMemoryError;
894 import core.memory : GC;
895 import core.stdc.stdlib : malloc;
896 import std.conv : emplace;
897 import std.traits : hasIndirections;
898 alias CT = PartialStreamDataImpl!ST; // i'm lazy
899 enum instSize = __traits(classInstanceSize, CT);
900 // let's hope that malloc() aligns returned memory right
901 auto mem = malloc(instSize);
902 if (mem is null) onOutOfMemoryError(); // oops
903 usize root = cast(usize)mem;
904 static if (hasIndirections!ST) {
905 // ouch, ST has some pointers; register it as gc root and range
906 // note that this approach is very simplictic; we might want to
907 // scan the type for pointers using typeinfo pointer bitmap and
908 // register only pointer containing areas.
909 GC.addRoot(cast(void*)root);
910 GC.addRange(cast(void*)root, instSize);
911 enum isrng = true;
912 } else {
913 enum isrng = false;
915 mStData = emplace!CT(mem[0..instSize], astrm, astart, asize, root, isrng);
919 public:
920 import core.stdc.stdio : SEEK_SET, SEEK_CUR, SEEK_END;
922 immutable string name;
924 // ST must support copying!
925 this(ST) (string aname, auto ref ST astrm, long astart=-1, long asize=-1)
926 if (isReadableStream!ST && isSeekableStream!ST)
928 initialize(astrm, astart, asize);
929 name = aname;
932 this(ST) (auto ref ST astrm, long astart=-1, long asize=-1)
933 if (isReadableStream!ST && isSeekableStream!ST)
935 initialize(astrm, astart, asize);
936 name = null;
939 this (this) @safe nothrow @nogc { if (isOpen) mStData.incRef(); }
940 ~this () { close(); }
942 void opAssign() (auto ref PartialStreamRO src) {
943 if (isOpen) {
944 // assigning to opened stream
945 if (src.isOpen) {
946 // both streams are opened
947 // we don't care if internal streams are different, our rc scheme will took care of this
948 auto old = mStData; // decRef() can throw, so be on the safe side
949 mStData = src.mStData;
950 mStData.incRef(); // this can't throw
951 old.decRef(); // release old stream
952 } else {
953 // just close this one
954 close();
956 } else if (src.isOpen) {
957 // this stream is closed, but other is open; easy deal
958 mStData = src.mStData;
959 mStData.incRef();
963 @property bool isOpen () const pure @safe nothrow @nogc { return (mStData !is null); }
965 void close () {
966 if (isOpen) {
967 mStData.decRef();
968 mStData = null;
972 @property long stofs () const @safe pure nothrow @nogc { return (isOpen ? mStData.start : 0); }
973 @property long tell () const @safe pure nothrow @nogc { return (isOpen ? mStData.curpos : 0); }
974 @property long size () const @safe pure nothrow @nogc { return (isOpen ? mStData.size : 0); }
975 @property bool eof () const @trusted pure nothrow @nogc { return (isOpen ? mStData.curpos >= mStData.size : true); }
977 //TODO: check for overflow
978 void seek (long offset, int origin=SEEK_SET) @trusted {
979 if (!isOpen) throw new StreamException("can't seek in closed partial stream");
980 if (origin == SEEK_CUR) {
981 offset += mStData.curpos;
982 } else if (origin == SEEK_END) {
983 offset = mStData.size+offset;
985 if (offset < 0 || offset > mStData.size) throw new StreamException("invalid offset");
986 mStData.curpos = offset;
989 T[] rawRead(T)(T[] buf) @trusted if (isMutable!T) {
990 if (!isOpen) throw new StreamException("can't read from closed partial stream");
991 if (buf.length > 0) {
992 auto res = mStData.read(cast(void[])buf);
993 return buf[0..res.length/T.sizeof];
994 } else {
995 return buf[0..0];
1001 static assert(isReadableStream!PartialStreamRO);
1002 static assert(!isWriteableStream!PartialStreamRO);
1003 static assert(!isRWStream!PartialStreamRO);
1004 static assert(isSeekableStream!PartialStreamRO);
1005 static assert(isClosableStream!PartialStreamRO);
1006 static assert(streamHasEOF!PartialStreamRO);
1007 static assert(streamHasSeek!PartialStreamRO);
1008 static assert(streamHasTell!PartialStreamRO);
1011 version(unittest_stream)
1012 unittest {
1013 void rwc(T) (T stream, long pos, char ch) {
1014 char[1] b;
1015 t.seek(pos);
1016 auto r = t.rawRead(b);
1017 assert(r.length == b.length);
1018 assert(b[0] == ch);
1021 auto ms = new MemoryStream();
1022 ms.rawWrite(cast(void[])"test");
1023 auto t = PartialStreamRO(ms, 1);
1025 ubyte[1] b;
1026 t.seek(1);
1027 auto r = t.rawRead(b);
1028 assert(r.length == b.length);
1029 assert(b[0] == 's');
1031 rwc(t, 2, 't');
1032 rwc(t, 0, 'e');
1033 t.close();
1037 // ////////////////////////////////////////////////////////////////////////// //
1038 // turn streams to ranges
1039 // rngtype can be: "any", "read", "write"
1040 // you can add ",indexable" to rngtype to include `opIndex()`
1041 auto streamAsRange(string rngtype="any", STP) (auto ref STP st) if (isReadableStream!STP || isWriteableStream|STP) {
1042 enum {
1043 HasR = 0x01,
1044 HasW = 0x02,
1045 HasRW = HasR|HasW,
1046 HasI = 0x04
1048 template ParseType (string s) {
1049 private static string get (string str) {
1050 usize spos = 0;
1051 while (spos < str.length && str[spos] <= ' ') ++spos;
1052 usize epos = spos;
1053 while (epos < str.length && str[epos] != ',') ++epos;
1054 while (epos > 0 && str[epos-1] <= ' ') --epos;
1055 return str[spos..epos];
1057 private static string skip (string str) {
1058 usize spos = 0;
1059 while (spos < str.length && str[spos] != ',') ++spos;
1060 if (spos < str.length) ++spos;
1061 while (spos < str.length && str[spos] <= ' ') ++spos;
1062 return str[spos..$];
1064 private ubyte parse (string str) {
1065 ubyte has;
1066 while (str.length > 0) {
1067 auto w = get(str);
1068 switch (w) {
1069 case "read": has |= HasR; break;
1070 case "write": has |= HasW; break;
1071 case "any": has |= HasR|HasW; break;
1072 case "indexable": has |= HasI; break;
1073 default:
1074 foreach (immutable char ch; w) {
1075 switch (ch) {
1076 case 'r': has |= HasR; break;
1077 case 'w': has |= HasW; break;
1078 case 'i': has |= HasI; break;
1079 default: assert(0, "invalid mode word: '"~w~"'");
1082 break;
1084 str = skip(str);
1086 if (has == 0) has = HasR|HasW; // any
1087 return has;
1089 enum ParseType = parse(s);
1092 enum typeflags = ParseType!(rngtype);
1093 // setup stream type
1094 static if ((typeflags&HasRW) == HasRW) {
1095 enum rdStream = isReadableStream!STP;
1096 enum wrStream = isWriteableStream!STP;
1097 } else static if (typeflags&HasR) {
1098 static assert(isReadableStream!STP, "stream must be readable");
1099 enum rdStream = isReadableStream!STP;
1100 enum wrStream = false;
1101 } else static if (typeflags&HasW) {
1102 static assert(isWriteableStream!STP, "stream must be writeable");
1103 enum rdStream = false;
1104 enum wrStream = isWriteableStream!STP;
1105 } else {
1106 static assert(0, "invalid range type: "~rngtype);
1109 import core.stdc.stdio : SEEK_SET, SEEK_CUR, SEEK_END;
1111 static struct StreamRange(ST) {
1112 private:
1113 ST strm;
1114 static if (rdStream) {
1115 ubyte[1] curByte;
1116 bool atEof;
1119 this(STX) (auto ref STX ast) {
1120 strm = ast;
1121 static if (rdStream) {
1122 atEof = true;
1123 // catch errors here, as `std.stdio.File` throws exception on reading from "w" files
1124 try {
1125 auto rd = strm.rawRead(curByte);
1126 if (rd.length != 0) atEof = false;
1127 } catch (Exception) {}
1131 public:
1132 // output range part
1133 static if (wrStream) {
1134 // `put`
1135 void put (in ubyte data) { strm.rawWriteExact((&data)[0..1]); }
1136 void put (in ubyte[] data...) { strm.rawWriteExact(data); }
1139 // input range part
1140 static if (rdStream) {
1141 // `empty`
1142 @property bool empty () const pure @safe nothrow @nogc { return atEof; }
1144 // `length`
1145 static if (streamHasTell!ST && (streamHasSeek!ST || streamHasSize!ST)) {
1146 private enum hasRealLength = true;
1147 @property usize length() () {
1148 immutable cpos = strm.tell;
1149 static if (streamHasSize!ST) {
1150 immutable sz = strm.size;
1151 } else {
1152 strm.seek(0, SEEK_END);
1153 immutable sz = strm.tell;
1154 strm.seek(cpos, SEEK_SET);
1156 if (cpos >= sz) return 0;
1157 immutable len = sz-cpos;
1158 if (len > usize.max) {
1159 import core.exception : onRangeError;
1160 onRangeError();
1162 return cast(usize)len;
1164 } else {
1165 private enum hasRealLength = false;
1168 // `front`
1169 @property ubyte front () const pure @safe nothrow @nogc { return curByte[0]; }
1171 // `popFront`
1172 void popFront() () {
1173 curByte[0] = 0;
1174 if (!atEof && strm.rawRead(curByte[]).length == 0) atEof = true;
1177 // `opIndex`
1178 // it's slow and unreliable
1179 static if ((typeflags&HasI) && streamHasTell!ST && streamHasSeek!ST && hasRealLength) {
1180 ubyte opIndex() (usize pos) {
1181 import core.exception : onRangeError;
1182 if (pos >= this.length) onRangeError();
1183 immutable cpos = strm.tell;
1184 strm.seek(pos, SEEK_CUR);
1185 ubyte[1] res;
1186 strm.rawReadExact(res[]);
1187 strm.seek(cpos, SEEK_SET);
1188 return res[0];
1194 return StreamRange!STP(st);