1 /** 2 Copyright: Copyright Thomas Stuart Bockman 2015 3 License: <a href="http://www.boost.org/LICENSE_1_0.txt">Boost License 1.0</a>. 4 Authors: Thomas Stuart Bockman 5 */ 6 7 module checkedint; 8 import checkedint.flags; 9 10 import std.algorithm, std.format, future.traits; 11 static import std.math; 12 13 @safe: 14 15 // traits 16 enum isSafeInt(T) = isInstanceOf!(SafeInt, T); 17 enum isSmartInt(T) = isInstanceOf!(SmartInt, T); 18 enum isCheckedInt(T) = isSafeInt!T || isSmartInt!T; 19 20 template hasBitOps(T) { 21 static if(isCheckedInt!T) 22 enum hasBitOps = TemplateArgsOf!T[1]; 23 else 24 enum hasBitOps = isFixedPoint!T; 25 } 26 template isThrowingCInt(T) { 27 static if(isCheckedInt!T) 28 enum isThrowingCInt = TemplateArgsOf!T[2]; 29 else 30 enum isThrowingCInt = false; 31 } 32 33 template BasicScalar(T) { 34 static if(isScalarType!T) 35 alias BasicScalar = Unqual!T; 36 else 37 static if(isCheckedInt!T) 38 alias BasicScalar = TemplateArgsOf!T[0]; 39 else 40 alias BasicScalar = void; 41 } 42 43 // conv 44 /+pragma(inline, true) {+/ 45 T to(T, bool throws, S)(const S value) 46 if(isScalarType!(BasicScalar!T) && isScalarType!(BasicScalar!S)) 47 { 48 import checkedint.internal : toImpl; 49 const T ret = toImpl!(BasicScalar!T, throws)(value.bscal); 50 51 return ret; 52 } 53 T to(T, S)(S value) { 54 static if(isScalarType!(BasicScalar!T) && isScalarType!(BasicScalar!S)) 55 return to!(T, true, S)(value); 56 else { 57 import std.conv : stdTo = to; 58 return stdTo!T(value); 59 } 60 } 61 62 @property { 63 /+ref N bscal(N)(return ref N num) 64 if(isScalarType!N) 65 { 66 return num; 67 } 68 ref N bscal(N)(return ref N num) 69 if(isCheckedInt!N) 70 { 71 return num.bscal; 72 }+/ 73 N bscal(N)(const N num) 74 if(isScalarType!N) 75 { 76 return num; 77 } 78 N bscal(N)(const N num) 79 if(isCheckedInt!N) 80 { 81 return num.bscal; 82 } 83 84 /+ref N bits(N)(return ref N num) 85 if(isFixedPoint!N) 86 { 87 return num; 88 } 89 ref N bits(N)(return ref N num) 90 if(isCheckedInt!N) 91 { 92 return num.bits; 93 }+/ 94 N bits(N)(const N num) 95 if(isFixedPoint!N) 96 { 97 return num; 98 } 99 N bits(N)(const N num) 100 if(isCheckedInt!N) 101 { 102 return num.bits; 103 } 104 105 Select!(isSigned!N, ptrdiff_t, size_t) idx(N)(const N num) 106 if(isScalarType!N) 107 { 108 return cast(typeof(return))num; 109 } 110 Select!(isSigned!(BasicScalar!N), ptrdiff_t, size_t) idx(N)(const N num) 111 if(isCheckedInt!N) 112 { 113 return num.idx; 114 } 115 } 116 /+}+/ 117 118 // ops 119 public import safeOp = checkedint.safeop; 120 public import smartOp = checkedint.smartop; 121 122 // checked types 123 /+pragma(inline, true) public {+/ 124 // TODO: Convert bitops and throws to std.typecons.Flags? 125 126 template SafeInt(N, bool bitOps = true, bool throws = true) 127 if(isCheckedInt!N || (isIntegral!N && !isUnqual!N)) 128 { 129 alias SafeInt = SafeInt!(BasicScalar!N, bitOps, throws); 130 } 131 struct SafeInt(N, bool bitOps = true, bool throws = true) 132 if(isIntegral!N && isUnqual!N) 133 { 134 /+pure+/ nothrow @nogc { 135 static if(bitOps) { 136 N bscal; 137 @property ref typeof(this) bits() { 138 return this; } 139 @property ref const(typeof(this)) bits() const { 140 return this; } 141 } else { 142 @property ref N bscal() { 143 return bits.bscal; } 144 @property ref const(N) bscal() const { 145 return bits.bscal; } 146 SafeInt!(N, true, throws) bits; 147 } 148 149 static if(__VERSION__ >= 2067) { 150 version(GNU) { static assert(false); } 151 enum min = typeof(this)(N.min); 152 enum max = typeof(this)(N.max); 153 } else { 154 static @property pure nothrow @nogc { 155 auto min() { 156 return typeof(this)(N.min); } 157 auto max() { 158 return typeof(this)(N.max); } 159 } 160 } 161 162 // Construction, assignment, and casting 163 private void checkImplicit(M)() 164 if(isScalarType!M || isCheckedInt!M) 165 { 166 alias MB = BasicScalar!M; 167 static assert(isImplicitlyConvertible!(MB, N), 168 "SafeInt does not support implicit conversions from " ~ 169 MB.stringof ~ " to " ~ N.stringof ~ 170 ", because they are unsafe when unchecked. Use the explicit checkedint.conv.to()." 171 ); 172 } 173 174 this(const N bscal) { 175 this.bscal = bscal; } 176 ref typeof(this) opAssign(const N bscal) { 177 this.bscal = bscal; 178 return this; 179 } 180 this(M)(const M that) 181 if(isScalarType!M) 182 { 183 checkImplicit!M(); 184 this.bscal = that.bscal; 185 } 186 ref typeof(this) opAssign(M)(const M that) 187 if(isScalarType!M) 188 { 189 checkImplicit!M(); 190 this.bscal = that.bscal; 191 return this; 192 } 193 194 M opCast(M)() const 195 if(isFloatingPoint!M) 196 { 197 return cast(M)bscal; 198 } 199 M opCast(M)() const 200 if(is(M == bool)) 201 { 202 return bscal != 0; 203 } 204 size_t toHash() const { 205 return cast(size_t)bscal; } 206 207 // Comparison 208 bool opEquals(M)(const M right) const 209 if(isSafeInt!M || isScalarType!M) 210 { 211 return safeOp.cmp!"=="(this.bscal, right.bscal); 212 } 213 auto opCmp(M)(const M right) const 214 if(isFloatingPoint!M) 215 { 216 return 217 (left < right)? -1 : 218 (left > right)? 1 : 219 (left == right)? 0 : float.nan; 220 } 221 int opCmp(M)(const M right) const 222 if(isSafeInt!M || isFixedPoint!M) 223 { 224 return safeOp.cmp(this.bscal, right.bscal); 225 } 226 227 // Unary 228 SafeInt!(int, bitOps, throws) popcnt() const { 229 static assert(bitOps, "Bitwise operations are disabled."); 230 231 import future.bitop : stdPC = popcnt; 232 return typeof(return)(stdPC(bscal)); 233 } 234 235 // Binary 236 M opBinaryRight(string op, M)(const M left) const 237 if(isFloatingPoint!M) 238 { 239 return mixin("left " ~ op ~ " bscal"); 240 } 241 M opBinary(string op, M)(const M right) const 242 if(isFloatingPoint!M) 243 { 244 return mixin("bscal " ~ op ~ " right"); 245 } 246 247 M pow(M)(const M exp) const 248 if(isFloatingPoint!M) 249 { 250 return std.math.pow(bscal, exp); 251 } 252 } 253 /+might throw or be impure+/ public { 254 // Construction, assignment, and casting 255 M opCast(M)() const 256 if(isCheckedInt!M || isIntegral!M || isSomeChar!M) 257 { 258 return to!(M, throws)(bscal); 259 } 260 @property Select!(isSigned!N, ptrdiff_t, size_t) idx() const { 261 return to!(typeof(return), throws)(bscal); 262 } 263 264 void toString(Writer, Char)(Writer sink, FormatSpec!Char fmt = (FormatSpec!Char).init) const @trusted { 265 formatValue(sink, bscal, fmt); } 266 string toString() const { 267 return to!string(bscal); } 268 269 // Unary 270 typeof(this) opUnary(string op)() const 271 if(op.among!("-", "+", "~")) 272 { 273 static assert(bitOps || (op != "~"), 274 "Bitwise operations are disabled."); 275 276 return typeof(return)(safeOp.unary!(op, throws)(bscal)); 277 } 278 ref typeof(this) opUnary(string op)() 279 if(op.among!("++", "--")) 280 { 281 safeOp.unary!(op, throws)(bscal); 282 return this; 283 } 284 285 typeof(this) abs() const { 286 return typeof(return)(safeOp.abs!throws(bscal)); } 287 288 SafeInt!(int, bitOps, throws) bsf() const { 289 static assert(bitOps, "Bitwise operations are disabled."); 290 291 return typeof(return)(safeOp.bsf!throws(bscal)); 292 } 293 SafeInt!(int, bitOps, throws) bsr() const { 294 static assert(bitOps, "Bitwise operations are disabled."); 295 296 return typeof(return)(safeOp.bsr!throws(bscal)); 297 } 298 299 SafeInt!(int, bitOps, throws) ilogb() const { 300 return typeof(return)(safeOp.ilogb!throws(bscal)); } 301 302 // Binary 303 SafeInt!(OpType!(M, op, N), bitOps, throws) opBinaryRight(string op, M)(const M left) const 304 if(isFixedPoint!M) 305 { 306 static assert(bitOps || !op.among!("<<", ">>", ">>>", "&", "|", "^"), 307 "Bitwise operations are disabled."); 308 309 return typeof(return)(safeOp.binary!(op, throws)(left, bscal)); 310 } 311 SafeInt!(OpType!(N, op, BasicScalar!M), bitOps && hasBitOps!M, throws || isThrowingCInt!M) opBinary(string op, M)(const M right) const 312 if(isSafeInt!M || isFixedPoint!M) 313 { 314 static assert((bitOps && hasBitOps!M) || !op.among!("<<", ">>", ">>>", "&", "|", "^"), 315 "Bitwise operations are disabled."); 316 317 return typeof(return)(safeOp.binary!(op, throws || isThrowingCInt!M)(this.bscal, right.bscal)); 318 } 319 320 ref typeof(this) opOpAssign(string op, M)(const M right) 321 if(isSafeInt!M || isFixedPoint!M) 322 { 323 static assert((bitOps && hasBitOps!M) || !op.among!("<<", ">>", ">>>", "&", "|", "^"), 324 "Bitwise operations are disabled."); 325 checkImplicit!(OpType!(N, op, M))(); 326 327 safeOp.binary!(op ~ "=", throws || isThrowingCInt!M)(this.bscal, right.bscal); 328 return this; 329 } 330 331 SafeInt!(CallType!(std.math.pow, N, BasicScalar!M), bitOps && hasBitOps!M, throws || isThrowingCInt!M) pow(M)(const M exp) const 332 if(isSafeInt!M || isFixedPoint!M) 333 { 334 return typeof(return)(safeOp.pow!(throws || isThrowingCInt!M)(this.bscal, exp.bscal)); 335 } 336 } 337 } 338 339 template SmartInt(N, bool bitOps = true, bool throws = true) 340 if(isCheckedInt!N || (isIntegral!N && !isUnqual!N)) 341 { 342 alias SmartInt = SmartInt!(BasicScalar!N, bitOps, throws); 343 } 344 struct SmartInt(N, bool bitOps = true, bool throws = true) 345 if(isIntegral!N && isUnqual!N) 346 { 347 /+pure+/ nothrow @nogc { 348 static if(bitOps) { 349 N bscal; 350 @property ref typeof(this) bits() { 351 return this; } 352 @property ref const(typeof(this)) bits() const { 353 return this; } 354 } else { 355 @property ref N bscal() { 356 return bits.bscal; } 357 @property ref const(N) bscal() const { 358 return bits.bscal; } 359 SmartInt!(N, true, throws) bits; 360 } 361 362 static if(__VERSION__ >= 2067) { 363 version(GNU) { static assert(false); } 364 enum min = typeof(this)(N.min); 365 enum max = typeof(this)(N.max); 366 } else { 367 static @property pure nothrow @nogc { 368 auto min() { 369 return typeof(this)(N.min); } 370 auto max() { 371 return typeof(this)(N.max); } 372 } 373 } 374 375 // Construction, assignment, and casting 376 this(const N bscal) { 377 this.bscal = bscal; } 378 ref typeof(this) opAssign(const N bscal) { 379 this.bscal = bscal; 380 return this; 381 } 382 this(M)(const M that) 383 if(isScalarType!M || isCheckedInt!M) 384 { 385 this.bscal = to!(N, throws)(that); 386 } 387 ref typeof(this) opAssign(M)(const M that) 388 if(isScalarType!M || isCheckedInt!M) 389 { 390 this.bscal = to!(N, throws)(that); 391 return this; 392 } 393 394 M opCast(M)() const 395 if(isFloatingPoint!M) 396 { 397 return cast(M)bscal; 398 } 399 M opCast(M)() const 400 if(is(M == bool)) 401 { 402 return bscal != 0; 403 } 404 size_t toHash() const { 405 return cast(size_t)bscal; } 406 407 // Comparison 408 bool opEquals(M)(const M right) const 409 if(isScalarType!M || isCheckedInt!M) 410 { 411 return smartOp.cmp!"=="(this.bscal, right.bscal); 412 } 413 auto opCmp(M)(const M right) const 414 if(isFloatingPoint!M) 415 { 416 return 417 (bscal < right)? -1 : 418 (bscal > right)? 1 : 419 (bscal == right)? 0 : float.nan; 420 } 421 int opCmp(M)(const M right) const 422 if(isScalarType!M || isCheckedInt!M) 423 { 424 return smartOp.cmp(this.bscal, right.bscal); 425 } 426 427 // Unary 428 typeof(this) opUnary(string op)() const 429 if(op == "~") 430 { 431 static assert(bitOps, 432 "Bitwise operations are disabled."); 433 434 return typeof(return)(smartOp.unary!(op, throws)(bscal)); 435 } 436 437 SmartInt!(Unsigned!N, bitOps, throws) abs() const { 438 return typeof(return)(smartOp.abs(bscal)); 439 } 440 441 SmartInt!(int, bitOps, throws) popcnt() const { 442 static assert(bitOps, "Bitwise operations are disabled."); 443 444 import future.bitop : stdPC = popcnt; 445 return typeof(return)(stdPC(bscal)); 446 } 447 448 // Binary 449 auto opBinaryRight(string op, M)(const M left) const 450 if(isFloatingPoint!M) 451 { 452 return smartOp.binary!op(left, bscal); 453 } 454 auto opBinary(string op, M)(const M right) const 455 if(isFloatingPoint!M) 456 { 457 return smartOp.binary!op(bscal, right); 458 } 459 460 auto mulPow2(M)(const M exp) const 461 if(isFloatingPoint!M) 462 { 463 return smartOp.mulPow2(bscal, exp); 464 } 465 auto divPow2(M)(const M exp) const 466 if(isFloatingPoint!M) 467 { 468 return smartOp.divPow2(bscal, exp); 469 } 470 471 auto pow(M)(const M exp) const 472 if(isFloatingPoint!M) 473 { 474 return std.math.pow(bscal, exp); 475 } 476 } 477 /+might throw or be impure+/ public { 478 // Construction, assignment, and casting 479 M opCast(M)() const 480 if(isCheckedInt!M || isIntegral!M || isSomeChar!M) 481 { 482 return to!(M, throws)(bscal); 483 } 484 @property Select!(isSigned!N, ptrdiff_t, size_t) idx() const { 485 return to!(typeof(return), throws)(bscal); 486 } 487 488 void toString(Writer, Char)(Writer sink, FormatSpec!Char fmt = (FormatSpec!Char).init) const @trusted { 489 formatValue(sink, bscal, fmt); } 490 string toString() const { 491 return to!string(bscal); } 492 493 // Unary 494 SmartInt!(Signed!N, bitOps, throws) opUnary(string op)() const 495 if(op == "+" || op == "-") 496 { 497 return typeof(return)(smartOp.unary!(op, throws)(bscal)); 498 } 499 ref typeof(this) opUnary(string op)() 500 if(op.among!("++", "--")) 501 { 502 smartOp.unary!(op, throws)(bscal); 503 return this; 504 } 505 506 SmartInt!(ubyte, bitOps, throws) bsf() const { 507 static assert(bitOps, "Bitwise operations are disabled."); 508 509 return typeof(return)(smartOp.bsf!throws(bscal)); 510 } 511 SmartInt!(ubyte, bitOps, throws) bsr() const { 512 static assert(bitOps, "Bitwise operations are disabled."); 513 514 return typeof(return)(smartOp.bsr!throws(bscal)); 515 } 516 517 SmartInt!(ubyte, bitOps, throws) ilogb() const { 518 return typeof(return)(smartOp.ilogb!throws(bscal)); } 519 520 // Binary 521 auto opBinaryRight(string op, M)(const M left) const 522 if(isFixedPoint!M || isSafeInt!M) 523 { 524 static assert((bitOps && hasBitOps!M) || !op.among!("<<", ">>", ">>>", "&", "|", "^"), 525 "Bitwise operations are disabled."); 526 527 const wret = smartOp.binary!(op, throws || isThrowingCInt!M)(left.bscal, this.bscal); 528 return SmartInt!(typeof(wret), bitOps && hasBitOps!M, throws || isThrowingCInt!M)(wret); 529 } 530 auto opBinary(string op, M)(const M right) const 531 if(isFixedPoint!M || isCheckedInt!M) 532 { 533 static assert((bitOps && hasBitOps!M) || !op.among!("<<", ">>", ">>>", "&", "|", "^"), 534 "Bitwise operations are disabled."); 535 536 const wret = smartOp.binary!(op, throws || isThrowingCInt!M)(this.bscal, right.bscal); 537 return SmartInt!(typeof(wret), bitOps && hasBitOps!M, throws || isThrowingCInt!M)(wret); 538 } 539 ref typeof(this) opOpAssign(string op, M)(const M right) 540 if(isScalarType!M || isCheckedInt!M) 541 { 542 static assert((bitOps && hasBitOps!M) || !op.among!("<<", ">>", ">>>", "&", "|", "^"), 543 "Bitwise operations are disabled."); 544 545 return opAssign(smartOp.binary!(op, throws || isThrowingCInt!M)(this.bscal, right.bscal)); 546 /+smartOp.binary!(op ~ "=", throws || isThrowingCInt!M)(this.bscal, right.bscal); 547 return this;+/ 548 } 549 550 auto mulPow2(M)(const M exp) const 551 if(isFixedPoint!M || isCheckedInt!M) 552 { 553 const wret = smartOp.mulPow2!throws(this.bscal, exp.bscal); 554 return SmartInt!(typeof(wret), bitOps && hasBitOps!M, throws || isThrowingCInt!M)(wret); 555 } 556 auto divPow2(M)(const M exp) const 557 if(isFixedPoint!M || isCheckedInt!M) 558 { 559 const wret = smartOp.divPow2!throws(this.bscal, exp.bscal); 560 return SmartInt!(typeof(wret), bitOps && hasBitOps!M, throws || isThrowingCInt!M)(wret); 561 } 562 563 auto pow(M)(const M exp) const 564 if(isFixedPoint!M || isCheckedInt!M) 565 { 566 const wret = smartOp.pow!throws(this.bscal, exp.bscal); 567 return SmartInt!(typeof(wret), bitOps && hasBitOps!M, throws || isThrowingCInt!M)(wret); 568 } 569 } 570 } 571 /+}+/