The OpenD Programming Language

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