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 }