1 module tame.buffer;
2 
3 enum maxAlloca = 2048;
4 
5 /*
6  *
7  * Fixed maximum number of items on the stack. Memory is a static stack buffer.
8  * This buffer can be filled up and cleared for reuse.
9  *
10  */
11 
12 struct FixedBuffer(size_t LEN, T = char) if (T.sizeof == 1) {
13 	invariant (pos <= LEN);
14 
15 	alias OutputFunc = void delegate(in T[]) @nogc;
16 	T[LEN] buf = void;
17 	alias buf this;
18 	size_t pos;
19 	OutputFunc outputFunc;
20 
21 	pure @nogc nothrow @safe {
22 		/// constructor
23 		this(in T[] rhs) {
24 			this = rhs;
25 		}
26 
27 		@property bool empty() const {
28 			return pos == 0;
29 		}
30 
31 		@property T[] data() {
32 			return buf[0 .. pos];
33 		}
34 
35 		void clear() {
36 			pos = 0;
37 		}
38 	}
39 
40 	/// ditto
41 	this(F)(F oFunc) if (is(typeof(oFunc(null)))) {
42 		outputFunc = cast(OutputFunc)oFunc;
43 	}
44 
45 	T opCast(T : bool)() const {
46 		return !empty();
47 	}
48 
49 	T opCast(T)() const if (!is(T : bool)) {
50 		return cast(T)buf[0 .. pos];
51 	}
52 
53 	/// assignment
54 	auto opAssign(in T[] rhs)
55 	in (rhs.length <= LEN) {
56 		pos = rhs.length;
57 		buf[0 .. pos] = rhs;
58 		return rhs;
59 	}
60 
61 	/// append
62 	auto opOpAssign(string op : "~", S)(S rhs) if (S.sizeof == 1) {
63 		if (pos == LEN) {
64 			outputFunc(buf[]);
65 			pos = 0;
66 		}
67 		buf[pos++] = cast(T)rhs;
68 	}
69 
70 	/// ditto
71 	auto opOpAssign(string op : "~", S)(ref S rhs) if (S.sizeof > 1) {
72 		this ~= (cast(T*)&rhs)[0 .. S.sizeof];
73 	}
74 
75 	/// ditto
76 	auto ref opOpAssign(string op : "~")(in void[] rhs) {
77 		import core.stdc.string;
78 
79 		auto s = cast(void[])rhs;
80 		auto remain = pos + s.length;
81 		for (;;) {
82 			auto outlen = remain < LEN ? remain : LEN;
83 			outlen -= pos;
84 			memcpy(buf.ptr + pos, s.ptr, outlen);
85 			s = s[outlen .. $];
86 			if (outlen + pos != LEN)
87 				break;
88 			pos = 0;
89 			remain -= LEN;
90 			outputFunc(buf[]);
91 		}
92 		pos = remain;
93 		return this;
94 	}
95 
96 	alias length = pos;
97 
98 	auto flush() {
99 		if (!pos)
100 			return false;
101 		outputFunc(buf[0 .. pos]);
102 		clear();
103 		return true;
104 	}
105 }
106 
107 import core.stdc.stdlib;
108 
109 struct TempBuffer(T) {
110 	T[] slice;
111 	bool callFree;
112 
113 	@disable this(this);
114 
115 	~this() nothrow {
116 		if (callFree)
117 			free(slice.ptr);
118 	}
119 
120 pure nothrow @safe:
121 	//dfmt off
122 	T[] opSlice() { return slice[]; }
123 	T[] opSlice(size_t a, size_t b) { return slice[a .. b]; }
124 	T[] opSliceAssign(const(T)[] value, size_t a, size_t b) { return slice[a .. b] = value; }
125 	ref T opIndex(size_t idx) { return slice[idx]; }
126 	@property size_t size() { return T.sizeof * slice.length; }
127 	@property size_t length() { return slice.length; }
128 	alias opDollar = length;
129 	@property T* ptr() @trusted { return slice.ptr; } // must use .ptr here for zero length strings
130 
131 	alias ptr this;
132 
133 	auto makeOutputRange() {
134 		struct OutputRange {
135 			T* ptr;
136 			size_t idx;
137 
138 			void put(T)(auto ref T t) { ptr[idx++] = t; }
139 
140 			T[] opSlice() { return ptr[0 .. idx]; }
141 		}
142 
143 		return OutputRange(slice.ptr, 0);
144 	}
145 	//dfmt on
146 }
147 
148 TempBuffer!T tempBuffer(T, alias length, size_t maxAlloca = .maxAlloca)(
149 	void* buffer = (T.sizeof * length <= maxAlloca) ? alloca(T.sizeof * length) : null) {
150 	return TempBuffer!T((cast(T*)(
151 			buffer ? buffer
152 			: malloc(T.sizeof * length)))[0 .. length],
153 		buffer is null);
154 }
155 
156 /*
157  *
158  * Returns a structure to your stack that contains a buffer of $(D size) size.
159  * Memory is allocated by calling `.alloc!T(count)` on it in order to get
160  * `count` elements of type `T`. The return value will be a RAII structure
161  * that releases the memory back to the stack buffer upon destruction, so it can
162  * be reused. The pointer within that RAII structure is aligned to
163  * `T.alignof`. If the internal buffer isn't enough to fulfill the request
164  * including padding from alignment, then `malloc()` is used instead.
165  *
166  * Warning:
167  *   Always keep the return value of `.alloc()` around on your stack until
168  *   you are done with its contents. Never pass it directly into functions as
169  *   arguments!
170  *
171  * Params:
172  *   size = The size of the buffer on the stack.
173  *
174  * Returns:
175  *   A stack buffer allocator.
176  *
177  */
178 auto stackBuffer(size_t size)() @trusted {
179 	// All that remains of this after inlining is a stack pointer decrement and
180 	// a mov instruction for the `null`.
181 	StackBuffer!size buf = void;
182 	buf.last = cast(StackBufferEntry!void*)&buf.last;
183 	buf.sentinel = null;
184 	return buf;
185 }
186 
187 auto asOutputRange(T)(T* t) {
188 	struct PointerRange {
189 		private T* start, ptr;
190 
191 		void put()(auto ref const(T) t) {
192 			*ptr++ = t;
193 		}
194 
195 		T[] opSlice() pure {
196 			return start[0 .. ptr - start];
197 		}
198 	}
199 
200 	static assert(isOutputRange!(PointerRange, T));
201 	return PointerRange(t, t);
202 }
203 
204 package:
205 
206 struct StackBuffer(size_t size) {
207 private:
208 
209 	void[size] space = void;
210 	StackBufferEntry!void* last;
211 	void* sentinel;
212 
213 public:
214 
215 	@disable this(this);
216 
217 	@trusted
218 	StackBufferEntry!T alloc(T)(size_t howMany) {
219 		enum max = size_t.max / T.sizeof;
220 		alias SBE = StackBufferEntry!T;
221 		T* target = cast(T*)(cast(uintptr_t)last.ptr / T.alignof * T.alignof);
222 		if (target > space.ptr && cast(uintptr_t)(target - cast(T*)space.ptr) >= howMany)
223 			return SBE(target - howMany, last);
224 		else // TODO: Respect alignment here as well by padding. Optionally also embed a length in the heap block, so we can provide slicing of the whole thing.
225 			return SBE(howMany <= max ? cast(T*)malloc(T.sizeof * howMany) : null);
226 	}
227 }
228 
229 struct StackBufferEntry(T) {
230 private:
231 
232 	StackBufferEntry!void* prev;
233 
234 	this(T* ptr) {
235 		this.ptr = ptr;
236 	}
237 
238 	this(T* ptr, ref StackBufferEntry!void* last) {
239 		this(ptr);
240 		prev = last;
241 		last = cast(StackBufferEntry!void*)&this;
242 	}
243 
244 public:
245 
246 	T* ptr;
247 
248 	static if (!is(T == void)) {
249 		@disable this(this);
250 
251 		~this() @trusted {
252 			if (prev) {
253 				StackBufferEntry!void* it = prev;
254 				while (it.prev)
255 					it = it.prev;
256 				auto last = cast(StackBufferEntry!void**)&prev.ptr;
257 				*last = prev;
258 			} else
259 				free(ptr);
260 		}
261 
262 	pure nothrow @nogc:
263 		ref inout(T) opIndex(size_t idx) @system inout {
264 			return ptr[idx];
265 		}
266 
267 		inout(T)[] opSlice(size_t a, size_t b) @system inout {
268 			return ptr[a .. b];
269 		}
270 
271 		@property auto range() @safe {
272 			return ptr.asOutputRange();
273 		}
274 	}
275 }