1 /**
2     Stream Readers and Writers.
3 
4     Utility classes for reading from and writing to streams.
5 
6     Copyright:
7         Copyright © 2023-2025, Kitsunebi Games
8         Copyright © 2023-2025, Inochi2D Project
9     
10     License:   $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost License 1.0)
11     Authors:   Luna Nielsen
12 */
13 module nulib.io.stream.rw;
14 import nulib.io.stream;
15 import nulib.memory.endian;
16 import nulib.text.unicode;
17 import nulib.string;
18 import numem;
19 
20 /**
21     A stream reader is a helper class which makes it easier to
22     read data from streams.
23 */
24 final
25 class StreamReader : NuObject {
26 @nogc:
27 private:
28     Stream stream_;
29 
30     // Reads a sequence of bytes and ensures their endianness is correct.
31     // Additionally does a type-cast
32     pragma(inline, true)
33     T readEndian(T, Endianess endian)() @trusted if (__traits(isScalar, T)) {
34         ubyte[T.sizeof] data;
35         ptrdiff_t l = stream_.read(data);
36         
37         if (l <= 0)
38             return T.init;
39     
40         return nu_etoh!(T, endian)(*(cast(T*)data.ptr));
41     }
42 
43     // Reads a sequence of bytes and ensures their endianness is correct.
44     pragma(inline, true)
45     T[] readEndianSeq(T, Endianess endian)(size_t count) @trusted if (__traits(isScalar, T)) {
46         T[] buffer;
47         buffer = nu_resize(buffer, count, 1);
48         ubyte[] bufferView = (cast(ubyte*)buffer.ptr)[0..count*T.sizeof];
49 
50         ptrdiff_t read = stream_.read(bufferView)/T.sizeof;
51         return nu_etoh!(T, endian)(nu_resize(buffer, read <= 0 ? 0 : read, 1)[]);
52     }
53 
54 public:
55 @safe:
56 
57     /**
58         The stream this reader is reading from
59     */
60     @property Stream stream() => stream_;
61 
62     /**
63         Constructs a new stream reader
64     */
65     this(Stream stream) @trusted {
66         enforce(stream.canRead(), nogc_new!StreamUnsupportedException(stream));
67         this.stream_ = stream;
68     }
69 
70     /**
71         Reads a float from the stream.
72         
73         Returns:
74             The read value.
75     */
76     float readF32LE() { return readEndian!(float, Endianess.littleEndian)(); }
77     float readF32BE() { return readEndian!(float, Endianess.bigEndian)(); } /// ditto
78 
79     /**
80         Reads a double from the stream.
81         
82         Returns:
83             The read value.
84     */
85     double readF64LE() { return readEndian!(double, Endianess.littleEndian)(); }
86     double readF64BE() { return readEndian!(double, Endianess.bigEndian)(); } /// ditto
87 
88     /**
89         Reads a byte from the stream.
90         
91         Returns:
92             The read value.
93     */
94     byte readI8() { return cast(byte)readU8(); }
95 
96     /**
97         Reads a short from the stream.
98         
99         Returns:
100             The read value.
101     */
102     short readI16LE() { return readEndian!(short, Endianess.littleEndian)(); }
103     short readI16BE() { return readEndian!(short, Endianess.bigEndian)(); } /// ditto
104 
105     /**
106         Reads a int from the stream.
107         
108         Returns:
109             The read value.
110     */
111     int readI32LE() { return readEndian!(int, Endianess.littleEndian)(); }
112     int readI32BE() { return readEndian!(int, Endianess.bigEndian)(); } /// ditto
113 
114     /**
115         Reads a long from the stream.
116         
117         Returns:
118             The read value.
119     */
120     long readI64LE() { return readEndian!(long, Endianess.littleEndian)(); }
121     long readI64BE() { return readEndian!(long, Endianess.bigEndian)(); } /// ditto
122 
123     /**
124         Reads a ubyte from the stream.
125         
126         Returns:
127             The read value.
128     */
129     ubyte readU8() {
130         ubyte[1] buffer;
131         stream.read(buffer);
132         return buffer[0];
133     }
134 
135     /**
136         Reads a ushort from the stream.
137         
138         Returns:
139             The read value.
140     */
141     ushort readU16LE() { return readEndian!(ushort, Endianess.littleEndian)(); }
142     ushort readU16BE() { return readEndian!(ushort, Endianess.bigEndian)(); } /// ditto
143 
144     /**
145         Reads a uint from the stream.
146         
147         Returns:
148             The read value.
149     */
150     uint readU32LE() { return readEndian!(uint, Endianess.littleEndian)(); }
151     uint readU32BE() { return readEndian!(uint, Endianess.bigEndian)(); } /// ditto
152 
153     /**
154         Reads a ulong from the stream.
155         
156         Returns:
157             The read value.
158     */
159     ulong readU64LE() { return readEndian!(ulong, Endianess.littleEndian)(); }
160     ulong readU64BE() { return readEndian!(ulong, Endianess.bigEndian)(); } /// ditto
161 
162     /**
163         Reads a value from the stream.
164         
165         Returns:
166             The read value.
167     */
168     T readLE(T)() { return readEndian!(T, Endianess.littleEndian)(); }
169     T readBE(T)() { return readEndian!(T, Endianess.bigEndian)(); }
170 
171     /**
172         Reads a UTF8 string from the stream.
173         
174         Returns:
175             The UTF8 string read from the stream.
176     */
177     nstring readUTF8(uint length) @trusted {
178         nstring result = nstring(length);
179         ptrdiff_t read = stream.read(cast(ubyte[])result.value);
180         return read <= 0 ? nstring.init : result;
181     }
182 
183     /**
184         Reads a UTF16 string from the stream.
185 
186         This function determines endianness from the byte-order-mark
187         in the stream; if none is found an exception is thrown.
188 
189         Params:
190             length = the length of the string to read, including BOM.
191         
192         Returns:
193             The UTF16 string read from the stream.
194     */
195     nwstring readUTF16(uint length) @trusted {
196         wchar[] buffer = this.readEndianSeq!(wchar, NATIVE_ENDIAN)(length);
197         codepoint bom = getBOM(buffer);
198 
199         if (!bom) {
200             
201             // Clear buffer.
202             buffer = nu_resize(buffer, 0, 1);
203             throw nogc_new!StreamReadException(stream, "UTF16 string did not have a BOM!");
204         }
205 
206         // Copy UTF-16 string out.
207         nwstring out_ = nwstring(nu_etoh!wchar(buffer, getEndianFromBOM(bom)));
208         nu_freea(buffer);
209         return out_;
210     }
211 
212     /**
213         Reads a UTF16 string from the stream.
214 
215         Params:
216             length = the length of the string to read, including BOM, if any.
217         
218         Returns:
219             The UTF16 string read from the stream.
220     */
221     nwstring readUTF16LE(uint length) @trusted {
222         wchar[] buffer = this.readEndianSeq!(wchar, Endianess.littleEndian)(length);
223         nwstring out_ = nwstring(buffer);
224         nu_freea(buffer);
225         return out_;
226     }
227 
228     /**
229         Reads a UTF16 string from the stream.
230 
231         Params:
232             length = the length of the string to read, including BOM, if any.
233         
234         Returns:
235             The UTF16 string read from the stream.
236     */
237     nwstring readUTF16BE(uint length) @trusted {
238         wchar[] buffer = this.readEndianSeq!(wchar, Endianess.bigEndian)(length);
239         nwstring out_ = nwstring(buffer);
240         nu_freea(buffer);
241         return out_;
242     }
243 
244     /**
245         Reads a UTF32 string from the stream.
246 
247         This function determines endianness from the byte-order-mark
248         in the stream; if none is found an exception is thrown.
249 
250         Params:
251             length = the length of the string to read, including BOM.
252         
253         Returns:
254             The UTF32 string read from the stream.
255     */
256     ndstring readUTF32(uint length) @trusted {
257         dchar[] buffer = this.readEndianSeq!(dchar, NATIVE_ENDIAN)(length);
258         codepoint bom = getBOM(buffer);
259 
260         if (!bom) {
261             
262             // Clear buffer.
263             buffer = nu_resize(buffer, 0, 1);
264             throw nogc_new!StreamReadException(stream, "UTF32 string did not have a BOM!");
265         }
266 
267         // Copy UTF-16 string out.
268         ndstring out_ = ndstring(nu_etoh!dchar(buffer, getEndianFromBOM(bom)));
269         nu_freea(buffer);
270         return out_;
271     }
272 
273     /**
274         Reads a UTF32 string from the stream.
275 
276         Params:
277             length = the length of the string to read, including BOM, if any.
278         
279         Returns:
280             The UTF32 string read from the stream.
281     */
282     ndstring readUTF32LE(uint length) @trusted {
283         dchar[] buffer = this.readEndianSeq!(dchar, Endianess.littleEndian)(length);
284         ndstring out_ = ndstring(buffer);
285         nu_freea(buffer);
286         return out_;
287     }
288 
289     /**
290         Reads a UTF32 string from the stream.
291 
292         Params:
293             length = the length of the string to read, including BOM, if any.
294         
295         Returns:
296             The UTF32 string read from the stream.
297     */
298     ndstring readUTF32BE(uint length) @trusted {
299         dchar[] buffer = this.readEndianSeq!(dchar, Endianess.bigEndian)(length);
300         ndstring out_ = ndstring(buffer);
301         buffer = nu_resize(buffer, 0, 1);
302         return out_;
303     }
304 }
305 
306 /**
307     A stream writer is a helper class which makes it easier to
308     write data to streams.
309 */
310 final
311 class StreamWriter : NuObject {
312 @nogc:
313 private:
314     Stream stream_;
315 
316     // Reads a sequence of bytes and ensures their endianness is correct.
317     // Additionally does a type-cast
318     pragma(inline, true)
319     void writeEndian(Endianess endian, T)(T value) @trusted if (__traits(isScalar, T)) {
320         import numem.core.traits : Unqual;
321         alias UT = Unqual!T;
322 
323         value = cast(T)nu_etoh!(T, endian)(cast(UT)value);
324         ubyte[] bufferView = (cast(ubyte*)&value)[0..T.sizeof];
325         stream.write(bufferView);
326     }
327 
328     // Reads a sequence of bytes and ensures their endianness is correct.
329     pragma(inline, true)
330     void writeEndianSeq(Endianess endian, T)(T[] values) @trusted if (__traits(isScalar, T)) {
331         import numem.core.traits : Unqual;
332         alias UT = Unqual!(T[]);
333 
334         auto buffer = cast(ubyte[])cast(void[])nu_etoh!(UT, endian)(cast(UT)values.nu_dup());
335         stream.write(buffer);
336         nu_freea(buffer);
337     }
338 
339 public:
340 @safe:
341 
342     /**
343         The stream this writer is writing to
344     */
345     @property Stream stream() => stream_;
346 
347     /**
348         Constructs a new stream writer
349     */
350     this(Stream stream) @trusted{
351         enforce(stream.canWrite(), nogc_new!StreamUnsupportedException(stream));
352         this.stream_ = stream;
353     }
354 
355     /**
356         Reads a value from the stream.
357         
358         Returns:
359             The read value.
360     */
361     void writeLE(T)(T value) { writeEndian!(Endianess.littleEndian)(value); }
362     void writeBE(T)(T value) { writeEndian!(Endianess.littleEndian)(value); }
363 
364     /**
365         Writes a UTF8 string to the stream.
366 
367         Params:
368             value = The string to write to the stream.
369     */
370     void writeUTF8(string value) @trusted {
371         stream.write(cast(ubyte[])value);
372     }
373 
374     /**
375         Writes a UTF16 string to the stream.
376 
377         Params:
378             value = The string to write to the stream.
379     */
380     void writeUTF16(wstring value) @trusted {
381         stream.write(cast(ubyte[])cast(void[])value);
382     }
383 
384     /**
385         Writes a UTF16 string to the stream.
386 
387         Params:
388             value = The string to write to the stream.
389     */
390     void writeUTF16LE(wstring value) @trusted {
391         this.writeEndianSeq!(Endianess.littleEndian)(value);
392     }
393 
394     /**
395         Writes a UTF16 string to the stream.
396 
397         Params:
398             value = The string to write to the stream.
399     */
400     void writeUTF16BE(wstring value) @trusted {
401         this.writeEndianSeq!(Endianess.bigEndian)(value);
402     }
403 
404     /**
405         Writes a UTF32 string to the stream.
406 
407         Params:
408             value = The string to write to the stream.
409     */
410     void writeUTF32(dstring value) @trusted {
411         stream.write(cast(ubyte[])cast(void[])value);
412     }
413 
414     /**
415         Writes a UTF32 string to the stream.
416 
417         Params:
418             value = The string to write to the stream.
419     */
420     void writeUTF32LE(dstring value) @trusted {
421         this.writeEndianSeq!(Endianess.littleEndian)(value);
422     }
423 
424     /**
425         Writes a UTF32 string to the stream.
426 
427         Params:
428             value = The string to write to the stream.
429     */
430     void writeUTF32BE(dstring value) @trusted {
431         this.writeEndianSeq!(Endianess.bigEndian)(value);
432     }
433 }