1 module mir.appender;
2 
3 // import std.traits: isAssignable, hasElaborateDestructorhasElaborateCopyConstructor, hasElaborateAssign;
4 
5 package void _mir_destroy(T)(T[] ar)
6 {
7     static if (__traits(hasMember, T, "__xdtor"))
8         foreach (ref e; ar)
9             static if (__traits(isSame, T, __traits(parent, e.__xdtor)))
10             {
11                 pragma(inline, false)
12                 e.__xdtor();
13             }
14 }
15 
16 private extern(C) @system nothrow @nogc pure void* memcpy(scope void* s1, scope const void* s2, size_t n);
17 
18 
19 ///
20 struct ScopedBuffer(T, size_t bytes = 4096)
21     if (bytes)
22 {
23     import std.traits: 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     private size_t _currentLength;
30     private align(T.alignof) ubyte[_bufferLength * T.sizeof] _scopeBufferPayload = void;
31 
32     private ref T[_bufferLength] _scopeBuffer() @trusted scope
33     {
34         return *cast(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                 _buffer = (cast(T*)malloc(T.sizeof * newLen))[0 .. newLen];
51                 memcpy(_buffer.ptr, _scopeBuffer.ptr, T.sizeof * (_currentLength - n));
52             }
53         }
54         else
55         if (_currentLength > _buffer.length)
56         {
57             auto newLen = _currentLength << 1;
58             _buffer = (cast(T*)realloc(_buffer.ptr, T.sizeof * newLen))[0 .. newLen];
59         }
60         return _buffer[0 .. _currentLength];
61     }
62 
63     static if (isAssignable!(T, const T))
64         private alias R = const T;
65     else
66         private alias R = T;
67 
68     @disable this(this);
69 
70     ~this()
71     {
72         import mir.internal.memory: free;
73         data._mir_destroy;
74         (() @trusted => free(_buffer.ptr))();
75     }
76 
77     void popBackN(size_t n)
78     {
79         sizediff_t t = _currentLength - n;
80         if (t < 0)
81             assert(0, "ScopedBffer.popBackN: n is too large.");
82             import mir.exception;
83         data[t .. _currentLength]._mir_destroy;
84         _currentLength = t;
85     }
86 
87     void put(R e) @safe scope
88     {
89         auto cl = _currentLength;
90         prepare(1);
91         emplaceRef(data[cl], e);
92     }
93 
94     static if (T.sizeof > 8 || hasElaborateAssign!T)
95     void put(ref R e) scope
96     {
97         auto cl = _currentLength;
98         auto d = prepare(1);
99         emplaceRef(d[cl], e);
100     }
101 
102     static if (!hasElaborateAssign!T && isAssignable!(T, const T))
103     void put(scope const(T)[] e) scope
104     {
105         auto cl = _currentLength;
106         auto d = prepare(e.length);
107         auto len = e.length * T.sizeof;
108         if (!__ctfe)
109             (()@trusted=>memcpy(d.ptr + cl, e.ptr, len))();
110         else
111             (()@trusted { (d.ptr + cl)[0 .. len] = e[0 .. len]; })();
112     }
113 
114     static if (!hasElaborateAssign!T && !isAssignable!(T, const T))
115     void put()(scope T[] e) scope
116     {
117         auto cl = _currentLength;
118         auto d = prepare(e.length);
119         auto len = e.length * T.sizeof;
120         if (!__ctfe)
121             (()@trusted=>memcpy(d.ptr + cl, e.ptr, len))();
122         else
123             (()@trusted { (d.ptr + cl)[0 .. len] = e[0 .. len]; })();
124     }
125 
126     void put(Iterable)(Iterable range) scope
127         if (isIterable!Iterable && !isArray!Iterable)
128     {
129         static if (hasLength!Iterable)
130         {
131             auto cl = _currentLength;
132             auto d = prepare(range.length);
133             static if (is(Iterable : R[]) && !hasElaborateAssign!T)
134             {
135                 auto len = range.length * T.sizeof;
136                 if (!__ctfe)
137                     (()@trusted=>memcpy(d.ptr + cl, e.ptr, len))();
138                 else
139                     (()@trusted { (d.ptr + cl)[0 .. len] = e[0 .. len]; })();
140             }
141             else
142             {
143                 foreach(ref e; range)
144                     emplaceRef(d[cl++], e);
145                 assert(_currentLength == cl);
146             }
147         }
148         else
149         {
150             foreach(ref e; range)
151                 put(e);
152         }
153     }
154 
155     void reset() scope nothrow
156     {
157         this.__dtor;
158         _currentLength = 0;
159         _buffer = null;
160     }
161 
162     T[] data() @property @safe scope
163     {
164         return _buffer.length ? _buffer[0 .. _currentLength] : _scopeBuffer[0 .. _currentLength];
165     }
166 }
167 
168 ///
169 @safe pure nothrow @nogc 
170 unittest
171 {
172     ScopedBuffer!char buf;
173     buf.put('c');
174     buf.put("str");
175     assert(buf.data == "cstr");
176 
177     buf.popBackN(2);
178     assert(buf.data == "cs");
179 }
180 
181 @safe pure nothrow @nogc 
182 unittest
183 {
184     ScopedBuffer!(char, 3) buf;
185     buf.put('c');
186     buf.put("str");
187     assert(buf.data == "cstr");
188 
189     buf.popBackN(2);
190     assert(buf.data == "cs");
191 }