1 // Written in the D programming language.
2 /**
3 Haystack misc utils.
4 
5 Copyright: Copyright (c) 2017, Radu Racariu <radu.racariu@gmail.com>
6 License:   $(LINK2 www.boost.org/LICENSE_1_0.txt, Boost License 1.0)
7 Authors:   Radu Racariu
8 **/
9 module haystack.util.misc;
10 import std.traits : isSomeChar;
11 import std.range.primitives : isInputRange,
12                               ElementEncodingType;
13 
14 enum isCharInputRange(Range) = isInputRange!Range && isSomeChar!(ElementEncodingType!Range);
15 
16 /**
17 A char range that allows look ahead buffering and can also collect a buffer of items
18 **/
19 struct LookAhead(Range)
20 if (isCharInputRange!Range)
21 {
22     import core.memory              : GC;
23     import std.utf                  : byChar, encode; 
24     import std.internal.scopebuffer : ScopeBuffer;
25 
26     this()(auto ref Range range)
27     {
28         this._range = range;
29         initStash();
30     }
31 
32     ~this()
33     {
34         _scratchBuf.free();
35     }
36 
37     @property bool empty()
38     {
39         return _range.empty && _scratchSlice.empty;
40     }
41     
42     @property char front()
43     {
44         if (_scratchSlice.empty)
45             return _range.front;
46         return _scratchSlice.front;
47     }
48 
49     void popFront()
50     {
51         if (_scratchSlice.empty)
52             _range.popFront();
53         else
54             _scratchSlice = _scratchSlice[1 .. _scratchSlice.length];
55     }
56     /// Save current stash as look ahead buffer
57     void save()
58     {
59         if (!_scratchBuf[].empty)
60             _scratchSlice = _scratchBuf[0 .. $];
61     }
62 
63     /// Buffer current char
64     void stash()
65     {
66         // stashing when there is an active look ahead buffer is a no op
67         if (!_scratchSlice.empty)
68             return;
69         _scratchBuf.put(_range.front);
70     }
71     /// Buffer a wide char
72     void stash(dchar c, bool override_ = false)
73     {
74         // stashing when there is an active look ahead buffer is a no op
75         if (!override_ && !_scratchSlice.empty)
76             return;
77         
78         char[4] parts   = void;
79         auto size       = encode(parts, c);
80         foreach (i; 0..size)
81             _scratchBuf.put(parts[i]);
82     }
83     /// Get the curent content of the stash
84     @property const(char)[] crtStash()
85     {
86         return _scratchBuf[];
87     }
88     /// True if stash has items
89     @property bool hasStash()
90     {
91         return !_scratchBuf[].empty;
92     }
93     /// Commit current stash to a string and re-init stash
94     string commitStash()
95     {
96         char[] buf;
97         if (_scratchSlice.length)
98             buf = _scratchBuf[0.. $ - _scratchSlice.length].dup();
99         else
100             buf = _scratchBuf[0..$].dup(); 
101         clearStash();
102         return cast(string) buf;
103     }
104 
105     /// Clears current stash
106     void clearStash()
107     {
108         _scratchBuf.free();
109         initStash();
110     }
111 
112     /**
113     True if suplied string is found in the underlying Input range
114     If not found, it allows the `LookAhead` to continue iteration from
115     the original position the Input range was when the call was performed.
116     */
117     bool find(string search, bool keepStash = false)
118     {
119         import std.range    : chain, refRange;
120 
121         if (search.empty)
122             return false;
123         
124         static struct WrapRangePtr
125         {
126             this(R* r) { this.r = r; }
127             @property bool empty() { return (*r).empty; }
128             @property char front() { return (*r).front; }
129             void popFront() { (*r).popFront(); }
130             
131             alias R = typeof(_range);
132             R* r;
133         }
134         
135         auto bufSlice   = _scratchSlice.byChar();
136         auto wholeRange = chain(refRange(&bufSlice), WrapRangePtr(&_range));
137         size_t count    = 0;
138         while (!wholeRange.empty)
139         {
140             if (count >= search.length || search[count] != wholeRange.front)
141                 break;
142             if (bufSlice.empty)
143                 stash(wholeRange.front, true);
144             wholeRange.popFront;
145             count++;
146         }
147         bool found = (count == search.length);
148         if (!found && hasStash) // keep look ahead buffer
149             save();
150         else if (!keepStash) // init stash buffer
151             clearStash();
152         return found;
153     }
154 
155     @property ref Range range()
156     {
157         return _range;
158     }
159 
160     @property void range(ref Range range)
161     {
162         this._range = range;
163     }
164 
165     // members
166 private:
167 
168     void initStash()
169     {
170         _scratchBuf     = ScopeBuffer!char();
171         _scratchSlice   = _scratchBuf[];
172     }
173 
174     // The underlying Input range that is iterated
175     Range _range                    = void;
176     // A slice on the stash, allows to create a look ahead buffer
177     char[] _scratchSlice           = void;
178     // Scope buffer used for stashing items
179     ScopeBuffer!char _scratchBuf    = void;
180 }
181 unittest
182 {
183     alias Buf = LookAhead!string;
184     auto buf = Buf("123456789");
185     assert(buf.front == '1');
186     buf.stash();
187     buf.save();
188     assert(buf.crtStash == "1");
189     buf.popFront();
190     assert(buf.front == '1');
191     buf.popFront();
192     assert(buf.front == '2');
193     buf.clearStash();
194     foreach (i; 0..3)
195     {
196         buf.stash();
197         buf.popFront();
198     }
199     assert(buf.crtStash == "234");
200     buf.save();
201     foreach (i; 0..3)
202     {
203         assert(buf.front == '0' + i + 2);
204         buf.popFront();
205     }
206 
207     buf = Buf("abc");
208     assert(buf.front == 'a');
209     buf.stash();
210     buf.popFront();
211     buf.save();
212 
213     assert(!buf.find("a23"));
214 
215     assert(buf._scratchSlice == "a");
216     assert(buf._scratchBuf[] == "a");
217 
218     assert(buf.find("abc", true));
219     assert(buf._scratchBuf[] == "abc");
220     
221     buf = Buf("zy亚");
222     assert(buf.find("zy亚", true));
223 
224 }
225 
226 ///////////////////////////////////////////////////////////////
227 //
228 // Non decoding char[] range primitives
229 //
230 ///////////////////////////////////////////////////////////////
231 
232 @property char front()(inout(char)[] range)
233 {
234     assert(!range.empty);
235     return range[0];
236 }
237 
238 @property bool empty()(inout(char)[] range)
239 {
240     return !range.length;
241 }
242 
243 @property void popFront(ref inout(char)[] range)
244 {
245     assert(!range.empty);
246     range = range[1..$];
247 }
248 
249 /**
250 Owns the type T memory ensuring that it is not copyable
251 and that T constructed memory is freed and T is destroyed at end of scope.
252 */
253 struct Own(T) if (is (T == struct))
254 {
255     import core.memory      : GC;
256     import std.conv         : emplace;
257     import std.typecons     : Proxy;
258     import std.algorithm    : moveEmplace;
259     import std.traits       : hasIndirections, hasMember;
260     import core.stdc.stdlib : malloc, free;
261 
262     this(T t)
263     {
264         val = cast(T*) malloc(T.sizeof);
265         moveEmplace(t, *val);
266         static if (hasIndirections!T)
267             GC.addRange(val, T.sizeof);
268     }
269 
270     this(Args...)(auto ref Args args)
271     {
272         val = cast(T*) malloc(T.sizeof);
273         emplace(val, args);
274         static if (hasIndirections!T)
275             GC.addRange(val, T.sizeof);
276     }
277 
278     ~this()
279     {
280         if (this.val is null)
281             return;
282 
283         static if (hasMember!(T, "__dtor"))
284             val.__dtor();
285         free(val);
286         GC.removeRange(val);
287         destroy(val);
288     }
289 
290     void opAssign(T)(T o)
291     {
292         destroy(this);
293         val = cast(T*) malloc(T.sizeof);
294         moveEmplace(o, *val);
295         static if (hasIndirections!T)
296             GC.addRange(val, T.sizeof);
297     }
298 
299     void opAssign(Own!T o)
300     {
301         destroy(this);
302         val = o.val;
303         o.val = null;
304     }
305     
306     @property bool isNull() pure const
307     {
308         return this.val is null; 
309     }
310 
311     bool opEquals()(auto ref const Own!T other) const
312     {
313         if (this.isNull || other.isNull)
314         {
315             if (this.isNull && other.isNull)
316                 return true;
317             return false;
318         }
319         return *this.val == *other.val;
320     }
321 
322     static if (hasMember!(T, "toHash"))
323     @safe size_t toHash() const nothrow
324     {
325         if (isNull)
326             return 0;
327         return (*val).toHash();
328     }
329 
330     mixin Proxy!val;
331 
332 private:
333     T* val;
334     @disable this(this);
335 }
336 
337 unittest
338 {
339     import std.algorithm : move;
340     struct X
341     {
342         bool b;
343     }
344 
345     Own!X x;
346     x = Own!X(true);
347     assert(x.b);
348     Own!X y = Own!X(true); 
349     x = y.move();
350     assert(y.isNull);
351     x.b = false;
352     auto z = Own!X(*x.move());
353     assert(x.isNull);
354     assert(!z.b);
355 
356     Own!X w = true;
357     Own!X v;
358 
359     assert(w != v);
360 
361     int i;
362     struct C
363     {
364         this(ref int i)
365         {
366             ++i;
367             ii = &i;
368         }
369 
370         @disable this();
371         @disable this(this);
372         
373         ~this()
374         {
375             if (ii !is null)
376                 --(*ii);
377         }
378         int* ii;
379     }
380 
381     {
382         Own!C dd = Own!C(i);
383         auto c = dd.move();
384     }
385     assert(i == 0);
386 
387     struct S
388     {
389         union U
390         {
391             Own!C oc;
392             C c;
393         }
394 
395         U u = void;
396         enum Type { o, c }
397 
398         Type type;
399 
400         ~this()
401         {
402             if (type == Type.o)
403                 destroy(u.oc);
404 
405             if (type == Type.c)
406                 destroy(u.c);
407         }
408     }
409 
410     {
411         S s;
412         s.type  = S.Type.o;
413         s.u.oc   = Own!C(i);
414     }
415     assert(i == 0);
416 
417     {
418         S s;
419         s.type  = S.Type.c;
420         s.u.c = C(i);
421     }
422     assert(i == 0);
423 }
424 
425 /**
426 Implements a safe discriminating union, aka sum type.
427 Accepts the type `Type` as an enum listing the supporting types.
428 Each enum filed name is considered as an alias for an actual type.
429 */
430 mixin template SumType(alias Type)
431 {
432     import std.traits       : EnumMembers, hasMember;
433     import std.conv         : to;
434     import std.meta         : AliasSeq, ApplyRight, Filter, staticMap, staticIndexOf;
435 
436     /// Value of the empty type
437     enum emptyType = cast(Type) uint.max;
438 
439     // Type names as string of the allowed types
440     alias AllowedTypeNames = staticMap!(aliasToStr, EnumMembers!Type);
441 
442     /// All allowed `Tag` types
443     alias AllowedTypes  = staticMap!(StrToSymbol, AllowedTypeNames);
444 
445     // Sanity check
446     static assert(AllowedTypes.length == EnumMembers!Type.length,
447                   "Missmatch sizes of `AllowedTypes` and specified `Type` enum " ~ to!string(Type));
448 
449     /// Check if type `T` is a supported type
450     enum allowed(T) = staticIndexOf!(T, AllowedTypes) != -1;
451 
452     /**
453     Returns the current `Type`
454     */
455     Type type() pure inout @safe nothrow
456     {
457         return curType;
458     }
459 
460     /**
461     Returns true if no value is stored
462     */
463     bool empty() pure inout @safe nothrow
464     {
465         return curType == emptyType;
466     }
467 
468 private:
469 
470     // Converts symbol to string
471     enum aliasToStr(alias T) = to!string(T);
472 
473     // Convert a string to symbol
474     template StrToSymbol(alias S)
475     {
476         mixin(`alias StrToSymbol = `~to!string(S)~`;`);
477     }
478 
479     // Get the `Value` field name for an allowed type `T`
480     enum valNameForType(T) = `val` ~ AllowedTypeNames[staticIndexOf!(T, AllowedTypes)];
481     // Get the `Type` enum value for an allowed type `T`
482     alias TagTypeForType(T) = EnumMembers!Type[staticIndexOf!(T, AllowedTypes)];
483 
484     // Check if one of the `AllowedType`s has a given member
485     template TypeHasMemeber(string name)
486     {
487         static if (name != "length" && hasMember(Tag, name))
488         {
489             enum TypeHasMemeber = false;
490         }
491         else
492         {
493             enum hasFunc(T) = hasMember!(T, name);
494             alias SupportingTypes = Filter!(ApplyRight!(hasFunc), AllowedTypes);
495 
496             static if (SupportingTypes.length != 0)
497                 enum TypeHasMemeber = true;
498             else
499                 enum TypeHasMemeber = false;
500         }
501     }
502 
503     /// Clears the current value
504     void clearCurValue() pure nothrow
505     {
506         if (empty)
507             return;
508 
509         static foreach (T; AllowedTypes)
510         {
511             if (curType == TagTypeForType!T)
512                 mixin(`return value.`~valNameForType!T~`.destroy();`);
513         }
514     }
515 
516     /// Sets the value for the type
517     void setValueForType(T)(auto ref T t)
518     {
519         mixin(`value.`~valNameForType!T~` = t;`);
520     }
521 
522     /// Gets the value for the type
523     T getValueForType(T)() pure inout nothrow
524     {
525         mixin(`return cast(T) value.`~valNameForType!T~`;`);
526     }
527 
528     // Storage for all tag type
529     union Value
530     {
531         // generate members for all allowed types
532         static foreach (T; AllowedTypes)
533             mixin(T.stringof ~ ` ` ~ valNameForType!T ~ `;`);
534     }
535 
536     Value value     = void;
537     Type curType    = emptyType;
538 }