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 }