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/>.
18 module iv
.vfs
.pred
/*is aliced*/;
20 public import iv
.alice
;
21 public import iv
.vfs
.types
: Seek
;
22 public import iv
.vfs
.error
;
23 public import iv
.vfs
.vfile
: IVVFSIgnore
;
26 // ////////////////////////////////////////////////////////////////////////// //
27 /// is this "low-level" stream that can be read?
28 enum isLowLevelStreamR(T
) = is(typeof((inout int=0) {
31 ssize r
= t
.read(b
.ptr
, 1);
34 /// is this "low-level" stream that can be written?
35 enum isLowLevelStreamW(T
) = is(typeof((inout int=0) {
38 ssize w
= t
.write(b
.ptr
, 1);
42 /// is this "low-level" stream that can be seeked?
43 enum isLowLevelStreamS(T
) = is(typeof((inout int=0) {
45 long p
= t
.lseek(0, 0);
49 // ////////////////////////////////////////////////////////////////////////// //
50 /// check if a given stream supports `eof`
51 enum streamHasEof(T
) = is(typeof((inout int=0) {
56 /// check if a given stream supports `seek`
57 enum streamHasSeek(T
) = is(typeof((inout int=0) {
58 import core
.stdc
.stdio
: SEEK_END
;
64 /// check if a given stream supports `tell`
65 enum streamHasTell(T
) = is(typeof((inout int=0) {
70 /// check if a given stream supports `tell`
71 enum streamHasClose(T
) = is(typeof((inout int=0) {
76 /// check if a given stream supports `name`
77 enum streamHasName(T
) = is(typeof((inout int=0) {
79 const(char)[] n
= t
.name
;
82 /// check if a given stream supports `size`
83 enum streamHasSize(T
) = is(typeof((inout int=0) {
88 /// check if a given stream supports `size`
89 enum streamHasSizeLowLevel(T
) = is(typeof((inout int=0) {
94 /// check if a given stream supports `isOpen`
95 enum streamHasIsOpen(T
) = is(typeof((inout int=0) {
100 /// check if a given stream supports `flush()`
101 enum streamHasFlush(T
) = is(typeof((inout int=0) {
106 // ////////////////////////////////////////////////////////////////////////// //
107 /// check if a given stream supports `rawRead()`.
108 /// it's enough to support `void[] rawRead (void[] buf)`
109 enum isReadableStream(T
) = is(typeof((inout int=0) {
112 auto v
= cast(void[])b
;
116 /// check if a given stream supports `rawWrite()`.
117 /// it's enough to support `inout(void)[] rawWrite (inout(void)[] buf)`
118 enum isWriteableStream(T
) = is(typeof((inout int=0) {
121 t
.rawWrite(cast(void[])b
);
124 /// check if a given stream supports both reading and writing
125 enum isRWStream(T
) = isReadableStream
!T
&& isWriteableStream
!T
;
127 /// check if a given stream supports both reading and writing
128 enum isRorWStream(T
) = isReadableStream
!T || isWriteableStream
!T
;
130 /// check if a given stream supports `.seek(ofs, [whence])`, and `.tell`
131 enum isSeekableStream(T
) = (streamHasSeek
!T
&& streamHasTell
!T
);
133 /// check if we can get size of a given stream.
134 /// this can be done either with `.size`, or with `.seek` and `.tell`
135 enum isSizedStream(T
) = (streamHasSize
!T || isSeekableStream
!T
);
138 // ////////////////////////////////////////////////////////////////////////// //
139 version(vfs_test_stream
) {
141 static assert(isReadableStream
!File
);
142 static assert(isWriteableStream
!File
);
143 static assert(isRWStream
!File
);
144 static assert(isSeekableStream
!File
);
145 static assert(streamHasEof
!File
);
146 static assert(streamHasSeek
!File
);
147 static assert(streamHasTell
!File
);
148 static assert(streamHasName
!File
);
149 static assert(streamHasSize
!File
);
151 static assert(!isReadableStream
!S
);
152 static assert(!isWriteableStream
!S
);
153 static assert(!isRWStream
!S
);
154 static assert(!isSeekableStream
!S
);
155 static assert(!streamHasEof
!S
);
156 static assert(!streamHasSeek
!S
);
157 static assert(!streamHasTell
!S
);
158 static assert(!streamHasName
!S
);
159 static assert(!streamHasSize
!S
);
163 // ////////////////////////////////////////////////////////////////////////// //
164 /// augment low-level streams with `rawRead`
165 T
[] rawRead(ST
, T
) (auto ref ST st
, T
[] buf
) if (isLowLevelStreamR
!ST
&& !is(T
== const) && !is(T
== immutable)) {
166 if (buf
.length
> 0) {
167 auto res
= st
.read(buf
.ptr
, buf
.length
*T
.sizeof
);
168 if (res
== -1 || res
%T
.sizeof
!= 0) throw new VFSException("read error");
169 return buf
[0..res
/T
.sizeof
];
175 /// augment low-level streams with `rawWrite`
176 void rawWrite(ST
, T
) (auto ref ST st
, in T
[] buf
) if (isLowLevelStreamW
!ST
) {
177 if (buf
.length
> 0) {
178 auto res
= st
.write(buf
.ptr
, buf
.length
*T
.sizeof
);
179 if (res
== -1 || res
%T
.sizeof
!= 0) throw new VFSException("write error");
183 /// read exact size or throw error
184 T
[] rawReadExact(ST
, T
) (auto ref ST st
, T
[] buf
) if (isReadableStream
!ST
&& !is(T
== const) && !is(T
== immutable)) {
185 if (buf
.length
== 0) return buf
;
186 auto left
= buf
.length
*T
.sizeof
;
187 auto dp
= cast(ubyte*)buf
.ptr
;
189 auto res
= st
.rawRead(cast(void[])(dp
[0..left
]));
190 if (res
.length
== 0) throw new VFSException("read error");
197 /// write exact size or throw error (just for convenience)
198 void rawWriteExact(ST
, T
) (auto ref ST st
, in T
[] buf
) if (isWriteableStream
!ST
) { st
.rawWrite(buf
); }
200 /// if stream doesn't have `.size`, but can be seeked, emulate it
201 long size(ST
) (auto ref ST st
) if (isSeekableStream
!ST
&& !streamHasSize
!ST
) {
203 st
.seek(0, Seek
.End
);
210 // ////////////////////////////////////////////////////////////////////////// //
211 public enum isGoodEndianness(string s
) = (s
== "LE" || s
== "le" || s
== "BE" || s
== "be");
213 public template isLittleEndianness(string s
) if (isGoodEndianness
!s
) {
214 enum isLittleEndianness
= (s
== "LE" || s
== "le");
217 public template isBigEndianness(string s
) if (isGoodEndianness
!s
) {
218 enum isLittleEndianness
= (s
== "BE" || s
== "be");
221 public template isSystemEndianness(string s
) if (isGoodEndianness
!s
) {
222 version(LittleEndian
) {
223 enum isSystemEndianness
= isLittleEndianness
!s
;
225 enum isSystemEndianness
= isBigEndianness
!s
;
230 // ////////////////////////////////////////////////////////////////////////// //
231 /// write integer value of the given type, with the given endianness (default: little-endian)
232 /// usage: st.writeNum!ubyte(10)
233 void writeNum(T
, string es
="LE", ST
) (auto ref ST st
, T n
) if (isGoodEndianness
!es
&& isWriteableStream
!ST
&& __traits(isIntegral
, T
)) {
234 static assert(T
.sizeof
<= 8); // just in case
235 static if (isSystemEndianness
!es || T
.sizeof
== 1) {
236 st
.rawWriteExact((&n
)[0..1]);
238 ubyte[T
.sizeof
] b
= void;
239 version(LittleEndian
) {
240 // convert to big-endian
241 foreach_reverse (ref x
; b
) { x
= n
&0xff; n
>>= 8; }
243 // convert to little-endian
244 foreach (ref x
; b
) { x
= n
&0xff; n
>>= 8; }
246 st
.rawWriteExact(b
[]);
251 /// read integer value of the given type, with the given endianness (default: little-endian)
252 /// usage: auto v = st.readNum!ubyte
253 T
readNum(T
, string es
="LE", ST
) (auto ref ST st
) if (isGoodEndianness
!es
&& isReadableStream
!ST
&& __traits(isIntegral
, T
)) {
254 static assert(T
.sizeof
<= 8); // just in case
255 static if (isSystemEndianness
!es || T
.sizeof
== 1) {
257 st
.rawReadExact((&v
)[0..1]);
260 ubyte[T
.sizeof
] b
= void;
261 st
.rawReadExact(b
[]);
263 version(LittleEndian
) {
264 // convert from big-endian
265 foreach (ubyte x
; b
) { v
<<= 8; v |
= x
; }
267 // conver from little-endian
268 foreach_reverse (ubyte x
; b
) { v
<<= 8; v |
= x
; }
275 private enum reverseBytesMixin
= "
276 foreach (idx; 0..b.length/2) {
284 /// write floating value of the given type, with the given endianness (default: little-endian)
285 /// usage: st.writeNum!float(10)
286 void writeNum(T
, string es
="LE", ST
) (auto ref ST st
, T n
) if (isGoodEndianness
!es
&& isWriteableStream
!ST
&& __traits(isFloating
, T
)) {
287 static assert(T
.sizeof
<= 8);
288 static if (isSystemEndianness
!es
) {
289 st
.rawWriteExact((&n
)[0..1]);
291 import core
.stdc
.string
: memcpy
;
292 ubyte[T
.sizeof
] b
= void;
293 memcpy(b
.ptr
, &v
, T
.sizeof
);
294 mixin(reverseBytesMixin
);
295 st
.rawWriteExact(b
[]);
300 /// read floating value of the given type, with the given endianness (default: little-endian)
301 /// usage: auto v = st.readNum!float
302 T
readNum(T
, string es
="LE", ST
) (auto ref ST st
) if (isGoodEndianness
!es
&& isReadableStream
!ST
&& __traits(isFloating
, T
)) {
303 static assert(T
.sizeof
<= 8);
305 static if (isSystemEndianness
!es
) {
306 st
.rawReadExact((&v
)[0..1]);
308 import core
.stdc
.string
: memcpy
;
309 ubyte[T
.sizeof
] b
= void;
310 st
.rawReadExact(b
[]);
311 mixin(reverseBytesMixin
);
312 memcpy(&v
, b
.ptr
, T
.sizeof
);
318 // ////////////////////////////////////////////////////////////////////////// //
319 // first byte: bit 7 is sign; bit 6 is "has more bytes" mark; bits 0..5: first number bits
320 // next bytes: bit 7 is "has more bytes" mark; bits 0..6: next number bits
321 void writeXInt(T
: ulong, ST
) (auto ref ST fl
, T vv
) if (isWriteableStream
!ST
) {
322 ubyte[16] buf
= void; // actually, 10 is enough ;-)
323 static if (T
.sizeof
== ulong.sizeof
) ulong v
= cast(ulong)vv
;
324 else static if (!__traits(isUnsigned
, T
)) ulong v
= cast(ulong)cast(long)vv
; // extend sign bits
325 else ulong v
= cast(ulong)vv
;
326 uint len
= 1; // at least
327 // now write as signed
328 if (v
== 0x8000_0000_0000_0000UL) {
329 // special (negative zero)
332 if (v
&0x8000_0000_0000_0000UL) {
333 v
= (v^
~0uL)+1; // negate v
334 buf
.ptr
[0] = 0x80; // sign bit
338 buf
.ptr
[0] |
= v
&0x3f;
340 if (v
!= 0) buf
.ptr
[0] |
= 0x40; // has more
342 buf
.ptr
[len
] = v
&0x7f;
344 if (v
> 0) buf
.ptr
[len
] |
= 0x80; // has more
348 fl
.rawWriteExact(buf
.ptr
[0..len
]);
352 T
readXInt(T
: ulong, ST
) (auto ref ST fl
) if (isReadableStream
!ST
) {
353 import std
.conv
: ConvOverflowException
;
356 // first byte contains sign flag
357 fl
.rawReadExact((&c
)[0..1]);
359 // special (negative zero)
360 v
= 0x8000_0000_0000_0000UL;
362 bool neg = ((c
&0x80) != 0);
365 // 63/7 == 9, so we can shift at most 56==(7*8) bits
368 if (shift
> 62) throw new ConvOverflowException("readXInt overflow");
369 fl
.rawReadExact((&c
)[0..1]);
371 if (shift
== 62 && n
> 1) throw new ConvOverflowException("readXInt overflow");
376 if (neg) v
= (v^
~0uL)+1; // negate v
378 // now convert to output
379 static if (T
.sizeof
== v
.sizeof
) {
381 } else static if (!__traits(isUnsigned
, T
)) {
382 auto l
= cast(long)v
;
383 if (v
< T
.min
) throw new ConvOverflowException("readXInt underflow");
384 if (v
> T
.max
) throw new ConvOverflowException("readXInt overflow");
387 if (v
> T
.max
) throw new ConvOverflowException("readXInt overflow");
393 // ////////////////////////////////////////////////////////////////////////// //
394 void readStruct(string es
="LE", SS
, ST
) (auto ref ST fl
, ref SS st
)
395 if (is(SS
== struct) && isGoodEndianness
!es
&& isReadableStream
!ST
)
397 import iv
.vfs
.vfile
: VFile
;
398 static assert(!is(SS
== VFile
), "invalid argument order in `readStruct`");
399 void unserData(T
) (ref T v
) {
400 import std
.traits
: Unqual
;
402 static if (is(T
: V
[], V
)) {
404 static if (__traits(isStaticArray
, T
)) {
405 foreach (ref it
; v
) unserData(it
);
406 } else static if (is(UT
== char)) {
407 // special case: dynamic `char[]` array will be loaded as asciiz string
410 if (fl
.rawRead((&c
)[0..1]).length
== 0) break; // don't require trailing zero on eof
415 assert(0, "cannot load dynamic arrays yet");
417 } else static if (is(T
: V
[K
], K
, V
)) {
418 assert(0, "cannot load associative arrays yet");
419 } else static if (__traits(isIntegral
, UT
) ||
__traits(isFloating
, UT
)) {
420 // this takes care of `*char` and `bool` too
421 v
= cast(UT
)fl
.readNum
!(UT
, es
);
422 } else static if (is(T
== struct)) {
424 import std
.traits
: FieldNameTuple
, hasUDA
;
425 foreach (string fldname
; FieldNameTuple
!T
) {
426 static if (!hasUDA
!(__traits(getMember
, T
, fldname
), IVVFSIgnore
)) {
427 unserData(__traits(getMember
, v
, fldname
));