1 module nxt.slicing;
2 
3 /** Slice at all positions where $(D isTerminator) is $(D false) before current
4     element and $(D true) at current.
5 
6     TODO: Can this be replaced by chunkBy
7     See_Also: http://dlang.org/library/std/algorithm/splitter.html.
8     See_Also: http://forum.dlang.org/post/cwqeywykubsuynkidlux@forum.dlang.org
9 */
10 auto preSlicer(alias isTerminator, R)(R input)
11 /* if (((isRandomAccessRange!R && */
12 /*       hasSlicing!R) || */
13 /*      isSomeString!R) && */
14 /*     is(typeof(unaryFun!isTerminator(input.front)))) */
15 {
16     import std.functional : unaryFun;
17     return PreSlicer!(unaryFun!isTerminator, R)(input);
18 }
19 
20 private struct PreSlicer(alias isTerminator, R)
21 {
22     this(R input)
23     {
24         _input = input;
25         import std.range.primitives : empty;
26         if (_input.empty)
27             _end = size_t.max;
28         else
29             skipTerminatorsAndSetEnd();
30     }
31 
32     import std.range.primitives : isInfinite;
33 
34     static if (isInfinite!R)
35         enum bool empty = false;  // propagate infiniteness
36     else
37         @property bool empty() => _end == size_t.max;
38 
39     @property auto front() => _input[0 .. _end];
40 
41     void popFront()
42     {
43         _input = _input[_end .. $];
44         import std.range.primitives : empty;
45         if (_input.empty)
46         {
47             _end = size_t.max;
48             return;
49         }
50         skipTerminatorsAndSetEnd();
51     }
52 
53     @property PreSlicer save()
54     {
55         auto ret = this;
56         import std.range.primitives : save;
57         ret._input = _input.save;
58         return ret;
59     }
60 
61     private void skipTerminatorsAndSetEnd()
62     {
63         // `_end` is now invalid in relation to `_input`
64         alias ElementEncodingType = typeof(_input[0]);
65         static if (is(ElementEncodingType : char) ||
66                    is(ElementEncodingType : wchar))
67         {
68             size_t offset = 0;
69             while (offset != _input.length)
70             {
71                 auto slice = _input[offset .. $];
72                 import std.utf : decodeFront;
73                 size_t numCodeUnits;
74                 const dchar dch = decodeFront(slice, numCodeUnits);
75                 if (offset != 0 && // ignore terminator at offset 0
76                     isTerminator(dch))
77                     break;
78                 offset += numCodeUnits; // skip over
79             }
80             _end = offset;
81         }
82         else
83         {
84             size_t offset = 0;
85             if (isTerminator(_input[0]))
86                 offset += 1;        // skip over it
87             import std.algorithm : countUntil;
88             const count = _input[offset .. $].countUntil!isTerminator();
89             if (count == -1)        // end reached
90                 _end = _input.length;
91             else
92                 _end = offset + count;
93         }
94     }
95 
96     private R _input;
97     private size_t _end = 0;    // _input[0 .. _end] is current front
98 }
99 alias preSplitter = preSlicer;
100 
101 unittest
102 {
103     import std.uni : isUpper, isWhite;
104     alias sepPred = ch => (ch == '-' || ch.isWhite);
105     assert(equal("doThis or doThat do-stuff".preSlicer!(_ => (_.isUpper ||
106                                                               sepPred(_)))
107                                    .map!(word => (word.length >= 1 &&
108                                                   sepPred(word[0]) ?
109                                                   word[1 .. $] :
110                                                   word)),
111                  ["do", "This", "or", "do", "That", "do", "stuff"]));
112 
113     assert(equal("isAKindOf".preSlicer!isUpper, ["is", "A", "Kind", "Of"]));
114 
115     assert(equal("doThis".preSlicer!isUpper, ["do", "This"]));
116 
117     assert(equal("doThisIf".preSlicer!isUpper, ["do", "This", "If"]));
118 
119     assert(equal("utcOffset".preSlicer!isUpper, ["utc", "Offset"]));
120     assert(equal("isUri".preSlicer!isUpper, ["is", "Uri"]));
121     // TODO: assert(equal("baseSIUnit".preSlicer!isUpper, ["base", "SI", "Unit"]));
122 
123     assert(equal("SomeGreatVariableName".preSlicer!isUpper, ["Some", "Great", "Variable", "Name"]));
124     assert(equal("someGGGreatVariableName".preSlicer!isUpper, ["some", "G", "G", "Great", "Variable", "Name"]));
125 
126     string[] e;
127     assert(equal("".preSlicer!isUpper, e));
128     assert(equal("a".preSlicer!isUpper, ["a"]));
129     assert(equal("A".preSlicer!isUpper, ["A"]));
130     assert(equal("A".preSlicer!isUpper, ["A"]));
131     assert(equal("ö".preSlicer!isUpper, ["ö"]));
132     assert(equal("åa".preSlicer!isUpper, ["åa"]));
133     assert(equal("aå".preSlicer!isUpper, ["aå"]));
134     assert(equal("åäö".preSlicer!isUpper, ["åäö"]));
135     assert(equal("aB".preSlicer!isUpper, ["a", "B"]));
136     assert(equal("äB".preSlicer!isUpper, ["ä", "B"]));
137     assert(equal("aäB".preSlicer!isUpper, ["aä", "B"]));
138     assert(equal("äaB".preSlicer!isUpper, ["äa", "B"]));
139     assert(equal("äaÖ".preSlicer!isUpper, ["äa", "Ö"]));
140 
141     assert(equal([1, -1, 1, -1].preSlicer!(a => a > 0), [[1, -1], [1, -1]]));
142 
143     /* TODO: Add bidir support */
144     /* import std.range : retro; */
145     /* assert(equal([-1, 1, -1, 1].retro.preSlicer!(a => a > 0), [[1, -1], [1, -1]])); */
146 }
147 
148 version(none)                   // TODO: enable
149 auto wordByMixedCaseSubWord(Range)(Range r)
150 {
151     static struct Result
152     {
153         this(Range input)
154         {
155             _input = input;
156             import std.range.primitives : empty;
157             if (_input.empty)
158                 _end = size_t.max;
159             else
160                 skipTerminatorsAndSetEnd();
161         }
162 
163         @property bool empty() => _end == size_t.max;
164 
165         @property auto front() => _input[0 .. _end];
166 
167         void popFront()
168         {
169             _input = _input[_end .. $];
170             import std.range.primitives : empty;
171             if (_input.empty)
172             {
173                 _end = size_t.max;
174                 return;
175             }
176             skipTerminatorsAndSetEnd();
177         }
178 
179         private void skipTerminatorsAndSetEnd()
180         {
181             // `_end` is now invalid in relation to `_input`
182             size_t offset = 0;
183             while (offset != _input.length)
184             {
185                 auto slice = _input[offset .. $];
186                 import std.utf : decodeFront;
187                 size_t numCodeUnits;
188                 const dchar dch = decodeFront(slice, numCodeUnits);
189                 if (offset != 0 && // ignore terminator at offset 0
190                     isTerminator(dch))
191                     break;
192                 offset += numCodeUnits; // skip over
193             }
194             _end = offset;
195         }
196 
197         private Range _input;
198         private size_t _end = 0;    // _input[0 .. _end] is current front
199     }
200     return Result(r);
201 }
202 
203 version(none)                   // TODO: enable
204 @safe pure unittest
205 {
206     assert(equal("äaÖ".wordByMixedCaseSubWord, ["äa", "Ö"]));
207 }
208 
209 version(unittest)
210 {
211      import std.algorithm.comparison : equal;
212      import std.algorithm.iteration : map;
213 }