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 	import std.uni : isUpper, isWhite;
103 	alias sepPred = ch => (ch == '-' || ch.isWhite);
104 	assert(equal("doThis or doThat do-stuff".preSlicer!(_ => (_.isUpper ||
105 															  sepPred(_)))
106 								   .map!(word => (word.length >= 1 &&
107 												  sepPred(word[0]) ?
108 												  word[1 .. $] :
109 												  word)),
110 				 ["do", "This", "or", "do", "That", "do", "stuff"]));
111 
112 	assert(equal("isAKindOf".preSlicer!isUpper, ["is", "A", "Kind", "Of"]));
113 
114 	assert(equal("doThis".preSlicer!isUpper, ["do", "This"]));
115 
116 	assert(equal("doThisIf".preSlicer!isUpper, ["do", "This", "If"]));
117 
118 	assert(equal("utcOffset".preSlicer!isUpper, ["utc", "Offset"]));
119 	assert(equal("isUri".preSlicer!isUpper, ["is", "Uri"]));
120 	/+ TODO: assert(equal("baseSIUnit".preSlicer!isUpper, ["base", "SI", "Unit"])); +/
121 
122 	assert(equal("SomeGreatVariableName".preSlicer!isUpper, ["Some", "Great", "Variable", "Name"]));
123 	assert(equal("someGGGreatVariableName".preSlicer!isUpper, ["some", "G", "G", "Great", "Variable", "Name"]));
124 
125 	string[] e;
126 	assert(equal("".preSlicer!isUpper, e));
127 	assert(equal("a".preSlicer!isUpper, ["a"]));
128 	assert(equal("A".preSlicer!isUpper, ["A"]));
129 	assert(equal("A".preSlicer!isUpper, ["A"]));
130 	assert(equal("ö".preSlicer!isUpper, ["ö"]));
131 	assert(equal("åa".preSlicer!isUpper, ["åa"]));
132 	assert(equal("aå".preSlicer!isUpper, ["aå"]));
133 	assert(equal("åäö".preSlicer!isUpper, ["åäö"]));
134 	assert(equal("aB".preSlicer!isUpper, ["a", "B"]));
135 	assert(equal("äB".preSlicer!isUpper, ["ä", "B"]));
136 	assert(equal("aäB".preSlicer!isUpper, ["aä", "B"]));
137 	assert(equal("äaB".preSlicer!isUpper, ["äa", "B"]));
138 	assert(equal("äaÖ".preSlicer!isUpper, ["äa", "Ö"]));
139 
140 	assert(equal([1, -1, 1, -1].preSlicer!(a => a > 0), [[1, -1], [1, -1]]));
141 
142 	/* TODO: Add bidir support */
143 	/* import std.range : retro; */
144 	/* assert(equal([-1, 1, -1, 1].retro.preSlicer!(a => a > 0), [[1, -1], [1, -1]])); */
145 }
146 
147 version (none)				   /+ TODO: enable +/
148 auto wordByMixedCaseSubWord(Range)(Range r)
149 {
150 	static struct Result
151 	{
152 		this(Range input)
153 		{
154 			_input = input;
155 			import std.range.primitives : empty;
156 			if (_input.empty)
157 				_end = size_t.max;
158 			else
159 				skipTerminatorsAndSetEnd();
160 		}
161 
162 		@property bool empty() => _end == size_t.max;
163 
164 		@property auto front() => _input[0 .. _end];
165 
166 		void popFront()
167 		{
168 			_input = _input[_end .. $];
169 			import std.range.primitives : empty;
170 			if (_input.empty)
171 			{
172 				_end = size_t.max;
173 				return;
174 			}
175 			skipTerminatorsAndSetEnd();
176 		}
177 
178 		private void skipTerminatorsAndSetEnd()
179 		{
180 			// `_end` is now invalid in relation to `_input`
181 			size_t offset = 0;
182 			while (offset != _input.length)
183 			{
184 				auto slice = _input[offset .. $];
185 				import std.utf : decodeFront;
186 				size_t numCodeUnits;
187 				const dchar dch = decodeFront(slice, numCodeUnits);
188 				if (offset != 0 && // ignore terminator at offset 0
189 					isTerminator(dch))
190 					break;
191 				offset += numCodeUnits; // skip over
192 			}
193 			_end = offset;
194 		}
195 
196 		private Range _input;
197 		private size_t _end = 0;	// _input[0 .. _end] is current front
198 	}
199 	return Result(r);
200 }
201 
202 version (none)				   /+ TODO: enable +/
203 pure @safe unittest {
204 	assert(equal("äaÖ".wordByMixedCaseSubWord, ["äa", "Ö"]));
205 }
206 
207 version (unittest)
208 {
209 	 import std.algorithm.comparison : equal;
210 	 import std.algorithm.iteration : map;
211 }