1 /++
2 $(H1 Scoped Buffer)
3 
4 License: $(HTTP www.apache.org/licenses/LICENSE-2.0, Apache-2.0)
5 Authors: Ilya Yaroshenko
6 +/
7 module mir.appender;
8 
9 // import std.traits: isAssignable, hasElaborateDestructorhasElaborateCopyConstructor, hasElaborateAssign;
10 import mir.conv: _mir_destroy = xdestroy;
11 
12 private extern(C) @system nothrow @nogc pure void* memcpy(scope void* s1, scope const void* s2, size_t n);
13 
14 
15 ///
16 struct ScopedBuffer(T, size_t bytes = 4096)
17     if (bytes && T.sizeof <= bytes)
18 {
19     import std.traits: Unqual, isMutable, isIterable, hasElaborateAssign, isAssignable, isArray;
20     import mir.primitives: hasLength;
21     import mir.conv: emplaceRef;
22 
23     private enum size_t _bufferLength =  bytes / T.sizeof + (bytes % T.sizeof != 0);
24     private T[] _buffer;
25     private size_t _currentLength;
26 
27     version (mir_secure_memory)
28         private align(T.alignof) ubyte[_bufferLength * T.sizeof] _scopeBufferPayload;
29     else
30         private align(T.alignof) ubyte[_bufferLength * T.sizeof] _scopeBufferPayload = void;
31 
32     private ref inout(T[_bufferLength]) _scopeBuffer() inout @trusted scope
33     {
34         return *cast(inout(T[_bufferLength])*)&_scopeBufferPayload;
35     }
36 
37     private T[] prepare(size_t n) @trusted scope
38     {
39         import mir.internal.memory: realloc, malloc;
40         _currentLength += n;
41         if (_buffer.length == 0)
42         {
43             if (_currentLength <= _bufferLength)
44             {
45                 return _scopeBuffer[0 .. _currentLength];
46             }
47             else
48             {
49                 auto newLen = _currentLength << 1;
50                 if (auto p = malloc(T.sizeof * newLen))
51                 {
52                     _buffer = (cast(T*)p)[0 .. newLen];
53                 }
54                 else assert(0);
55                 version (mir_secure_memory)
56                 {
57                     (cast(ubyte[])_buffer)[] = 0;
58                 }
59                 memcpy(cast(void*)_buffer.ptr, _scopeBuffer.ptr, T.sizeof * (_currentLength - n));
60             }
61         }
62         else
63         if (_currentLength > _buffer.length)
64         {
65             auto newLen = _currentLength << 1;
66             _buffer = (cast(T*)realloc(cast(void*)_buffer.ptr, T.sizeof * newLen))[0 .. newLen];
67             version (mir_secure_memory)
68             {
69                 (cast(ubyte[])_buffer[_currentLength .. $])[] = 0;
70             }
71         }
72         return _buffer[0 .. _currentLength];
73     }
74 
75     static if (isAssignable!(T, const T))
76         private alias R = const T;
77     else
78         private alias R = T;
79 
80     /// Copy constructor is enabled only if `T` is mutable type without eleborate assign.
81     static if (isMutable!T && !hasElaborateAssign!T)
82     this(this)
83     {
84         import mir.internal.memory: malloc;
85         if (_buffer.ptr)
86         {
87             typeof(_buffer) buffer;
88             if (auto p = malloc(T.sizeof * _buffer.length))
89             {
90                 buffer = (cast(T*)p)[0 .. T.sizeof * _buffer.length];
91             }
92             else assert(0);
93             version (mir_secure_memory)
94             {
95                 (cast(ubyte[])buffer)[] = 0;
96             }
97             buffer[0 .. _currentLength] = _buffer[0 .. _currentLength];
98             _buffer = buffer;
99         }
100     }
101     else
102     @disable this(this);
103 
104     ///
105     ~this()
106     {
107         import mir.internal.memory: free;
108         data._mir_destroy;
109         version(mir_secure_memory)
110             _currentLength = 0;
111         (() @trusted { if (_buffer.ptr) free(cast(void*)_buffer.ptr); })();
112     }
113 
114     ///
115     void shrinkTo(size_t length)
116     {
117         assert(length <= _currentLength);
118         data[length .. _currentLength]._mir_destroy;
119         _currentLength = length;
120     }
121 
122     ///
123     size_t length() scope const @property
124     {
125         return _currentLength;
126     }
127 
128     ///
129     void popBackN(size_t n)
130     {
131         sizediff_t t = _currentLength - n;
132         if (t < 0)
133             assert(0, "ScopedBffer.popBackN: n is too large.");
134         data[t .. _currentLength]._mir_destroy;
135         _currentLength = t;
136     }
137 
138     ///
139     void put(T e) @safe scope
140     {
141         auto cl = _currentLength;
142         auto d = prepare(1);
143         static if (isMutable!T)
144         {
145             import core.lifetime: moveEmplace;
146             ()@trusted{moveEmplace(e, d[cl]);}();
147         }
148         else
149         {
150             emplaceRef!(Unqual!T)(d[cl], e);
151         }
152     }
153 
154     static if (T.sizeof > 8 || hasElaborateAssign!T)
155     ///
156     void put(ref R e) scope
157     {
158         auto cl = _currentLength;
159         auto d = prepare(1);
160         emplaceRef!(Unqual!T)(d[cl], e);
161     }
162 
163     static if (!hasElaborateAssign!T)
164     ///
165     void put(scope R[] e) scope
166     {
167         auto cl = _currentLength;
168         auto d = prepare(e.length);
169         if (!__ctfe)
170             (()@trusted=>memcpy(cast(void*)(d.ptr + cl), e.ptr, e.length * T.sizeof))();
171         else
172             static if (isMutable!T)
173                 (()@trusted=> d[cl .. cl + e.length] = e)();
174             else
175                 assert(0);
176     }
177 
178     ///
179     void put(Iterable)(Iterable range) scope
180         if (isIterable!Iterable && !__traits(isStaticArray, Iterable) && (!isArray!Iterable || hasElaborateAssign!T))
181     {
182         static if (hasLength!Iterable)
183         {
184             auto cl = _currentLength;
185             auto d = prepare(range.length);
186             foreach(ref e; range)
187                 emplaceRef!(Unqual!T)(d[cl++], e);
188             assert(_currentLength == cl);
189         }
190         else
191         {
192             foreach(ref e; range)
193                 put(e);
194         }
195     }
196 
197     ///
198     void reset() scope nothrow
199     {
200         this.__dtor;
201         _currentLength = 0;
202         _buffer = null;
203     }
204 
205     ///
206     void initialize() @system scope nothrow @nogc
207     {
208         _currentLength = 0;
209         _buffer = null;
210     }
211 
212     ///
213     inout(T)[] data() inout @property @safe scope
214     {
215         return _buffer.length ? _buffer[0 .. _currentLength] : _scopeBuffer[0 .. _currentLength];
216     }
217 
218     /++
219     Copies data into an array of the same length using `memcpy` C routine.
220     Shrinks the length to `0`.
221     +/
222     void moveDataAndEmplaceTo(T[] array) @system
223     in {
224         assert(array.length == _currentLength);
225     }
226     do {
227         memcpy(cast(void*)array.ptr, data.ptr, _currentLength * T.sizeof);
228         _currentLength = 0;
229     }
230 }
231 
232 /// ditto
233 auto scopedBuffer(T, size_t bytes = 4096)() @trusted
234 {
235     ScopedBuffer!(T, bytes) buffer = void;
236     buffer.initialize;
237     return buffer;
238 }
239 
240 ///
241 @safe pure nothrow @nogc
242 version (mir_test) unittest
243 {
244     auto buf = scopedBuffer!char;
245     buf.put('c');
246     buf.put("str");
247     assert(buf.data == "cstr");
248 
249     buf.popBackN(2);
250     assert(buf.data == "cs");
251 }
252 
253 /// immutable
254 @safe pure nothrow @nogc
255 version (mir_test) unittest
256 {
257     auto buf = scopedBuffer!(immutable char);
258     buf.put('c');
259     buf.put("str");
260     assert(buf.data == "cstr");
261 
262     buf.popBackN(2);
263     assert(buf.data == "cs");
264 }
265 
266 @safe pure nothrow @nogc
267 version (mir_test) unittest
268 {
269     auto buf = scopedBuffer!(char, 3);
270     buf.put('c');
271     buf.put("str");
272     assert(buf.data == "cstr");
273 
274     buf.popBackN(2);
275     assert(buf.data == "cs");
276 }
277 
278 ///
279 struct UnsafeArrayBuffer(T)
280 {
281     import std.traits: isImplicitlyConvertible;
282 
283     ///
284     T[] buffer;
285     ///
286     size_t length;
287 
288     ///
289     void put(T a)
290     {
291         import core.lifetime: move;
292         assert(length < buffer.length);
293         buffer[length++] = move(a);
294     }
295 
296     static if (isImplicitlyConvertible!(const T, T))
297         private alias E = const T;
298     else
299         private alias E = T;
300 
301     ///
302     void put(E[] a)
303     {
304         import core.lifetime: move;
305         assert(buffer.length >= a.length + length);
306         buffer[length .. length + a.length] = a;
307         length += a.length;
308     }
309 
310     ///
311     inout(T)[] data() inout @property @safe scope
312     {
313         return buffer[0 .. length];
314     }
315 
316     ///
317     void popBackN(size_t n)
318     {
319         sizediff_t t = length - n;
320         if (t < 0)
321             assert(0, "UnsafeBuffer.popBackN: n is too large.");
322         buffer[t .. length]._mir_destroy;
323         length = t;
324     }
325 }
326 
327 ///
328 @safe pure nothrow @nogc
329 version (mir_test) unittest
330 {
331     char[4] array;
332     auto buf = UnsafeArrayBuffer!char(array);
333     buf.put('c');
334     buf.put("str");
335     assert(buf.data == "cstr");
336 
337     buf.popBackN(2);
338     assert(buf.data == "cs");
339 }
340 
341 version(mir_bignum_test) // for DIP1000
342 @safe pure nothrow
343 unittest
344 {
345     import mir.conv: to;
346     import mir.algebraic : Algebraic;
347     static struct S
348     {
349     @safe pure nothrow @nogc:
350         @property string toString() const
351         {
352             return "_";
353         }
354     }
355     Algebraic!(int, string, double) x;
356     x = 42;
357     auto s = x.to!string;
358     assert(s == "42", s);
359     x = "abc";
360     assert(x.to!string == "abc");
361     x = 42.0;
362     assert(x.to!string == "42.0");
363     Algebraic!S y;
364     y = S();
365     assert(y.to!string == "_");
366 }