1 // Written in the D programming language. 2 /** 3 Haystack misc utils. 4 5 Copyright: Copyright (c) 2017, Radu Racariu <radu.racariu@gmail.com> 6 License: $(LINK2 www.boost.org/LICENSE_1_0.txt, Boost License 1.0) 7 Authors: Radu Racariu 8 **/ 9 module haystack.util.misc; 10 import std.traits : isSomeChar; 11 import std.range.primitives : isInputRange, 12 ElementEncodingType; 13 14 enum isCharInputRange(Range) = isInputRange!Range && isSomeChar!(ElementEncodingType!Range); 15 16 /** 17 A char range that allows look ahead buffering and can also collect a buffer of items 18 **/ 19 struct LookAhead(Range) 20 if (isCharInputRange!Range) 21 { 22 import core.memory : GC; 23 import std.utf : byChar, encode; 24 import std.internal.scopebuffer : ScopeBuffer; 25 26 this()(auto ref Range range) 27 { 28 this._range = range; 29 initStash(); 30 } 31 32 ~this() 33 { 34 _scratchBuf.free(); 35 } 36 37 @property bool empty() 38 { 39 return _range.empty && _scratchSlice.empty; 40 } 41 42 @property char front() 43 { 44 if (_scratchSlice.empty) 45 return _range.front; 46 return _scratchSlice.front; 47 } 48 49 void popFront() 50 { 51 if (_scratchSlice.empty) 52 _range.popFront(); 53 else 54 _scratchSlice = _scratchSlice[1 .. _scratchSlice.length]; 55 } 56 /// Save current stash as look ahead buffer 57 void save() 58 { 59 if (!_scratchBuf[].empty) 60 _scratchSlice = _scratchBuf[0 .. $]; 61 } 62 63 /// Buffer current char 64 void stash() 65 { 66 // stashing when there is an active look ahead buffer is a no op 67 if (!_scratchSlice.empty) 68 return; 69 _scratchBuf.put(_range.front); 70 } 71 /// Buffer a wide char 72 void stash(dchar c, bool override_ = false) 73 { 74 // stashing when there is an active look ahead buffer is a no op 75 if (!override_ && !_scratchSlice.empty) 76 return; 77 78 char[4] parts = void; 79 auto size = encode(parts, c); 80 foreach (i; 0..size) 81 _scratchBuf.put(parts[i]); 82 } 83 /// Get the curent content of the stash 84 @property const(char)[] crtStash() 85 { 86 return _scratchBuf[]; 87 } 88 /// True if stash has items 89 @property bool hasStash() 90 { 91 return !_scratchBuf[].empty; 92 } 93 /// Commit current stash to a string and re-init stash 94 string commitStash() 95 { 96 char[] buf; 97 if (_scratchSlice.length) 98 buf = _scratchBuf[0.. $ - _scratchSlice.length].dup(); 99 else 100 buf = _scratchBuf[0..$].dup(); 101 clearStash(); 102 return cast(string) buf; 103 } 104 105 /// Clears current stash 106 void clearStash() 107 { 108 _scratchBuf.free(); 109 initStash(); 110 } 111 112 /** 113 True if suplied string is found in the underlying Input range 114 If not found, it allows the `LookAhead` to continue iteration from 115 the original position the Input range was when the call was performed. 116 */ 117 bool find(string search, bool keepStash = false) 118 { 119 import std.range : chain, refRange; 120 121 if (search.empty) 122 return false; 123 124 static struct WrapRangePtr 125 { 126 this(R* r) { this.r = r; } 127 @property bool empty() { return (*r).empty; } 128 @property char front() { return (*r).front; } 129 void popFront() { (*r).popFront(); } 130 131 alias R = typeof(_range); 132 R* r; 133 } 134 135 auto bufSlice = _scratchSlice.byChar(); 136 auto wholeRange = chain(refRange(&bufSlice), WrapRangePtr(&_range)); 137 size_t count = 0; 138 while (!wholeRange.empty) 139 { 140 if (count >= search.length || search[count] != wholeRange.front) 141 break; 142 if (bufSlice.empty) 143 stash(wholeRange.front, true); 144 wholeRange.popFront; 145 count++; 146 } 147 bool found = (count == search.length); 148 if (!found && hasStash) // keep look ahead buffer 149 save(); 150 else if (!keepStash) // init stash buffer 151 clearStash(); 152 return found; 153 } 154 155 @property ref Range range() 156 { 157 return _range; 158 } 159 160 @property void range(ref Range range) 161 { 162 this._range = range; 163 } 164 165 // members 166 private: 167 168 void initStash() 169 { 170 _scratchBuf = ScopeBuffer!char(); 171 _scratchSlice = _scratchBuf[]; 172 } 173 174 // The underlying Input range that is iterated 175 Range _range = void; 176 // A slice on the stash, allows to create a look ahead buffer 177 char[] _scratchSlice = void; 178 // Scope buffer used for stashing items 179 ScopeBuffer!char _scratchBuf = void; 180 } 181 unittest 182 { 183 alias Buf = LookAhead!string; 184 auto buf = Buf("123456789"); 185 assert(buf.front == '1'); 186 buf.stash(); 187 buf.save(); 188 assert(buf.crtStash == "1"); 189 buf.popFront(); 190 assert(buf.front == '1'); 191 buf.popFront(); 192 assert(buf.front == '2'); 193 buf.clearStash(); 194 foreach (i; 0..3) 195 { 196 buf.stash(); 197 buf.popFront(); 198 } 199 assert(buf.crtStash == "234"); 200 buf.save(); 201 foreach (i; 0..3) 202 { 203 assert(buf.front == '0' + i + 2); 204 buf.popFront(); 205 } 206 207 buf = Buf("abc"); 208 assert(buf.front == 'a'); 209 buf.stash(); 210 buf.popFront(); 211 buf.save(); 212 213 assert(!buf.find("a23")); 214 215 assert(buf._scratchSlice == "a"); 216 assert(buf._scratchBuf[] == "a"); 217 218 assert(buf.find("abc", true)); 219 assert(buf._scratchBuf[] == "abc"); 220 221 buf = Buf("zy亚"); 222 assert(buf.find("zy亚", true)); 223 224 } 225 226 /////////////////////////////////////////////////////////////// 227 // 228 // Non decoding char[] range primitives 229 // 230 /////////////////////////////////////////////////////////////// 231 232 @property char front()(inout(char)[] range) 233 { 234 assert(!range.empty); 235 return range[0]; 236 } 237 238 @property bool empty()(inout(char)[] range) 239 { 240 return !range.length; 241 } 242 243 @property void popFront(ref inout(char)[] range) 244 { 245 assert(!range.empty); 246 range = range[1..$]; 247 } 248 249 /** 250 Owns the type T memory ensuring that it is not copyable 251 and that T constructed memory is freed and T is destroyed at end of scope. 252 */ 253 struct Own(T) if (is (T == struct)) 254 { 255 import core.memory : GC; 256 import std.conv : emplace; 257 import std.typecons : Proxy; 258 import std.algorithm : moveEmplace; 259 import std.traits : hasIndirections, hasMember; 260 import core.stdc.stdlib : malloc, free; 261 262 this(T t) 263 { 264 val = cast(T*) malloc(T.sizeof); 265 moveEmplace(t, *val); 266 static if (hasIndirections!T) 267 GC.addRange(val, T.sizeof); 268 } 269 270 this(Args...)(auto ref Args args) 271 { 272 val = cast(T*) malloc(T.sizeof); 273 emplace(val, args); 274 static if (hasIndirections!T) 275 GC.addRange(val, T.sizeof); 276 } 277 278 ~this() 279 { 280 if (this.val is null) 281 return; 282 283 static if (hasMember!(T, "__dtor")) 284 val.__dtor(); 285 free(val); 286 GC.removeRange(val); 287 destroy(val); 288 } 289 290 void opAssign(T)(T o) 291 { 292 destroy(this); 293 val = cast(T*) malloc(T.sizeof); 294 moveEmplace(o, *val); 295 static if (hasIndirections!T) 296 GC.addRange(val, T.sizeof); 297 } 298 299 void opAssign(Own!T o) 300 { 301 destroy(this); 302 val = o.val; 303 o.val = null; 304 } 305 306 @property bool isNull() pure const 307 { 308 return this.val is null; 309 } 310 311 bool opEquals()(auto ref const Own!T other) const 312 { 313 if (this.isNull || other.isNull) 314 { 315 if (this.isNull && other.isNull) 316 return true; 317 return false; 318 } 319 return *this.val == *other.val; 320 } 321 322 static if (hasMember!(T, "toHash")) 323 @safe size_t toHash() const nothrow 324 { 325 if (isNull) 326 return 0; 327 return (*val).toHash(); 328 } 329 330 mixin Proxy!val; 331 332 private: 333 T* val; 334 @disable this(this); 335 } 336 337 unittest 338 { 339 import std.algorithm : move; 340 struct X 341 { 342 bool b; 343 } 344 345 Own!X x; 346 x = Own!X(true); 347 assert(x.b); 348 Own!X y = Own!X(true); 349 x = y.move(); 350 assert(y.isNull); 351 x.b = false; 352 auto z = Own!X(*x.move()); 353 assert(x.isNull); 354 assert(!z.b); 355 356 Own!X w = true; 357 Own!X v; 358 359 assert(w != v); 360 361 int i; 362 struct C 363 { 364 this(ref int i) 365 { 366 ++i; 367 ii = &i; 368 } 369 370 @disable this(); 371 @disable this(this); 372 373 ~this() 374 { 375 if (ii !is null) 376 --(*ii); 377 } 378 int* ii; 379 } 380 381 { 382 Own!C dd = Own!C(i); 383 auto c = dd.move(); 384 } 385 assert(i == 0); 386 387 struct S 388 { 389 union U 390 { 391 Own!C oc; 392 C c; 393 } 394 395 U u = void; 396 enum Type { o, c } 397 398 Type type; 399 400 ~this() 401 { 402 if (type == Type.o) 403 destroy(u.oc); 404 405 if (type == Type.c) 406 destroy(u.c); 407 } 408 } 409 410 { 411 S s; 412 s.type = S.Type.o; 413 s.u.oc = Own!C(i); 414 } 415 assert(i == 0); 416 417 { 418 S s; 419 s.type = S.Type.c; 420 s.u.c = C(i); 421 } 422 assert(i == 0); 423 } 424 425 /** 426 Implements a safe discriminating union, aka sum type. 427 Accepts the type `Type` as an enum listing the supporting types. 428 Each enum filed name is considered as an alias for an actual type. 429 */ 430 mixin template SumType(alias Type) 431 { 432 import std.traits : EnumMembers, hasMember; 433 import std.conv : to; 434 import std.meta : AliasSeq, ApplyRight, Filter, staticMap, staticIndexOf; 435 436 /// Value of the empty type 437 enum emptyType = cast(Type) uint.max; 438 439 // Type names as string of the allowed types 440 alias AllowedTypeNames = staticMap!(aliasToStr, EnumMembers!Type); 441 442 /// All allowed `Tag` types 443 alias AllowedTypes = staticMap!(StrToSymbol, AllowedTypeNames); 444 445 // Sanity check 446 static assert(AllowedTypes.length == EnumMembers!Type.length, 447 "Missmatch sizes of `AllowedTypes` and specified `Type` enum " ~ to!string(Type)); 448 449 /// Check if type `T` is a supported type 450 enum allowed(T) = staticIndexOf!(T, AllowedTypes) != -1; 451 452 /** 453 Returns the current `Type` 454 */ 455 Type type() pure inout @safe nothrow 456 { 457 return curType; 458 } 459 460 /** 461 Returns true if no value is stored 462 */ 463 bool empty() pure inout @safe nothrow 464 { 465 return curType == emptyType; 466 } 467 468 private: 469 470 // Converts symbol to string 471 enum aliasToStr(alias T) = to!string(T); 472 473 // Convert a string to symbol 474 template StrToSymbol(alias S) 475 { 476 mixin(`alias StrToSymbol = `~to!string(S)~`;`); 477 } 478 479 // Get the `Value` field name for an allowed type `T` 480 enum valNameForType(T) = `val` ~ AllowedTypeNames[staticIndexOf!(T, AllowedTypes)]; 481 // Get the `Type` enum value for an allowed type `T` 482 alias TagTypeForType(T) = EnumMembers!Type[staticIndexOf!(T, AllowedTypes)]; 483 484 // Check if one of the `AllowedType`s has a given member 485 template TypeHasMemeber(string name) 486 { 487 static if (name != "length" && hasMember(Tag, name)) 488 { 489 enum TypeHasMemeber = false; 490 } 491 else 492 { 493 enum hasFunc(T) = hasMember!(T, name); 494 alias SupportingTypes = Filter!(ApplyRight!(hasFunc), AllowedTypes); 495 496 static if (SupportingTypes.length != 0) 497 enum TypeHasMemeber = true; 498 else 499 enum TypeHasMemeber = false; 500 } 501 } 502 503 /// Clears the current value 504 void clearCurValue() pure nothrow 505 { 506 if (empty) 507 return; 508 509 static foreach (T; AllowedTypes) 510 { 511 if (curType == TagTypeForType!T) 512 mixin(`return value.`~valNameForType!T~`.destroy();`); 513 } 514 } 515 516 /// Sets the value for the type 517 void setValueForType(T)(auto ref T t) 518 { 519 mixin(`value.`~valNameForType!T~` = t;`); 520 } 521 522 /// Gets the value for the type 523 T getValueForType(T)() pure inout nothrow 524 { 525 mixin(`return cast(T) value.`~valNameForType!T~`;`); 526 } 527 528 // Storage for all tag type 529 union Value 530 { 531 // generate members for all allowed types 532 static foreach (T; AllowedTypes) 533 mixin(T.stringof ~ ` ` ~ valNameForType!T ~ `;`); 534 } 535 536 Value value = void; 537 Type curType = emptyType; 538 }