1 module bufref;
2 
3 import std.range.primitives;
4 
5 // arrays and random-access non-infinite ranges are buffers.
6 template isBuffer(B)
7 {
8     import std.range.primitives : isRandomAccessRange, isInfinite;
9     import std.traits : isDynamicArray;
10     enum isBuffer = isDynamicArray!B || (isRandomAccessRange!B && !isInfinite!B);
11 }
12 
13 struct BufRef
14 {
15     size_t pos;
16     size_t length;
17 }
18 
19 enum isBufRef(alias item) = is(typeof(item) == BufRef);
20 enum isBufRefRange(alias item) = isInputRange!(typeof(item)) && is(ElementType!(typeof(item)) == BufRef);
21 
22 template hasBufRefs(T)
23 {
24     // type has at least one bufRef item as a member
25     static if(is(T == struct))
26     {
27         import std.meta : anySatisfy;
28         enum hasBufRefs = anySatisfy!(isBufRef, T.tupleof) || anySatisfy!(isBufRefRange, T.tupleof);
29     }
30     else
31         enum hasBufRefs = false;
32 }
33 
34 
35 unittest
36 {
37     static struct S1
38     {
39         string member1;
40         BufRef member2;
41         int member3;
42     }
43 
44     static assert(hasBufRefs!S1);
45 
46     static struct S2
47     {
48         int member1;
49         string member2;
50     }
51 
52     static assert(!hasBufRefs!S2);
53 
54     static struct S3
55     {
56         BufRef[] arr;
57     }
58 
59     static assert(hasBufRefs!S3);
60 }
61 
62 // Wrapper for a BufRef range that exposes elements as concrete buffer slices.
63 // Does not make any copies of items.
64 struct ConcreteRange(R, W) if (isInputRange!R && is(ElementType!R == BufRef) && isBuffer!W)
65 {
66     R r;
67     W w;
68     auto front()
69     {
70         return r.front.concrete(w);
71     }
72 
73     void popFront()
74     {
75         r.popFront;
76     }
77 
78     bool empty()
79     {
80         return r.empty;
81     }
82     
83     static if(isForwardRange!R)
84     {
85         ConcreteRange save()
86         {
87             return ConcreteRange(r.save, w);
88         }
89     }
90 
91     static if(isBidirectionalRange!R)
92     {
93         auto back()
94         {
95             return r.back.concrete(w);
96         }
97 
98         void popBack()
99         {
100             r.popBack;
101         }
102     }
103 
104     static if(hasLength!R)
105     {
106         size_t length()
107         {
108             return r.length;
109         }
110 
111         auto opDollar(size_t dim)() if (dim == 0)
112         {
113             return length;
114         }
115     }
116 
117     static if(is(typeof(r[0])))
118     {
119         auto opIndex(size_t idx)
120         {
121             return r[idx].concrete(w);
122         }
123     }
124 
125     static if(hasSlicing!R)
126     {
127         size_t[2] opSlice(size_t dim)(size_t start, size_t end) if (dim == 0)
128         {
129             return [start, end];
130         }
131 
132         auto opIndex(size_t[2] idx)
133         {
134             return ConcreteRange(r[idx[0] .. idx[1]], w);
135         }
136     }
137 }
138 
139 // get a concrete represenation of the buffer reference
140 auto concrete(B)(const(BufRef) bref, B window) if (isBuffer!B)
141 {
142     return window[bref.pos .. bref.pos + bref.length];
143 }
144 
145 // get a concrete range from a range of buffer references.
146 auto concrete(R, B)(R brefRange, B window) if (isBufRefRange!brefRange && isBuffer!B)
147 {
148     return ConcreteRange!(R, B)(brefRange, window);
149 }
150 
151 // get a concrete representation of an item that contains buffer references.
152 // DOES NOT copy functions.
153 auto concrete(T, B)(auto ref T item, B window) if (hasBufRefs!T && isBuffer!B)
154 {
155     BufRef test;
156     alias SliceType = typeof(BufRef.init.concrete(window));
157     static string resultstr()
158     {
159         string result = "static struct Concrete" ~ T.stringof ~ " {";
160         static foreach(i; 0 .. T.tupleof.length)
161         {{
162              alias TE = typeof(T.tupleof[i]);
163              static if(is(TE == BufRef))
164              {
165                  // replace with the slice type
166                  result ~= "SliceType " ~ __traits(identifier, T.tupleof[i]) ~ ";";
167              }
168              else static if(isInputRange!TE && is(ElementType!TE == BufRef))
169              {
170                  result ~= "ConcreteRange!( " ~ TE.stringof ~ ",B) " ~ __traits(identifier, T.tupleof[i]) ~ ";";
171              }
172              else
173              {
174                  result ~= typeof(T.tupleof[i]).stringof ~ " " ~ __traits(identifier, T.tupleof[i]) ~ ";";
175              }
176         }}
177         result ~= "} alias Result = Concrete" ~ T.stringof ~ ";";
178         return result;
179     }
180     mixin(resultstr());
181     Result r;
182     foreach(i, ref it; item.tupleof)
183     {
184         static if(isBufRef!(item.tupleof[i]) || isBufRefRange!(item.tupleof[i]))
185         {
186             r.tupleof[i] = it.concrete(window);
187         }
188         else
189         {
190             r.tupleof[i] = it;
191         }
192     }
193     return r;
194 }
195 
196 unittest
197 {
198     static struct S
199     {
200         int i;
201         BufRef b;
202         long l;
203         BufRef[] barr;
204     }
205     S s;
206     s.b = BufRef(0, 10);
207     s.barr = [BufRef(0, 2), BufRef(1, 2), BufRef(2, 2)];
208     auto c = s.concrete("0123456789012345");
209     assert(c.b == "0123456789");
210     assert(c.barr.length == 3);
211     assert(c.barr[0] == "01");
212     assert(c.barr[1] == "12");
213     assert(c.barr[2] == "23");
214     assert(c.barr[1 .. $][0] == "12");
215 }
216 
217 // adjust the pos of the reference inside the buffer.
218 void adjustPos(ref BufRef bref, ptrdiff_t adjustment)
219 {
220     assert(cast(ptrdiff_t)(bref.pos + adjustment) >= 0);
221     bref.pos += adjustment;
222 }
223 
224 // adjust the pos of all the buffer references inside the item.
225 void adjustPos(T)(ref T item, ptrdiff_t adjustment) if (hasBufRefs!T)
226 {
227     foreach(i, ref it; item.tupleof)
228     {
229         static if(is(typeof(it) == BufRef))
230         {
231             it.adjustPos(adjustment);
232         }
233         else static if(is(typeof(it[]) == BufRef[]))
234         {
235             foreach(ref br; it[])
236                 br.adjustPos(adjustment);
237         }
238         else static if(isBufRefRange!(item.tupleof[i]) && isForwardRange!(typeof(it)) && hasLvalueElements!(typeof(it)))
239         {
240             foreach(ref br; it.save)
241                 br.adjustPos(adjustment);
242         }
243     }
244 }
245 
246 // a buffer window. This wraps a buffer into a range that knows what position
247 // it's at in the original window.
248 auto bwin(B)(B window) if (isBuffer!B)
249 {
250     static struct Result
251     {
252         private size_t pos;
253         private B _buffer;
254 
255         import std.traits : isNarrowString;
256         static if(isNarrowString!B)
257         {
258             auto ref front()
259             {
260                 return _buffer[0];
261             }
262 
263             void popFront()
264             {
265                 ++pos;
266                 _buffer = _buffer[1 .. $];
267             }
268             
269             auto ref back()
270             {
271                 return _buffer[$-1];
272             }
273 
274             void popBack()
275             {
276                 _buffer = _buffer[0 .. $-1];
277             }
278         }
279         else
280         {
281             auto ref front()
282             {
283                 return _buffer.front;
284             }
285 
286             void popFront()
287             {
288                 ++pos;
289                 _buffer.popFront;
290             }
291             
292             auto ref back()
293             {
294                 return _buffer.back;
295             }
296 
297             void popBack()
298             {
299                 _buffer.popBack;
300             }
301         }
302 
303         size_t length()
304         {
305             return _buffer.length;
306         }
307 
308         auto ref opIndex(size_t idx)
309         {
310             return _buffer[idx];
311         }
312 
313         // implement the slice operations
314         size_t[2] opSlice(size_t dim)(size_t start, size_t end) if (dim == 0)
315         in
316         { assert(start >= 0 && end <= _buffer.length); }
317         do
318         {
319             return [start, end];
320         }
321 
322         Result opIndex(size_t[2] dims)
323         {
324             return Result(pos + dims[0], _buffer[dims[0] .. dims[1]]);
325         }
326 
327         Result save()
328         {
329             return this;
330         }
331 
332         bool empty()
333         {
334             return _buffer.empty;
335         }
336 
337         // the specialized buffer reference accessor.
338         @property auto bufRef()
339         {
340             return BufRef(pos, _buffer.length);
341         }
342     }
343 
344     return Result(0, window);
345 }
346 
347 unittest
348 {
349     import std.algorithm : splitter, equal;
350     auto buf = "hi there this is a sentence";
351     auto split1 = buf.bwin.splitter(" ");
352 
353     auto split2 = buf.splitter;
354     while(!split1.empty)
355     {
356         assert(split1.front.equal(split2.front));
357         assert(split1.front.bufRef.concrete(buf) == split2.front);
358         split1.popFront;
359         split2.popFront;
360     }
361 }