1 module more.common;
2 
3 import std.ascii;
4 import std.conv;
5 import std.range;
6 import std.stdio;
7 import std..string;
8 import std.bitmanip;
9 import std.format;
10 import std.traits : Unqual;
11 
12 import core.exception;
13 
14 import core.stdc..string : memmove;
15 
16 version(unittest)
17 {
18     import std.array;
19     import more.test;
20 }
21 
22 void implement(string feature = "", string file = __FILE__, int line = __LINE__) {
23     string msg = "not implemented";
24     if(feature.length)
25     {
26         msg = feature~' '~msg;
27     }
28     throw new Exception(msg, file, line);
29 }
30 
31 float stdTimeMillis(long stdTime)
32 {
33     return cast(float)stdTime / 10000f;
34 }
35 string prettyTime(float millis)
36 {
37     if (millis < 0) return "-"~prettyTime(-millis);
38 
39     if (millis < 1000)
40         return to!string(millis)~" millis";
41     if (millis < 60000)
42         return to!string(millis / 1000)~" seconds";
43     if (millis < 3600000)
44         return to!string(millis / 60000)~" minutes";
45     if (millis < 86400000)
46         return to!string(millis / 3600000)~" hours";
47     return to!string(millis / 86400000)~" days";
48 }
49 
50 template isChar(T) {
51   static if(is(T == char) ||
52             is(T == const char) ||
53             is(T == immutable char))
54     enum isChar = true;
55   else
56     enum isChar = false;
57 }
58 
59 template ArrayElementType(Type)
60 {
61     static if(is(Type : E[], E))
62         alias ArrayElementType = E;
63     else
64         alias ArrayElementType = void;
65 }
66 template TypeTupleStrings(alias sep, T...)
67 {
68   static if(T.length == 0) {
69     immutable string TypeTupleStrings = "";
70   } else static if(T.length == 1) {
71     immutable string TypeTupleStrings = T[0].stringof;
72   } else {
73     immutable string TypeTupleStrings = T[0].stringof ~ sep ~ TypeTupleStrings!(sep, T[1..$]);
74   }
75 }
76 template TupleRange(int from, int to) if (from <= to) {
77   static if (from >= to) {
78     alias TupleRange = TypeTuple!();
79   } else {
80     alias TupleRange = TypeTuple!(from, TupleRange!(from + 1, to));
81   }
82 }
83 
84 public interface IDisposable
85 {
86   void dispose();
87 }
88 
89 template Unroll(alias CODE, alias N, alias SEP="")
90 {
91     enum NEW_CODE = replace(CODE, "%", "%1$d");
92     enum Unroll = iota(N).map!(i => format(NEW_CODE, i)).join(SEP);
93 }
94 template UnrollTuple(alias CODE, alias SEP, T...)
95 {
96     enum NEW_CODE = replace(CODE, "%", "%1$d");
97     enum UnrollTuple = T.map!(t => format(NEW_CODE, t)).join(SEP);
98 }
99 
100 public alias DataHandler = void delegate(ubyte[] data);
101 public alias StringHandler = void delegate(string data);
102 public interface IDataHandler : IDisposable
103 {
104   void handleData(ubyte[] data);
105 }
106 
107 
108 string debugChar(char c)
109 {
110   switch(c) {
111   case '\r':return r"'\r'";
112   case '\n':return r"'\n'";
113   default:
114     if(isPrintable(c)) return "'" ~ c ~ "'";
115     return to!string(cast(uint)c);
116   }
117 }
118 
119 void dcpy(void* destination, const ubyte[] source) pure nothrow
120 {
121   (cast(ubyte*)destination)[0..source.length][] = source;
122 }
123 void dcpy(void* destination, const char[] source) pure nothrow
124 {
125   (cast(char*)destination)[0..source.length][] = source;
126 }
127 /+
128 void dcpy(Source)(void * destination, Source source) pure nothrow
129 {
130   (cast(ubyte*)destination)[0..source.length][] = source;
131 }
132 +/
133 
134 /+
135 void trimNewline(ref inout(char)[] line) {
136   // remove ending characters
137   while(true) {
138     if( line.length <= 0 || (line[$-1] != '\n' && line[$-1] != '\r') ) return;
139     line = line[0..$-1];
140   }
141 }
142 +/
143 
144 void trimNewline(inout(char)[]* line) {
145   // remove ending characters
146   while(true) {
147     if( (*line).length <= 0 || ((*line)[$-1] != '\n' && (*line)[$-1] != '\r') ) return;
148     (*line) = (*line)[0..$-1];
149   }
150 }
151 
152 
153 unittest
154 {
155   mixin(scopedTest!("trimNewline"));
156 
157   void testTrimNewline(string s, string expected) {
158     //writef("'%s' => ", escape(s));
159     trimNewline(&s);
160     //writefln("'%s'", escape(s));
161     assert(expected == s);
162   }
163 
164   testTrimNewline("", "");
165   testTrimNewline("\r", "");
166   testTrimNewline("\n", "");
167   testTrimNewline("1234", "1234");
168   testTrimNewline("abcd  \n", "abcd  ");
169   testTrimNewline("hello\r\r\r\n\n\r\r\n", "hello");
170 }
171 
172 
173 void readFullSize(File file, char[] buffer)
174 {
175     while(true) {
176         char[] lastRead = file.rawRead(buffer);
177         if(lastRead.length == buffer.length) return;
178         if(lastRead.length == 0) throw new Exception("File did not have enough data left to fill the buffer");
179         buffer = buffer[lastRead.length..$];
180     }
181 }
182 
183 // returns true on success, false if the file reached EOF
184 bool tryReadFullSize(File file, char[] buffer) {
185   while(true) {
186     char[] lastRead = file.rawRead(buffer);
187     if(lastRead.length == buffer.length) return true;
188     if(lastRead.length == 0) return false;
189     buffer = buffer[lastRead.length..$];
190   }
191 }
192 
193 alias ubyte[] function(ubyte[] oldBuffer, size_t newLength, bool copy) Allocator;
194 alias void function(ubyte[] buffer) Deallocator;
195 
196 ubyte[] defaultAllocator(ubyte[] oldBuffer, size_t newLength, bool copy)
197 {
198   oldBuffer.length = newLength;
199   return oldBuffer;
200 }
201 void defaultDeallocator(ubyte[] buffer)
202 {
203   // do nothing, the garbage collector handles it
204 }
205 alias CustomLineParser!(defaultAllocator, defaultDeallocator) LineParser;
206 
207 template CustomLineParser(A...)
208 {
209 struct CustomLineParser
210 {
211   size_t expandLength;
212 
213   ubyte[] buffer;
214 
215   ubyte *lineStart;       // Points to the start of the next line
216   size_t lineCheckOffset; // Offset from lineStart to the next character to check
217   size_t lineDataLimit;   // Length from lineStart to the end of the data
218 
219   public this(ubyte[] buffer, size_t expandLength = 256)
220   {
221     this.expandLength = expandLength;
222 
223     this.buffer = buffer;
224     this.lineStart = buffer.ptr;
225     this.lineCheckOffset = 0;
226     this.lineDataLimit = 0;
227   }
228   @property public void put(Data)(Data data) if ( (*data.ptr).sizeof == 1 )
229   {
230     debug writefln("[debug] ---> put(%4d bytes) : start %d check %d limit %d",
231                    data.length, (lineStart - buffer.ptr), lineCheckOffset, lineDataLimit);
232 
233     size_t newDataOffset = (lineStart - buffer.ptr) + lineDataLimit;
234     size_t bytesLeft = buffer.length - newDataOffset;
235 
236     if(bytesLeft < data.length) {
237       size_t lineStartOffset = lineStart - buffer.ptr;
238       size_t defaultExpandLength = this.buffer.length + this.expandLength;
239       size_t neededLength = newDataOffset + data.length;
240 
241       //this.buffer.length = (neededLength >= defaultExpandLength) ? neededLength : defaultExpandLength;
242       ubyte[] newBuffer = A[0](this.buffer, (neededLength >= defaultExpandLength) ? neededLength : defaultExpandLength, true);
243       A[1](this.buffer);
244       this.buffer = newBuffer;
245 
246       lineStart = this.buffer.ptr + lineStartOffset;
247     }
248 
249     dcpy(lineStart + lineDataLimit, data);
250     lineDataLimit += data.length;
251 
252     debug writefln("[debug] <--- put             : start %d check %d limit %d",
253                    (lineStart - buffer.ptr), lineCheckOffset, lineDataLimit);
254   }
255 
256   /// Returns null when it has parsed all the lines that have been added
257   public ubyte[] getLine()
258   {
259     debug writefln("[debug] ---> getLine         : start %d check %d limit %d",
260                    (lineStart - buffer.ptr), lineCheckOffset, lineDataLimit);
261 
262     while(lineCheckOffset < lineDataLimit) {
263       debug {
264         char c = lineStart[lineCheckOffset];
265         writefln("[debug] start[%s] = %s", lineCheckOffset, debugChar(c));
266       }
267       if(lineStart[lineCheckOffset] == '\n') {
268         ubyte[] line;
269         if(lineCheckOffset > 0 && lineStart[lineCheckOffset - 1] == '\r') {
270           line = lineStart[0..lineCheckOffset - 1];
271         } else {
272           line = lineStart[0..lineCheckOffset];
273         }
274         lineCheckOffset++;
275         lineDataLimit -= lineCheckOffset;
276         if(lineDataLimit > 0) {
277           lineStart = lineStart + lineCheckOffset;
278         } else {
279           lineStart = buffer.ptr;
280         }
281         lineCheckOffset = 0;
282 
283         debug writefln("[debug] <--- getLine line    : start %d check %d limit %d",
284                        (lineStart - buffer.ptr), lineCheckOffset, lineDataLimit);
285         return line;
286       }
287       lineCheckOffset++;
288     }
289 
290     if(lineStart == buffer.ptr) {
291       debug writeln("[debug] No data");
292       return null;
293     }
294 
295     assert(lineDataLimit > 0);
296 
297     //
298     // Move remaining data to the beginning of the buffer
299     //
300     debug writeln("[debug] Moving line data to beginning of buffer");
301     memmove(buffer.ptr, lineStart, lineDataLimit);
302     lineStart = buffer.ptr;
303     debug {
304       foreach(i,c; lineStart[0..lineDataLimit]) {
305         writefln("[debug] line[%s] = %s", i, debugChar(c));
306       }
307     }
308     debug writefln("[debug] <--- getLine no-line : start %d check %d limit %d",
309                    (lineStart - buffer.ptr), lineCheckOffset, lineDataLimit);
310     return null;
311   }
312 }
313 
314 }
315 unittest
316 {
317   mixin(scopedTest!("LineParser"));
318 
319   void TestLineParser(LineParser lineParser)
320   {
321     lineParser.put("\n");
322     assertEqual("", cast(string)lineParser.getLine());
323     assert(!lineParser.getLine());
324 
325     lineParser.put("\r\n");
326     assertEqual("", cast(string)lineParser.getLine());
327     assert(!lineParser.getLine());
328 
329     lineParser.put("\r\r\n");
330     assertEqual("\r", cast(string)lineParser.getLine());
331     assert(!lineParser.getLine());
332 
333     lineParser.put("\n\n\n");
334     assertEqual("", cast(string)lineParser.getLine());
335     assertEqual("", cast(string)lineParser.getLine());
336     assertEqual("", cast(string)lineParser.getLine());
337     assert(!lineParser.getLine());
338 
339     lineParser.put("\r\r\r\n\r\n\n\r\r\r\n");
340     assertEqual("\r\r", cast(string)lineParser.getLine());
341     assertEqual("", cast(string)lineParser.getLine());
342     assertEqual("", cast(string)lineParser.getLine());
343     assertEqual("\r\r", cast(string)lineParser.getLine());
344     assert(!lineParser.getLine());
345 
346     lineParser.put("abcd\n");
347     assertEqual("abcd", cast(string)lineParser.getLine());
348     assert(!lineParser.getLine());
349 
350     lineParser.put("abcd\r\n");
351     assertEqual("abcd", cast(string)lineParser.getLine());
352     assert(!lineParser.getLine());
353 
354     lineParser.put("abcd\nefgh\r\n");
355     assertEqual("abcd", cast(string)lineParser.getLine());
356     assertEqual("efgh", cast(string)lineParser.getLine());
357     assert(!lineParser.getLine());
358 
359     lineParser.put("abcd\r\nefghijkl");
360     assertEqual("abcd", cast(string)lineParser.getLine());
361     assert(!lineParser.getLine());
362     lineParser.put("\n");
363     assertEqual("efghijkl", cast(string)lineParser.getLine());
364     assert(!lineParser.getLine());
365 
366     lineParser.put("abcd\n");
367     lineParser.put("abcd\r\n");
368     lineParser.put("abcd\n");
369     assertEqual("abcd", cast(string)lineParser.getLine());
370     assertEqual("abcd", cast(string)lineParser.getLine());
371     assertEqual("abcd", cast(string)lineParser.getLine());
372     assert(!lineParser.getLine());
373 
374     lineParser.put("a");
375     assert(!lineParser.getLine());
376     lineParser.put("bc");
377     assert(!lineParser.getLine());
378     lineParser.put("d");
379     assert(!lineParser.getLine());
380     lineParser.put("\r\ntu");
381     lineParser.put("v");
382     assertEqual("abcd", cast(string)lineParser.getLine());
383     assert(!lineParser.getLine());
384     lineParser.put("\r");
385     assert(!lineParser.getLine());
386     lineParser.put("\n");
387     assertEqual("tuv", cast(string)lineParser.getLine());
388     assert(!lineParser.getLine());
389   }
390 
391   TestLineParser(LineParser([],1));
392   TestLineParser(LineParser([0,0,0], 3));
393   TestLineParser(LineParser(new ubyte[256]));
394 }
395 
396 
397 enum noEndingQuoteMessage = "found '\"' with no ending '\"'";
398 
399 /**
400  * Used to parse the fields in <i>line</i> to the <i>fields</i> sink.
401  * line is a single line without the line ending character.
402  * returns error message on error
403  */
404 string tryParseFields(string comment = "#", T, C)(T fields, C[] line) if(isOutputRange!(T,C))
405 {
406   size_t off = 0;
407   size_t startOfField;
408 
409   Unqual!C c;
410 
411   while(true) {
412 
413     // Skip whitespace
414     while(true) {
415       if(off >= line.length) return null;
416       c = line[off];
417       if(c != ' ' && c != '\t') break;
418       off++;
419     }
420 
421     // check for comment
422     static if(comment != null && comment.length == 1) {
423 
424       if(c == comment[0]) return null;
425 
426     } else static if(comment != null && comment.length > 1) {
427 
428       if(c == comment[0]) {
429         size_t i = 1;
430         while(true) {
431           if(off + i > line.length) break;
432           c = line[off + i];
433           if(c != comment[i]) break;
434 
435           i++;
436           if(i >= comment.length) return null;
437         }
438 
439         c = comment[0]; // restore state
440       }
441 
442     }
443 
444     if(c == '"') {
445       off++;
446       startOfField = off;
447       while(true) {
448         if(off >= line.length) return noEndingQuoteMessage;
449         c = line[off];
450         if(c == '"') {
451           fields.put(line[startOfField..off]);
452           break;
453         }
454         if(c == '\\') {
455           throw new Exception("Escaping string not implemented yet");
456         }
457         off++;
458       }
459     } else {
460       startOfField = off;
461       while(true) {
462         off++;
463         if(off >= line.length) {
464           fields.put(line[startOfField..$]);
465           return null;
466         }
467         c = line[off];
468         if(c == ' ' || c == '\t') {
469           fields.put(line[startOfField..off]);
470           break;
471         }
472       }
473     }
474   }
475 }
476 
477 unittest
478 {
479   mixin(scopedTest!("tryParseFields"));
480 
481   string line;
482   auto fields = appender!(string[])();
483 
484   line = "\"Missing ending quote";
485   assert(tryParseFields(fields, line) == noEndingQuoteMessage);
486 
487   line = "\"Missing ending quote\n";
488   assert(tryParseFields(fields, line) == noEndingQuoteMessage);
489 
490   line = "\"Missing ending quote\nOn next line \"";
491   assert(tryParseFields(fields, line) == noEndingQuoteMessage);
492 
493   line = "";
494   assert(tryParseFields(fields, line) == null);
495 }
496 
497 
498 void bigEndianSetUshort(ubyte* bytes, ushort value)
499 {
500   *bytes       = cast(ubyte)(value >> 8);
501   *(bytes + 1) = cast(ubyte)(value     );
502 }
503 struct ArrayList(V)
504 {
505   public V[] array;
506   public size_t count;
507 
508   this(size_t initialSize)
509   {
510     if(initialSize < 1) throw new Exception("Cannot give an initial size of 0");
511     array = new V[initialSize];
512     count = 0;
513   }
514   this(Range)(Range r)
515   {
516     array = new V[r.length];
517     foreach(i, element; r) {
518       array[i] = element;
519     }
520     count = r.length;
521   }
522   void clear()
523   {
524     count = 0;
525   }
526   void checkSizeBeforeAdd()
527   {
528     if(count >= array.length) {
529       array.length *= 2;
530     }
531   }
532   public void put(V element)
533   {
534     checkSizeBeforeAdd();
535     array[count++] = element;
536   }
537   public void forwardTo(Sink)(Sink sink)
538   {
539     foreach(i; 0..count) {
540       sink.add(array[i]);
541     }
542   }
543   public void removeAt(size_t index)
544   {
545     for(auto i = index; i < count - 1; i++) {
546       array[index] = array[index + 1];
547     }
548     count--;
549   }
550 }
551 
552 
553 immutable string[] escapeTable =
554   [
555    "\\0"  ,
556    "\\x01",
557    "\\x02",
558    "\\x03",
559    "\\x04",
560    "\\x05",
561    "\\x06",
562    "\\a",  // Bell
563    "\\b",  // Backspace
564    "\\t",
565    "\\n",
566    "\\v",  // Vertical tab
567    "\\f",  // Form feed
568    "\\r",
569    "\\x0E",
570    "\\x0F",
571    "\\x10",
572    "\\x11",
573    "\\x12",
574    "\\x13",
575    "\\x14",
576    "\\x15",
577    "\\x16",
578    "\\x17",
579    "\\x18",
580    "\\x19",
581    "\\x1A",
582    "\\x1B",
583    "\\x1C",
584    "\\x1D",
585    "\\x1E",
586    "\\x1F",
587    " ", //
588    "!", //
589    "\"", //
590    "#", //
591    "$", //
592    "%", //
593    "&", //
594    "'", //
595    "(", //
596    ")", //
597    "*", //
598    "+", //
599    ",", //
600    "-", //
601    ".", //
602    "/", //
603    "0", //
604    "1", //
605    "2", //
606    "3", //
607    "4", //
608    "5", //
609    "6", //
610    "7", //
611    "8", //
612    "9", //
613    ":", //
614    ";", //
615    "<", //
616    "=", //
617    ">", //
618    "?", //
619    "@", //
620    "A", //
621    "B", //
622    "C", //
623    "D", //
624    "E", //
625    "F", //
626    "G", //
627    "H", //
628    "I", //
629    "J", //
630    "K", //
631    "L", //
632    "M", //
633    "N", //
634    "O", //
635    "P", //
636    "Q", //
637    "R", //
638    "S", //
639    "T", //
640    "U", //
641    "V", //
642    "W", //
643    "X", //
644    "Y", //
645    "Z", //
646    "[", //
647    "\\", //
648    "]", //
649    "^", //
650    "_", //
651    "`", //
652    "a", //
653    "b", //
654    "c", //
655    "d", //
656    "e", //
657    "f", //
658    "g", //
659    "h", //
660    "i", //
661    "j", //
662    "k", //
663    "l", //
664    "m", //
665    "n", //
666    "o", //
667    "p", //
668    "q", //
669    "r", //
670    "s", //
671    "t", //
672    "u", //
673    "v", //
674    "w", //
675    "x", //
676    "y", //
677    "z", //
678    "{", //
679    "|", //
680    "}", //
681    "~", //
682    "\x7F", //
683    "\x80", "\x81", "\x82", "\x83", "\x84", "\x85", "\x86", "\x87", "\x88", "\x89", "\x8A", "\x8B", "\x8C", "\x8D", "\x8E", "\x8F",
684    "\x90", "\x91", "\x92", "\x93", "\x94", "\x95", "\x96", "\x97", "\x98", "\x99", "\x9A", "\x9B", "\x9C", "\x9D", "\x9E", "\x9F",
685    "\xA0", "\xA1", "\xA2", "\xA3", "\xA4", "\xA5", "\xA6", "\xA7", "\xA8", "\xA9", "\xAA", "\xAB", "\xAC", "\xAD", "\xAE", "\xAF",
686    "\xB0", "\xB1", "\xB2", "\xB3", "\xB4", "\xB5", "\xB6", "\xB7", "\xB8", "\xB9", "\xBA", "\xBB", "\xBC", "\xBD", "\xBE", "\xBF",
687    "\xC0", "\xC1", "\xC2", "\xC3", "\xC4", "\xC5", "\xC6", "\xC7", "\xC8", "\xC9", "\xCA", "\xCB", "\xCC", "\xCD", "\xCE", "\xCF",
688    "\xD0", "\xD1", "\xD2", "\xD3", "\xD4", "\xD5", "\xD6", "\xD7", "\xD8", "\xD9", "\xDA", "\xDB", "\xDC", "\xDD", "\xDE", "\xDF",
689    "\xE0", "\xE1", "\xE2", "\xE3", "\xE4", "\xE5", "\xE6", "\xE7", "\xE8", "\xE9", "\xEA", "\xEB", "\xEC", "\xED", "\xEE", "\xEF",
690    "\xF0", "\xF1", "\xF2", "\xF3", "\xF4", "\xF5", "\xF6", "\xF7", "\xF8", "\xF9", "\xFA", "\xFB", "\xFC", "\xFD", "\xFE", "\xFF",
691    ];
692 string escape(char c) {
693   return escapeTable[c];
694 }
695 string escape(dchar c) {
696   return (c < escapeTable.length) ? escapeTable[c] : to!string(c);
697 }
698 inout(char)[] escape(inout(char)[] str) pure {
699   size_t extra = 0;
700   foreach(c; str) {
701     if(c == '\r' || c == '\t' || c == '\n' || c == '\\') {
702       extra++;
703     }
704   }
705 
706   if(extra == 0) return str;
707 
708   char[] newString = new char[str.length + extra];
709   size_t oldIndex, newIndex = 0;
710   for(oldIndex = 0; oldIndex < str.length; oldIndex++) {
711     auto c = str[oldIndex];
712     switch(c) {
713     case '\r':
714       newString[newIndex++] = '\\';
715       newString[newIndex++] = 'r';
716       break;
717     case '\t':
718       newString[newIndex++] = '\\';
719       newString[newIndex++] = 't';
720       break;
721     case '\n':
722       newString[newIndex++] = '\\';
723       newString[newIndex++] = 'n';
724       break;
725     case '\\':
726       newString[newIndex++] = '\\';
727       newString[newIndex++] = '\\';
728       break;
729     default:
730       newString[newIndex++] = c;
731     }
732   }
733 
734   assert(newIndex == newString.length);
735 
736   return cast(inout(char)[])newString;
737 }
738 unittest
739 {
740   mixin(scopedTest!("escape"));
741 
742   assert (char.max == escapeTable.length - 1);
743 
744   for(auto c = ' '; c <= '~'; c++) {
745     assert(c == escape(c)[0]);
746   }
747   assert("\\0" == escape(0));
748   assert("\\r" == escape('\r'));
749   assert("\\n" == escape('\n'));
750   assert("\\t" == escape('\t'));
751 
752 
753   assert("\\r" == escape("\r"));
754   assert("\\t" == escape("\t"));
755   assert("\\n" == escape("\n"));
756   assert("\\\\" == escape("\\"));
757 }
758 char hexchar(ubyte b) in { assert(b <= 0x0F); } body
759 {
760     return cast(char)(b + ((b <= 9) ? '0' : ('A'-10)));
761 }
762 
763 struct Escaped
764 {
765     const(char)* str;
766     const char* limit;
767     this(const(char)[] str)
768     {
769         this.str = str.ptr;
770         this.limit = str.ptr + str.length;
771     }
772     void toString(scope void delegate(const(char)[]) sink) const
773     {
774         const(char) *print = str;
775         const(char) *ptr = str;
776       CHUNK_LOOP:
777         for(;ptr < limit;ptr++)
778         {
779             char c = *ptr;
780             if(c < ' ' || c > '~') // if not human readable
781             {
782                 if(ptr > print)
783                 {
784                     sink(print[0..ptr-print]);
785                 }
786                 if(c == '\r') sink("\\r");
787                 else if(c == '\t') sink("\\t");
788                 else if(c == '\n') sink("\\n");
789                 else {
790                     char[4] buffer;
791                     buffer[0] = '\\';
792                     buffer[1] = 'x';
793                     buffer[2] = hexchar(c>>4);
794                     buffer[3] = hexchar(c&0xF);
795                     sink(buffer);
796                 }
797                 print = ptr + 1;
798             }
799         }
800         if(ptr > print)
801         {
802             sink(print[0..ptr-print]);
803         }
804     }
805 }
806 
807 
808 struct StdoutWriter
809 {
810   void put(const (char)[] str) {
811     write(str);
812   }
813 }
814 
815 struct ReadBuffer(T)
816 {
817   const(T)* next;
818   const T* limit;
819 }
820 struct WriteBuffer(T)
821 {
822   T* next;
823   const T* limit;
824   this(T[] array) {
825     this.next = array.ptr;
826     this.limit = array.ptr + array.length;
827   }
828   T[] slice(T* start) {
829     return start[0..next-start];
830   }
831   void put(const(T)[] array) {
832     if(next + array.length > limit)
833       throw new Exception(format("buffer has %s elements left but you tried to add %s",
834                                  limit - next, array.length));
835 
836     foreach(value; array) {
837       *next = value;
838       next++;
839     }
840   }
841 }
842 
843 alias void delegate(const(char)[] msg) const Writer;
844 
845 alias size_t delegate(char[] buffer) CharReader;
846 alias size_t delegate(ubyte[] buffer) DataReader;
847 
848 struct FileCharReader
849 {
850   File file;
851   size_t read(char[] buffer) {
852     return file.rawRead(buffer).length;
853   }
854 }
855 
856 struct AsciiBufferedInput
857 {
858   CharReader reader;
859   char[] buffer;
860   size_t start;
861   size_t limit;
862 
863   this(inout(char)[] s) {
864     this.reader = &emptyRead;
865     this.buffer = new char[s.length + 1];
866     this.buffer[0..$-1] = s;
867     this.start = 0;
868     this.limit = s.length;
869   }
870   private size_t emptyRead(char[] buffer) { return 0; }
871 
872   char[] sliceSaved() {
873     return buffer[start..limit];
874   }
875 
876   void clear() {
877     start = 0;
878     limit = 0;
879   }
880 
881   // returns true if there is data, false on EOF
882   bool readNoSave() {
883     start = 0;
884     limit = reader(buffer);
885     return limit > 0;
886   }
887 
888   // returns true if read succeeded, false on EOF
889   bool read(size_t* offsetToShift) {
890     //writefln("[DEBUG] --> read(start=%s, limit=%s)", start, limit);
891     size_t leftover = limit - start;
892 
893     if(leftover) {
894       if(leftover >= buffer.length) {
895         throw new Exception("Buffer not large enough");
896       }
897 
898       if(start > 0) {
899         memmove(buffer.ptr, buffer.ptr + start, leftover);
900         *offsetToShift -= leftover;
901       }
902     }
903 
904     start = 0;
905     limit = leftover;
906     size_t readLength = reader(buffer[leftover..$]);
907     if(readLength == 0) {
908       //writefln("[DEBUG] <-- read(start=%s, length=%s) = false", start, length);
909       return false;
910     }
911 
912     limit += readLength;
913     return true;
914   }
915 
916 
917 }
918 
919 
920 struct FormattedBinaryWriter
921 {
922   void delegate(const(char)[]) sink;
923 
924   ubyte[] columnBuffer;
925 
926   string offsetFormat;
927   mixin(bitfields!
928         (bool, "hex", 1,
929          bool, "text", 1,
930          void, "", 6));
931 
932   size_t cachedData;
933 
934   uint offset;
935 
936   this(scope void delegate(const(char)[]) sink, ubyte[] columnBuffer,
937        ubyte offsetTextWidth = 8, bool hex = true, bool text = true) {
938     this.sink = sink;
939     this.columnBuffer = columnBuffer;
940 
941     if(offsetTextWidth == 0) {
942       offsetFormat = null;
943     } else {
944       offsetFormat = "%0"~to!string(offsetTextWidth)~"x";
945     }
946 
947     this.hex = hex;
948     this.text = text;
949   }
950 
951   void writeAscii(byte b) {
952     if(b <= '~' && b >= ' ') {
953       sink((cast(char*)&b)[0..1]);
954     } else {
955       sink(".");
956     }
957   }
958 
959   void writeRow(T)(T data) {
960     bool atFirst = true;
961     void prefix() {
962       if(atFirst) {atFirst = false; }
963       else { sink(" "); }
964     }
965     if(offsetFormat) {
966       prefix();
967       formattedWrite(sink, offsetFormat, offset);
968     }
969 
970     if(hex) {
971       prefix();
972       foreach(b; data) {
973         formattedWrite(sink, " %02x", b);
974       }
975     }
976 
977     if(text) {
978       prefix();
979       foreach(b; data) {
980         writeAscii(b);
981       }
982     }
983 
984     sink("\n");
985     offset += columnBuffer.length;
986   }
987   void finish() {
988     if(cachedData > 0) {
989       bool atFirst = true;
990       void prefix() {
991         if(atFirst) {atFirst = false; }
992         else { sink(" "); }
993       }
994       if(offsetFormat) {
995         prefix();
996         formattedWrite(sink, offsetFormat, offset);
997       }
998 
999       if(hex) {
1000         prefix();
1001         foreach(b; columnBuffer[0..cachedData]) {
1002           formattedWrite(sink, " %02x", b);
1003         }
1004         foreach(b; cachedData..columnBuffer.length) {
1005           sink("   ");
1006         }
1007       }
1008 
1009       if(text) {
1010         prefix();
1011         foreach(b; columnBuffer[0..cachedData]) {
1012           writeAscii(b);
1013         }
1014       }
1015 
1016       sink("\n");
1017       offset += columnBuffer.length;
1018 
1019       cachedData = 0;
1020     }
1021   }
1022   void put(scope ubyte[] data) in { assert(data.length); } body {
1023     if(cachedData > 0) {
1024       auto combineLength = columnBuffer.length - cachedData;
1025       if(combineLength > data.length) {
1026         combineLength = data.length;
1027       }
1028       columnBuffer[cachedData..cachedData+combineLength] = data[];
1029       writeRow(columnBuffer);
1030       data = data[combineLength..$];
1031     }
1032 
1033     while(data.length >= columnBuffer.length) {
1034       writeRow(data[0..columnBuffer.length]);
1035       data = data[columnBuffer.length..$];
1036     }
1037       
1038     if(data.length > 0) {
1039       columnBuffer[0..data.length][] = data;
1040       cachedData = data.length;
1041     }
1042   }
1043 }
1044 
1045 
1046 
1047 //
1048 // Range Initializers
1049 //
1050 string arrayRange(char min, char max, string initializer) {
1051   string initializers = "";
1052   for(char c = min; c < max; c++) {
1053     initializers ~= "'"~c~"': "~initializer~",\n";
1054   }
1055   initializers ~= "'"~max~"': "~initializer;
1056   return initializers;
1057 }
1058 string rangeInitializers(string[] s...) {
1059   if(s.length % 2 != 0) assert(0, "must supply an even number of arguments to rangeInitializers");
1060   string code = "["~rangeInitializersCurrent(s);
1061   //assert(0, code); // uncomment to see the code
1062   return code;
1063 }
1064 string rangeInitializersCurrent(string[] s) {
1065   string range = s[0];
1066   if(range[0] == '\'') {
1067     if(range.length == 3 || (range.length == 4 && range[1] == '\\')) {
1068       if(range[$-1] != '\'') throw new Exception(format("a single-character range %s started with an apostrophe (') but did not end with one", range));
1069       return range ~ ":" ~ s[1] ~ rangeInitializersNext(s);
1070     }
1071   } else {
1072     throw new Exception(format("range '%s' not supported", range));
1073   }
1074   char min = range[1];
1075   char max = range[5];
1076   return arrayRange(min, max, s[1]) ~ rangeInitializersNext(s);
1077 }
1078 string rangeInitializersNext(string[] s...) {
1079   if(s.length <= 2) return "]";
1080   return ",\n"~rangeInitializersCurrent(s[2..$]);
1081 }
1082 
1083 
1084 
1085 struct StringByLine
1086 {
1087   string s;
1088   size_t startOfLineOffset;
1089   size_t endOfLineOffset;
1090   this(string s) @safe pure nothrow @nogc
1091   {
1092     this.s = s;
1093     this.startOfLineOffset = 0;
1094     this.endOfLineOffset = 0;
1095     popFront;
1096   }
1097   @property bool empty() pure nothrow @safe @nogc
1098   {
1099     return startOfLineOffset >= s.length;
1100   }
1101   @property auto front() pure nothrow @safe @nogc
1102   {
1103     return s[startOfLineOffset..endOfLineOffset];
1104   }
1105   @property void popFront() pure nothrow @safe @nogc
1106   {
1107     if(startOfLineOffset < s.length) {
1108       startOfLineOffset = endOfLineOffset;
1109       while(true) {
1110         if(endOfLineOffset >= s.length) break;
1111         if(s[endOfLineOffset] == '\n') {
1112           endOfLineOffset++;
1113           break;
1114         }
1115         endOfLineOffset++;
1116       }
1117     }
1118   }
1119 }
1120 auto byLine(string s) pure nothrow @safe @nogc {
1121   return StringByLine(s);
1122 }
1123 unittest
1124 {
1125   mixin(scopedTest!("StringByLine"));
1126   
1127   void test(string[] expectedStrings, string s, size_t testLine = __LINE__)
1128   {
1129     auto stringByLine = s.byLine();
1130 
1131     foreach(expectedString; expectedStrings) {
1132       if(stringByLine.empty) {
1133         writefln("Expected string '%s' but no more strings", escape(expectedString));
1134         assert(0);
1135       }
1136       if(expectedString != stringByLine.front) {
1137         writefln("Expected: '%s'", escape(expectedString));
1138         writefln("Actual  : '%s'", escape(stringByLine.front));
1139         assert(0);
1140       }
1141       stringByLine.popFront;
1142     }
1143 
1144     if(!stringByLine.empty) {
1145       writefln("Expected no more strings but got another '%s'", escape(stringByLine.front));
1146       assert(0);
1147     }
1148   }
1149 
1150   test([], "");
1151   test(["a"], "a");
1152   test(["a\n"], "a\n");
1153   test(["abc"], "abc");
1154   test(["abc\n"], "abc\n");
1155 
1156   test(["abc\n", "123"], "abc\n123");
1157   test(["abc\n", "123\n"], "abc\n123\n");
1158 
1159 }
1160 
1161 
1162 
1163 enum BufferTooSmall
1164 {
1165   returnPartialData,
1166   throwException,
1167   resizeBuffer,
1168 }
1169 
1170 /**
1171    The LinesChunker reads as many lines as it can.  If the buffer runs out in the
1172    middle of a line it will return all the previous full lines.  On the next
1173    read it will move the left over data to the beginning of the buffer and continue reading.
1174    If it cannot read a full line it will either return partial lines or it will throw an
1175    error depending on what the user specifies.
1176    Options on how to handle lines that are too long
1177      1. Return partial lines
1178      2. Resize the buffer to hold the entire line
1179      3. Throw an exception/cause an error
1180 */
1181 struct LinesChunker
1182 {
1183   char[] buffer;
1184   BufferTooSmall tooSmall;
1185   private char[] leftOver;
1186   
1187   this(char[] buffer, BufferTooSmall tooSmall) {
1188     this.buffer = buffer;
1189     this.tooSmall = tooSmall;
1190   }
1191   size_t read(CharReader reader)
1192   {
1193     //
1194     // Handle leftOver Data
1195     //
1196     size_t bufferOffset;
1197     if(leftOver.length == 0) {
1198       bufferOffset = 0;
1199     } else {
1200       // TODO: do I need this check? Can this ever happen?
1201       if(leftOver.ptr != buffer.ptr) {
1202         memmove(buffer.ptr, leftOver.ptr, leftOver.length);
1203       }
1204       bufferOffset = leftOver.length;
1205       leftOver = null;
1206     }
1207 
1208     //
1209     // Read More Data
1210     //
1211     while(true) {
1212       if(bufferOffset >= buffer.length) {
1213         if(tooSmall == BufferTooSmall.returnPartialData) {
1214           return bufferOffset;
1215         } else if(tooSmall == BufferTooSmall.resizeBuffer) {
1216           throw new Exception("BufferTooSmall.resizeBuffer is not implemented in LinesChunker");
1217         }
1218         throw new Exception(format("the current buffer of length %s is too small to hold the current line", buffer.length));
1219       }
1220 
1221       size_t readLength = reader(buffer[bufferOffset .. $]);
1222       if(readLength == 0) return bufferOffset;
1223 
1224       auto totalLength = bufferOffset + readLength;
1225       auto i = totalLength - 1;
1226       while(true) {
1227         auto c = buffer[i];
1228         if(c == '\n') {
1229           leftOver = buffer[i+1..totalLength];
1230           return i+1;
1231         }
1232         if(i == bufferOffset) {
1233           break;
1234         }
1235         i--;
1236       }
1237 
1238       bufferOffset = totalLength;
1239     }
1240   }
1241 }
1242 
1243 version(unittest)
1244 {
1245   struct CustomChunks {
1246     string[] chunks;
1247     size_t chunkIndex;
1248     size_t read(char[] buffer) {
1249       if(chunkIndex >= chunks.length) return 0;
1250       auto chunk = chunks[chunkIndex++];
1251       if(chunk.length > buffer.length) {
1252         assert(0, format("Chunk at index %s is %s bytes but the buffer is only %s", chunkIndex, chunk.length, buffer.length));
1253       }
1254       buffer[0..chunk.length] = chunk;
1255       return chunk.length;
1256     }
1257   }
1258 }
1259 struct LinesReader
1260 {
1261   CharReader reader;
1262   LinesChunker chunker;
1263   size_t currentBytes;
1264   this(CharReader reader, char[] buffer, BufferTooSmall tooSmall)
1265   {
1266     this.reader = reader;
1267     this.chunker = LinesChunker(buffer, tooSmall);
1268     this.currentBytes = this.chunker.read(reader);
1269   }
1270   @property empty()
1271   {
1272     return currentBytes == 0;
1273   }
1274   @property char[] front() {
1275     return chunker.buffer[0..currentBytes];
1276   }
1277   @property void popFront() {
1278     this.currentBytes = this.chunker.read(reader);
1279   }
1280 }
1281 auto byLines(CharReader reader, char[] buffer, BufferTooSmall tooSmall) {
1282   return LinesReader(reader, buffer, tooSmall);
1283 }
1284 auto byLines(File file, char[] buffer, BufferTooSmall tooSmall) {
1285   auto fileCharReader = FileCharReader(file);
1286   return LinesReader(&(fileCharReader.read), buffer, tooSmall);
1287 }
1288 
1289 
1290 unittest
1291 {
1292   mixin(scopedTest!("LinesChunker/LinesReader"));
1293 
1294   CustomChunks customChunks;
1295   char[5] buffer5;
1296   char[256] buffer256;
1297 
1298   void testLinesChunkerCustom(string[] expectedChunks, CharReader reader, char[] chunkerBuffer = buffer256, size_t testLine = __LINE__)
1299   {
1300     auto lineChunker = LinesChunker(chunkerBuffer, BufferTooSmall.throwException);
1301 
1302     foreach(expectedChunk; expectedChunks) {
1303       size_t actualLength = lineChunker.read(reader);
1304       if(chunkerBuffer[0..actualLength] != expectedChunk) {
1305         writefln("Expected: '%s'", escape(expectedChunk));
1306         writefln("Actual  : '%s'", escape(chunkerBuffer[0..actualLength]));
1307         assert(0);
1308       }
1309 /+
1310       debug {
1311         writefln("
1312       }
1313 +/
1314     }
1315   }
1316   void testLinesChunker(string[] expectedChunks, string[] customChunkStrings, char[] chunkerBuffer = buffer256, size_t testLine = __LINE__)
1317   {
1318     writefln("testLinesChunker %s %s", expectedChunks, customChunkStrings);
1319 
1320     customChunks = CustomChunks(customChunkStrings);
1321     testLinesChunkerCustom(expectedChunks, &(customChunks.read), chunkerBuffer, testLine);
1322   }
1323   
1324   
1325   testLinesChunker(["a\n"], ["a\n"]);
1326   testLinesChunker(["a\n", "b"], ["a\nb"], buffer5); // NOTE: One would think that this should return one chunk, however,
1327                                                      //       This simulates the case when the reader returned the available
1328                                                      //       data so there's no way for the chunker to know if there is another newline
1329                                                      //       coming at the next read call so it just returns the currently known lines
1330 
1331   testLinesChunker(["a\nb\r\nc\n"], ["a\nb\r\nc\n"]);
1332   testLinesChunker(["a\nb\r\nc\n", "d"], ["a\nb\r\nc\nd"]);
1333   testLinesChunker(["123\n", "123\n"], ["123\n1", "23\n"], buffer5);
1334   testLinesChunker(["123\n", "123"], ["123\n1", "23"], buffer5);
1335 
1336   //
1337   // Test LinesReader
1338   //
1339 /+
1340   void testByLines(string[] expectedChunks, string[] customChunkStrings, char[] cunkBuffer = buffer256, size_t testLine = __LINE__)
1341   {
1342     
1343   }
1344 +/
1345   customChunks = CustomChunks(["a\nb"]);
1346   foreach(lines; (&(customChunks.read)).byLines(buffer5, BufferTooSmall.throwException)) {
1347     writefln("lines: '%s'", escape(lines));
1348   }
1349 }
1350 
1351 
1352 struct LineReader
1353 {
1354   CharReader reader;
1355   LinesChunker chunker;
1356 
1357   size_t dataLength;
1358   char[] line;
1359   size_t endOfLineOffset;
1360 
1361   this(CharReader reader, char[] buffer, BufferTooSmall tooSmall) {
1362     this.reader = reader;
1363     this.chunker = LinesChunker(buffer, tooSmall);
1364 
1365     this.dataLength = this.chunker.read(reader);
1366     this.line = null;
1367     this.endOfLineOffset = 0;
1368     popFront();
1369   }
1370   @property bool empty()
1371   {
1372     return line is null;
1373   }
1374   @property auto front()
1375   {
1376     return line;
1377   }
1378   @property void popFront()
1379   {
1380     if(endOfLineOffset >= dataLength) {
1381 
1382       if(this.line is null) {
1383         //writefln("[DEBUG] LineReader.popFront no more data");
1384         return;
1385       }
1386       this.dataLength = this.chunker.read(reader);
1387       if(this.dataLength == 0) {
1388         this.line = null;
1389         //writefln("[DEBUG] LineReader.popFront no more data");
1390         return;
1391       }
1392       endOfLineOffset = 0;
1393     }
1394 
1395     auto startOfNextLine = endOfLineOffset;
1396     while(true) {
1397       if(chunker.buffer[endOfLineOffset] == '\n') {
1398         endOfLineOffset++;
1399         line = chunker.buffer[startOfNextLine..endOfLineOffset];
1400         break;
1401       }
1402       endOfLineOffset++;
1403       if(endOfLineOffset >= dataLength) {
1404         line = chunker.buffer[startOfNextLine..endOfLineOffset];
1405         break;
1406       }
1407     }
1408 
1409     //writefln("[DEBUG] LineReader.popFront '%s'", escape(line));
1410 
1411   }
1412 }
1413 
1414 
1415 auto byLine(CharReader reader, char[] buffer, BufferTooSmall tooSmall) {
1416   return LineReader(reader, buffer, tooSmall);
1417 }
1418 /*
1419 auto byLine(File file, char[] buffer, BufferTooSmall tooSmall) {
1420   auto fileCharReader = FileCharReader(file);
1421   return LinesReader(&(fileCharReader.read), buffer, tooSmall);
1422 }
1423 */
1424 
1425 unittest
1426 {
1427   mixin(scopedTest!("LineReader"));
1428 
1429   char[256] buffer256;
1430 
1431   void testLines(string data, size_t testLine = __LINE__) {
1432     CustomChunks customChunks;
1433         
1434     for(auto chunkSize = 1; chunkSize <= data.length; chunkSize++) {
1435 
1436       // Create Chunks
1437       string[] chunks;
1438       size_t offset;
1439       for(offset = 0; offset + chunkSize <= data.length; offset += chunkSize) {
1440         chunks ~= data[offset .. offset + chunkSize];
1441       }
1442       if(data.length - offset > 0) {
1443         chunks ~= data[offset .. $];
1444       }
1445       //writefln("ChunkSize %s Chunks %s", chunkSize, chunks);
1446 
1447       customChunks = CustomChunks(chunks);
1448       auto lineReader = LineReader(&(customChunks.read), buffer256, BufferTooSmall.throwException);
1449 
1450       //writefln("[DEBUG] lineReader.front = '%s'", lineReader.front);
1451       
1452       
1453       size_t lineNumber = 1;
1454       foreach(line; data.byLine()) {
1455         //writefln("[DEBUG] line %s '%s'", lineNumber, escape(line));
1456 
1457         if(lineReader.empty) {
1458           writefln("Expected line '%s' but no more lines", escape(line));
1459           assert(0);
1460         }
1461         if(line != lineReader.front) {
1462           writefln("Expected: '%s'", escape(line));
1463           writefln("Actual  : '%s'", escape(lineReader.front));
1464           assert(0);
1465         }
1466         lineReader.popFront;
1467         lineNumber++;
1468       }
1469 
1470       if(!lineReader.empty) {
1471         writefln("Got extra line '%s' but expected no more lines", escape(lineReader.front));
1472         assert(0);
1473       }
1474 
1475     }
1476   }
1477 
1478   //testLines("abc");
1479   //testLines("abc\n");
1480   testLines("abc\n1234\n\n");
1481 
1482 
1483 }