1 /** 2 Utility functions for array processing 3 4 Copyright: © 2012 Sönke Ludwig 5 License: Subject to the terms of the MIT license, as written in the included LICENSE.txt file. 6 Authors: Sönke Ludwig 7 */ 8 module dutils.data.utils.array; 9 10 import dutils.data.utils.utilallocator; 11 12 import std.algorithm; 13 import std.range : isInputRange, isOutputRange; 14 import std.traits; 15 static import std.utf; 16 17 enum AppenderResetMode { 18 keepData, 19 freeData, 20 reuseData 21 } 22 23 struct AllocAppender(ArrayType : E[], E) { 24 alias ElemType = Unqual!E; 25 26 static assert(!hasIndirections!E && !hasElaborateDestructor!E); 27 28 private { 29 ElemType[] m_data; 30 ElemType[] m_remaining; 31 IAllocator m_alloc; 32 bool m_allocatedBuffer = false; 33 } 34 35 this(IAllocator alloc, ElemType[] initial_buffer = null) { 36 m_alloc = alloc; 37 m_data = initial_buffer; 38 m_remaining = initial_buffer; 39 } 40 41 @disable this(this); 42 43 @property ArrayType data() { 44 return cast(ArrayType) m_data[0 .. m_data.length - m_remaining.length]; 45 } 46 47 void reset(AppenderResetMode reset_mode = AppenderResetMode.keepData) { 48 if (reset_mode == AppenderResetMode.keepData) 49 m_data = null; 50 else if (reset_mode == AppenderResetMode.freeData) { 51 if (m_allocatedBuffer) 52 m_alloc.deallocate(m_data); 53 m_data = null; 54 } 55 m_remaining = m_data; 56 } 57 58 /** Grows the capacity of the internal buffer so that it can hold a minumum amount of elements. 59 60 Params: 61 amount = The minimum amount of elements that shall be appendable without 62 triggering a re-allocation. 63 64 */ 65 void reserve(size_t amount) @trusted { 66 size_t nelems = m_data.length - m_remaining.length; 67 if (!m_data.length) { 68 m_data = cast(ElemType[]) m_alloc.allocate(amount * E.sizeof); 69 m_remaining = m_data; 70 m_allocatedBuffer = true; 71 } 72 if (m_remaining.length < amount) { 73 debug { 74 import std.digest.crc; 75 76 auto checksum = crc32Of(m_data[0 .. nelems]); 77 } 78 if (m_allocatedBuffer) { 79 void[] vdata = m_data; 80 m_alloc.reallocate(vdata, (nelems + amount) * E.sizeof); 81 m_data = () @trusted { return cast(ElemType[]) vdata; }(); 82 } else { 83 auto newdata = cast(ElemType[]) m_alloc.allocate((nelems + amount) * E.sizeof); 84 newdata[0 .. nelems] = m_data[0 .. nelems]; 85 m_data = newdata; 86 m_allocatedBuffer = true; 87 } 88 debug assert(crc32Of(m_data[0 .. nelems]) == checksum); 89 } 90 m_remaining = m_data[nelems .. m_data.length]; 91 } 92 93 void put(E el) @safe { 94 if (m_remaining.length == 0) 95 grow(1); 96 m_remaining[0] = el; 97 m_remaining = m_remaining[1 .. $]; 98 } 99 100 void put(ArrayType arr) @safe { 101 if (m_remaining.length < arr.length) 102 grow(arr.length); 103 m_remaining[0 .. arr.length] = arr[]; 104 m_remaining = m_remaining[arr.length .. $]; 105 } 106 107 static if (!hasAliasing!E) { 108 void put(in ElemType[] arr) @trusted { 109 put(cast(ArrayType) arr); 110 } 111 } 112 113 static if (is(ElemType == char)) { 114 void put(dchar el) @safe { 115 if (el < 128) 116 put(cast(char) el); 117 else { 118 char[4] buf; 119 auto len = std.utf.encode(buf, el); 120 put(() @trusted { return cast(ArrayType) buf[0 .. len]; }()); 121 } 122 } 123 } 124 125 static if (is(ElemType == wchar)) { 126 void put(dchar el) @safe { 127 if (el < 128) 128 put(cast(wchar) el); 129 else { 130 wchar[3] buf; 131 auto len = std.utf.encode(buf, el); 132 put(() @trusted { return cast(ArrayType) buf[0 .. len]; }()); 133 } 134 } 135 } 136 137 static if (!is(E == immutable) || !hasAliasing!E) { 138 /** Appends a number of bytes in-place. 139 140 The delegate will get the memory slice of the memory that follows 141 the already written data. Use `reserve` to ensure that this slice 142 has enough room. The delegate should overwrite as much of the 143 slice as desired and then has to return the number of elements 144 that should be appended (counting from the start of the slice). 145 */ 146 void append(scope size_t delegate(scope ElemType[] dst) @safe del) { 147 auto n = del(m_remaining); 148 assert(n <= m_remaining.length); 149 m_remaining = m_remaining[n .. $]; 150 } 151 } 152 153 void grow(size_t min_free) { 154 if (!m_data.length && min_free < 16) 155 min_free = 16; 156 157 auto min_size = m_data.length + min_free - m_remaining.length; 158 auto new_size = max(m_data.length, 16); 159 while (new_size < min_size) 160 new_size = (new_size * 3) / 2; 161 reserve(new_size - m_data.length + m_remaining.length); 162 } 163 } 164 165 unittest { 166 auto a = AllocAppender!string(theAllocator()); 167 a.put("Hello"); 168 a.put(' '); 169 a.put("World"); 170 assert(a.data == "Hello World"); 171 a.reset(); 172 assert(a.data == ""); 173 } 174 175 unittest { 176 char[4] buf; 177 auto a = AllocAppender!string(theAllocator(), buf); 178 a.put("He"); 179 assert(a.data == "He"); 180 assert(a.data.ptr == buf.ptr); 181 a.put("ll"); 182 assert(a.data == "Hell"); 183 assert(a.data.ptr == buf.ptr); 184 a.put('o'); 185 assert(a.data == "Hello"); 186 assert(a.data.ptr != buf.ptr); 187 } 188 189 unittest { 190 char[4] buf; 191 auto a = AllocAppender!string(theAllocator(), buf); 192 a.put("Hello"); 193 assert(a.data == "Hello"); 194 assert(a.data.ptr != buf.ptr); 195 } 196 197 unittest { 198 auto app = AllocAppender!(int[])(theAllocator); 199 app.reserve(2); 200 app.append((scope mem) { 201 assert(mem.length >= 2); 202 mem[0] = 1; 203 mem[1] = 2; 204 return size_t(2); 205 }); 206 assert(app.data == [1, 2]); 207 } 208 209 unittest { 210 auto app = AllocAppender!string(theAllocator); 211 app.reserve(3); 212 app.append((scope mem) { 213 assert(mem.length >= 3); 214 mem[0] = 'f'; 215 mem[1] = 'o'; 216 mem[2] = 'o'; 217 return size_t(3); 218 }); 219 assert(app.data == "foo"); 220 }