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 }