1 //Written in the D programming language
2 /*
3  * Some rountines for use with ranges
4  *
5  * Copyright (C) 2013-6 Jaypha
6  *
7  * Distributed under the Boost Software License, Version 1.0.
8  * (See http://www.boost.org/LICENSE_1_0.txt)
9  *
10  * Authors: Jason den Dulk
11  */
12 
13 module jaypha.range;
14 
15 import std.algorithm;
16 import std.range.primitives;
17 import std.traits;
18 import std.exception;
19 
20 enum defaultBufferSize = 8192;
21 
22 public import jaypha.algorithm : munch;
23 
24 //----------------------------------------------------------------------------
25 // Consume the rest of the range.
26 
27 void drain(R)(ref R r) if (isInputRange!R)
28 {
29   while (!r.empty) r.popFront();
30 }
31 
32 //----------------------------------------------------------------------------
33 // Splits a range into chunks of given size. Doesn't work with narrow
34 // strings (It might split a character in the middle).
35 
36 struct ByChunk(R) if (isInputRange!R && !isNarrowString!R)
37 {
38   alias Unqual!(ElementType!R) E;
39 
40   this(R range, size_t chunkSize = defaultBufferSize)
41   {
42     front.length = chunkSize;
43     rng = range;
44     num = chunkSize;
45     popFront();
46   }
47 
48   E[] front;
49 
50   bool empty = false;
51 
52   void popFront()
53   {
54     if (empty) return;
55     if (rng.empty)
56     {
57       empty = true;
58     }
59     else
60     {
61       front = new E[num];
62       foreach (i; 0..num)
63       {
64         if (rng.empty) { front.length = i; break; }
65         front[i] = rng.front;
66         rng.popFront();
67       }
68     }
69   }
70 
71   private:
72     R rng;
73     size_t num;
74 }
75 
76 ByChunk!R byChunk(R)(R range, size_t chunkSize)
77 {
78   return ByChunk!R(range,chunkSize);
79 }
80 
81 //----------------------------------------------------------------------------
82 
83 unittest
84 {
85   import std.array;
86   import std.range.interfaces;
87 
88   ubyte[] txt = cast(ubyte[]) "acabacbxyz".dup;
89   auto buff = appender!(ubyte[]);
90 
91   auto r1 = inputRangeObject(txt);
92   assert(!r1.empty);
93   r1.drain();
94   assert(r1.empty);
95 
96   buff.clear();
97 
98   dstring ds =  "acaba";
99 
100   auto bc = byChunk(ds, 3);
101   assert(bc.front == "aca"d);
102   bc.popFront();
103   assert(bc.front == "ba"d);
104   bc.popFront();
105   assert(bc.empty);
106 }
107 
108 //----------------------------------------------------------------------------
109 
110 struct ByLines(R)
111   if (isInputRange!R && (isSomeChar!(ElementEncodingType!R) || is(ElementType!R : ubyte)))
112 {
113   import std.array;
114 
115   alias ElementEncodingType!R E;
116 
117   private:
118     R r;
119     E[] line;
120 
121   public:
122     bool empty = false;
123     string eoln = "x";
124 
125   this(R _r) { r = _r; popFront(); }
126 
127   void popFront()
128   {
129     assert(!empty);
130     if (eoln == "")
131       empty =true;
132     else
133     {
134       auto napp = appender!(E[])();
135 
136       E[] ln;
137       while (!r.empty && r.front != '\n' && r.front != '\r')
138       {
139         napp.put(r.front);
140         r.popFront();
141       }
142       if (!r.empty)
143       {
144         auto c = r.front; r.popFront();
145         if (c == '\r')
146         {
147           if (r.front == '\n')
148           {
149             r.popFront();
150             eoln = "\r\n";
151           }
152           else
153             eoln = "\r";
154         }
155         else
156           eoln = "\n";
157       }
158       else
159         eoln = "";
160 
161       line = napp.data;
162     }
163   }
164 
165   @property E[] front()
166   {
167     return line;
168   }
169 }
170 
171 ByLines!R byLines(R)(R range)
172 {
173   return ByLines!R(range);
174 }
175 
176 //----------------------------------------------------------------------------
177 
178 unittest
179 {
180   import std.conv;
181 
182   // Detect if D behaviour changes.
183 
184   assert("" !is null);
185   assert([] is null);
186 
187 
188   char[] a = "abc".dup;
189   assert(a[0..0] !is null);
190   assert(a[0..0].length == 0);
191   a.length = 0;
192   assert(a !is null);
193 
194   // Ripped off from Phobos.
195   void test(string txt, string[] witness)
196   {
197     import std.range.interfaces;
198 
199     auto r1 = inputRangeObject(txt);
200 
201     static assert(isInputRange!(typeof(r1)));
202     static assert(is(typeof(r1) : InputRange!(dchar)));
203 
204     auto lines = new ByLines!(typeof(r1))(r1);
205     uint i;
206 
207     while (!lines.empty)
208     {
209       assert(i<witness.length, text(i, ">=", witness.length));
210       assert(to!string(lines.front) == witness[i++]);
211       lines.popFront();
212     }
213     assert(i == witness.length, text(i, " != ", witness.length));
214   }
215 
216   test("", [ "" ]);
217   test("\n", [ "", "" ]);
218   test("asd\ndef\nasdf", [ "asd", "def", "asdf" ]);
219   test("asd\ndef\nasdf\n", [ "asd", "def", "asdf", "" ]);
220   test("asd\n\nasdf\n", [ "asd", "", "asdf", "" ]);
221   test("asd\r\ndef\r\nasdf\r\n", [ "asd", "def", "asdf", "" ]);
222   test("asd\r\n\r\nasdf\r\n", [ "asd", "", "asdf", "" ]);
223 }
224 
225 //----------------------------------------------------------------------------
226 // Buffered output. Sort of like a ByChunk for output ranges.
227 
228 struct Buffered(W,E, size_t bufferSize = defaultBufferSize) if (isOutputRange!(W,E[]) && bufferSize < 16777216)
229 {
230   this(W w)
231   {
232     writer = w;
233     length = 0;
234   }
235 
236   void put(E e)
237   {
238     if (length == bufferSize)
239     {
240       writer.put(buffer[0..$]);
241       length = 0;
242     }
243     buffer[length] = e;
244     ++length;
245   }
246 
247   void put(E[] e)
248   {
249     if (length + e.length > bufferSize)
250     {
251       if (length)
252       {
253         writer.put(buffer[0..length]);
254         length = 0;
255       }
256       if (e.length > bufferSize)
257       {
258         writer.put(e);
259         return;
260       }
261     }
262     buffer[length..(length+e.length)] = e;
263     length += e.length;
264   }
265 
266   void flush()
267   {
268     writer.put(buffer[0..length]);
269     length = 0;
270   }
271 
272   private:
273     W writer;
274     E[bufferSize] buffer;
275     size_t length;
276 }
277 
278 auto ref buffered(E, size_t bufferSize = defaultBufferSize, W)(W writer)
279 {
280   return Buffered!(W,E,bufferSize)(writer);
281 }
282 
283 unittest
284 {
285   import std.array;
286 
287   auto a = appender!(int[])();
288 
289   auto b = buffered!(int,10)(a);
290 
291   b.put(12);
292   assert(a.data == []);
293   b.flush();
294   assert(a.data == [12]);
295   b.put([5,13,6,9,1,0]);
296   assert(a.data == [12]);
297   b.put(3); b.put([55,13]); b.put(20);
298   assert(a.data == [12]);
299   b.put(11);
300   assert(a.data == [12,5,13,6,9,1,0,3,55,13,20]);
301   b.put([34,1,24,42,11,0,67]);
302   assert(a.data == [12,5,13,6,9,1,0,3,55,13,20]);
303   b.put([100,9,43,40]);
304   assert(a.data == [12,5,13,6,9,1,0,3,55,13,20,11,34,1,24,42,11,0,67]);
305   b.put([9,8,7,6,5,4,3,2,1,0,-1]);
306   assert(a.data == [12,5,13,6,9,1,0,3,55,13,20,11,34,1,24,42,11,0,67,100,9,43,40,9,8,7,6,5,4,3,2,1,0,-1]);
307   b.put([19,28,37,46,55,64,73,82,91,102,13]);
308   assert(a.data == [12,5,13,6,9,1,0,3,55,13,20,11,34,1,24,42,11,0,67,100,9,43,40,9,8,7,6,5,4,3,2,1,0,-1,19,28,37,46,55,64,73,82,91,102,13]);
309 }
310 
311 //----------------------------------------------------------------------------
312 // Provides "ungetc" for input ranges.
313 
314 import jaypha.container.stack;
315 
316 struct UnPopable(R) if (isInputRange!R)
317 {
318   alias ElementType!R E;
319 
320   R range;
321 
322   @property bool empty()
323   {
324     return range.empty && store.empty;
325   }
326 
327   @property E front()
328   {
329     if (!store.empty)
330       return store.front();
331     else
332       return range.front();
333   }
334 
335   void popFront()
336   {
337     if (!store.empty)
338       store.popFront();
339     else range.popFront();
340   }
341 
342   void unPopFront(E e)
343   {
344     store.put(e);
345   }
346 
347   private:
348     Stack!E store;
349 }
350 
351 auto ref unPopable(R)(R range)
352 {
353   return UnPopable!R(range);
354 }
355 
356 unittest
357 {
358   string s = "abôd";
359 
360   auto r = unPopable(s);
361 
362   assert(!r.empty);
363   assert(r.front == 'a');
364   r.popFront();
365   assert(!r.empty);
366   assert(r.front == 'b');
367   r.unPopFront('x');
368   assert(!r.empty);
369   assert(r.front == 'x');
370   r.unPopFront('y');
371   assert(!r.empty);
372   assert(r.front == 'y');
373   r.popFront();
374   assert(!r.empty);
375   assert(r.front == 'x');
376   r.popFront();
377   assert(!r.empty);
378   assert(r.front == 'b');
379   r.popFront();
380   assert(!r.empty);
381   assert(r.front == 'ô');
382   r.unPopFront('z');
383   assert(!r.empty);
384   assert(r.front == 'z');
385   r.popFront();
386   assert(!r.empty);
387   assert(r.front == 'ô');
388   r.popFront();
389   assert(!r.empty);
390   assert(r.front == 'd');
391   r.popFront();
392   assert(r.empty);
393   r.unPopFront('本');
394   assert(!r.empty);
395   assert(r.front == '本');
396   r.popFront();
397   assert(r.empty);
398 }