1 module bufref; 2 3 import std.range.primitives; 4 5 // arrays and random-access non-infinite ranges are buffers. 6 template isBuffer(B) 7 { 8 import std.range.primitives : isRandomAccessRange, isInfinite; 9 import std.traits : isDynamicArray; 10 enum isBuffer = isDynamicArray!B || (isRandomAccessRange!B && !isInfinite!B); 11 } 12 13 struct BufRef 14 { 15 size_t pos; 16 size_t length; 17 } 18 19 enum isBufRef(alias item) = is(typeof(item) == BufRef); 20 enum isBufRefRange(alias item) = isInputRange!(typeof(item)) && is(ElementType!(typeof(item)) == BufRef); 21 22 template hasBufRefs(T) 23 { 24 // type has at least one bufRef item as a member 25 static if(is(T == struct)) 26 { 27 import std.meta : anySatisfy; 28 enum hasBufRefs = anySatisfy!(isBufRef, T.tupleof) || anySatisfy!(isBufRefRange, T.tupleof); 29 } 30 else 31 enum hasBufRefs = false; 32 } 33 34 35 unittest 36 { 37 static struct S1 38 { 39 string member1; 40 BufRef member2; 41 int member3; 42 } 43 44 static assert(hasBufRefs!S1); 45 46 static struct S2 47 { 48 int member1; 49 string member2; 50 } 51 52 static assert(!hasBufRefs!S2); 53 54 static struct S3 55 { 56 BufRef[] arr; 57 } 58 59 static assert(hasBufRefs!S3); 60 } 61 62 // Wrapper for a BufRef range that exposes elements as concrete buffer slices. 63 // Does not make any copies of items. 64 struct ConcreteRange(R, W) if (isInputRange!R && is(ElementType!R == BufRef) && isBuffer!W) 65 { 66 R r; 67 W w; 68 auto front() 69 { 70 return r.front.concrete(w); 71 } 72 73 void popFront() 74 { 75 r.popFront; 76 } 77 78 bool empty() 79 { 80 return r.empty; 81 } 82 83 static if(isForwardRange!R) 84 { 85 ConcreteRange save() 86 { 87 return ConcreteRange(r.save, w); 88 } 89 } 90 91 static if(isBidirectionalRange!R) 92 { 93 auto back() 94 { 95 return r.back.concrete(w); 96 } 97 98 void popBack() 99 { 100 r.popBack; 101 } 102 } 103 104 static if(hasLength!R) 105 { 106 size_t length() 107 { 108 return r.length; 109 } 110 111 auto opDollar(size_t dim)() if (dim == 0) 112 { 113 return length; 114 } 115 } 116 117 static if(is(typeof(r[0]))) 118 { 119 auto opIndex(size_t idx) 120 { 121 return r[idx].concrete(w); 122 } 123 } 124 125 static if(hasSlicing!R) 126 { 127 size_t[2] opSlice(size_t dim)(size_t start, size_t end) if (dim == 0) 128 { 129 return [start, end]; 130 } 131 132 auto opIndex(size_t[2] idx) 133 { 134 return ConcreteRange(r[idx[0] .. idx[1]], w); 135 } 136 } 137 } 138 139 // get a concrete represenation of the buffer reference 140 auto concrete(B)(const(BufRef) bref, B window) if (isBuffer!B) 141 { 142 return window[bref.pos .. bref.pos + bref.length]; 143 } 144 145 // get a concrete range from a range of buffer references. 146 auto concrete(R, B)(R brefRange, B window) if (isBufRefRange!brefRange && isBuffer!B) 147 { 148 return ConcreteRange!(R, B)(brefRange, window); 149 } 150 151 // get a concrete representation of an item that contains buffer references. 152 // DOES NOT copy functions. 153 auto concrete(T, B)(auto ref T item, B window) if (hasBufRefs!T && isBuffer!B) 154 { 155 BufRef test; 156 alias SliceType = typeof(BufRef.init.concrete(window)); 157 static string resultstr() 158 { 159 string result = "static struct Concrete" ~ T.stringof ~ " {"; 160 static foreach(i; 0 .. T.tupleof.length) 161 {{ 162 alias TE = typeof(T.tupleof[i]); 163 static if(is(TE == BufRef)) 164 { 165 // replace with the slice type 166 result ~= "SliceType " ~ __traits(identifier, T.tupleof[i]) ~ ";"; 167 } 168 else static if(isInputRange!TE && is(ElementType!TE == BufRef)) 169 { 170 result ~= "ConcreteRange!( " ~ TE.stringof ~ ",B) " ~ __traits(identifier, T.tupleof[i]) ~ ";"; 171 } 172 else 173 { 174 result ~= typeof(T.tupleof[i]).stringof ~ " " ~ __traits(identifier, T.tupleof[i]) ~ ";"; 175 } 176 }} 177 result ~= "} alias Result = Concrete" ~ T.stringof ~ ";"; 178 return result; 179 } 180 mixin(resultstr()); 181 Result r; 182 foreach(i, ref it; item.tupleof) 183 { 184 static if(isBufRef!(item.tupleof[i]) || isBufRefRange!(item.tupleof[i])) 185 { 186 r.tupleof[i] = it.concrete(window); 187 } 188 else 189 { 190 r.tupleof[i] = it; 191 } 192 } 193 return r; 194 } 195 196 unittest 197 { 198 static struct S 199 { 200 int i; 201 BufRef b; 202 long l; 203 BufRef[] barr; 204 } 205 S s; 206 s.b = BufRef(0, 10); 207 s.barr = [BufRef(0, 2), BufRef(1, 2), BufRef(2, 2)]; 208 auto c = s.concrete("0123456789012345"); 209 assert(c.b == "0123456789"); 210 assert(c.barr.length == 3); 211 assert(c.barr[0] == "01"); 212 assert(c.barr[1] == "12"); 213 assert(c.barr[2] == "23"); 214 assert(c.barr[1 .. $][0] == "12"); 215 } 216 217 // adjust the pos of the reference inside the buffer. 218 void adjustPos(ref BufRef bref, ptrdiff_t adjustment) 219 { 220 assert(cast(ptrdiff_t)(bref.pos + adjustment) >= 0); 221 bref.pos += adjustment; 222 } 223 224 // adjust the pos of all the buffer references inside the item. 225 void adjustPos(T)(ref T item, ptrdiff_t adjustment) if (hasBufRefs!T) 226 { 227 foreach(i, ref it; item.tupleof) 228 { 229 static if(is(typeof(it) == BufRef)) 230 { 231 it.adjustPos(adjustment); 232 } 233 else static if(is(typeof(it[]) == BufRef[])) 234 { 235 foreach(ref br; it[]) 236 br.adjustPos(adjustment); 237 } 238 else static if(isBufRefRange!(item.tupleof[i]) && isForwardRange!(typeof(it)) && hasLvalueElements!(typeof(it))) 239 { 240 foreach(ref br; it.save) 241 br.adjustPos(adjustment); 242 } 243 } 244 } 245 246 // a buffer window. This wraps a buffer into a range that knows what position 247 // it's at in the original window. 248 auto bwin(B)(B window) if (isBuffer!B) 249 { 250 static struct Result 251 { 252 private size_t pos; 253 private B _buffer; 254 255 import std.traits : isNarrowString; 256 static if(isNarrowString!B) 257 { 258 auto ref front() 259 { 260 return _buffer[0]; 261 } 262 263 void popFront() 264 { 265 ++pos; 266 _buffer = _buffer[1 .. $]; 267 } 268 269 auto ref back() 270 { 271 return _buffer[$-1]; 272 } 273 274 void popBack() 275 { 276 _buffer = _buffer[0 .. $-1]; 277 } 278 } 279 else 280 { 281 auto ref front() 282 { 283 return _buffer.front; 284 } 285 286 void popFront() 287 { 288 ++pos; 289 _buffer.popFront; 290 } 291 292 auto ref back() 293 { 294 return _buffer.back; 295 } 296 297 void popBack() 298 { 299 _buffer.popBack; 300 } 301 } 302 303 size_t length() 304 { 305 return _buffer.length; 306 } 307 308 auto ref opIndex(size_t idx) 309 { 310 return _buffer[idx]; 311 } 312 313 // implement the slice operations 314 size_t[2] opSlice(size_t dim)(size_t start, size_t end) if (dim == 0) 315 in 316 { assert(start >= 0 && end <= _buffer.length); } 317 do 318 { 319 return [start, end]; 320 } 321 322 Result opIndex(size_t[2] dims) 323 { 324 return Result(pos + dims[0], _buffer[dims[0] .. dims[1]]); 325 } 326 327 Result save() 328 { 329 return this; 330 } 331 332 bool empty() 333 { 334 return _buffer.empty; 335 } 336 337 // the specialized buffer reference accessor. 338 @property auto bufRef() 339 { 340 return BufRef(pos, _buffer.length); 341 } 342 } 343 344 return Result(0, window); 345 } 346 347 unittest 348 { 349 import std.algorithm : splitter, equal; 350 auto buf = "hi there this is a sentence"; 351 auto split1 = buf.bwin.splitter(" "); 352 353 auto split2 = buf.splitter; 354 while(!split1.empty) 355 { 356 assert(split1.front.equal(split2.front)); 357 assert(split1.front.bufRef.concrete(buf) == split2.front); 358 split1.popFront; 359 split2.popFront; 360 } 361 }