summary refs log tree commit diff stats
diff options
context:
space:
mode:
-rw-r--r--changelog.md4
-rw-r--r--lib/pure/bitops.nim227
-rw-r--r--tests/stdlib/tbitops.nim71
3 files changed, 280 insertions, 22 deletions
diff --git a/changelog.md b/changelog.md
index 082fe5690..9ae50f1f3 100644
--- a/changelog.md
+++ b/changelog.md
@@ -61,6 +61,10 @@
 - `paramCount` & `paramStr` are now defined in os.nim instead of nimscript.nim for nimscript/nimble.
 - `dollars.$` now works for unsigned ints with `nim js`
 
+- Improvements to the `bitops` module, including bitslices, non-mutating versions
+  of the original masking functions, `mask`/`masked`, and varargs support for
+  `bitand`, `bitor`, and `bitxor`.
+
 - `sugar.=>` and `sugar.->` changes: Previously `(x, y: int)` was transformed
   into `(x: auto, y: int)`, it now becomes `(x: int, y: int)` in consistency
   with regular proc definitions (although you cannot use semicolons).
diff --git a/lib/pure/bitops.nim b/lib/pure/bitops.nim
index 439f6be03..6aeec3092 100644
--- a/lib/pure/bitops.nim
+++ b/lib/pure/bitops.nim
@@ -26,6 +26,7 @@
 ## may return undefined and/or platform dependent value if given invalid input.
 
 import macros
+import std/private/since
 
 proc bitnot*[T: SomeInteger](x: T): T {.magic: "BitnotI", noSideEffect.}
   ## Computes the `bitwise complement` of the integer `x`.
@@ -85,37 +86,220 @@ template forwardImpl(impl, arg) {.dirty.} =
 
 when defined(nimHasalignOf):
   type BitsRange*[T] = range[0..sizeof(T)*8-1]
-    ## Returns a range with all bit positions for type ``T``.
+    ## A range with all bit positions for type ``T``
 
-  proc setMask*[T: SomeInteger](v: var T, mask: T) {.inline.} =
+  func bitsliced*[T: SomeInteger](v: T; slice: Slice[int]): T {.inline, since: (1, 3).} =
+    ## Returns an extracted (and shifted) slice of bits from ``v``.
+    runnableExamples:
+      doAssert 0b10111.bitsliced(2 .. 4) == 0b101
+      doAssert 0b11100.bitsliced(0 .. 2) == 0b100
+      doAssert 0b11100.bitsliced(0 ..< 3) == 0b100
+
+    let
+      upmost = sizeof(T) * 8 - 1
+      uv     = when v is SomeUnsignedInt: v else: v.toUnsigned
+    (uv shl (upmost - slice.b) shr (upmost - slice.b + slice.a)).T
+
+  proc bitslice*[T: SomeInteger](v: var T; slice: Slice[int]) {.inline, since: (1, 3).} =
+    ## Mutates ``v`` into an extracted (and shifted) slice of bits from ``v``.
+    runnableExamples:
+      var x = 0b101110
+      x.bitslice(2 .. 4)
+      doAssert x == 0b011
+
+    let
+      upmost = sizeof(T) * 8 - 1
+      uv     = when v is SomeUnsignedInt: v else: v.toUnsigned
+    v = (uv shl (upmost - slice.b) shr (upmost - slice.b + slice.a)).T
+
+  func toMask*[T: SomeInteger](slice: Slice[int]): T {.inline, since: (1, 3).} =
+    ## Creates a bitmask based on a slice of bits.
+    runnableExamples:
+      doAssert toMask[int32](1 .. 3) == 0b1110'i32
+      doAssert toMask[int32](0 .. 3) == 0b1111'i32
+
+    let
+      upmost = sizeof(T) * 8 - 1
+      bitmask = when T is SomeUnsignedInt:
+                  bitnot(0.T)
+                else:
+                  bitnot(0.T).toUnsigned
+    (bitmask shl (upmost - slice.b + slice.a) shr (upmost - slice.b)).T
+
+  proc masked*[T: SomeInteger](v, mask :T): T {.inline, since: (1, 3).} =
+    ## Returns ``v``, with only the ``1`` bits from ``mask`` matching those of
+    ## ``v`` set to 1.
+    ##
+    ## Effectively maps to a `bitand` operation.
+    runnableExamples:
+      var v = 0b0000_0011'u8
+      doAssert v.masked(0b0000_1010'u8) == 0b0000_0010'u8
+
+    bitand(v, mask)
+
+  func masked*[T: SomeInteger](v: T; slice: Slice[int]): T {.inline, since: (1, 3).} =
+    ## Mutates ``v``, with only the ``1`` bits in the range of ``slice``
+    ## matching those of ``v`` set to 1.
+    ##
+    ## Effectively maps to a `bitand` operation.
+    runnableExamples:
+      var v = 0b0000_1011'u8
+      doAssert v.masked(1 .. 3) == 0b0000_1010'u8
+
+    bitand(v, toMask[T](slice))
+
+  proc mask*[T: SomeInteger](v: var T; mask: T) {.inline, since: (1, 3).} =
+    ## Mutates ``v``, with only the ``1`` bits from ``mask`` matching those of
+    ## ``v`` set to 1.
+    ##
+    ## Effectively maps to a `bitand` operation.
+    runnableExamples:
+      var v = 0b0000_0011'u8
+      v.mask(0b0000_1010'u8)
+      doAssert v == 0b0000_1010'u8
+
+    v = bitand(v, mask)
+
+  proc mask*[T: SomeInteger](v: var T; slice: Slice[int]) {.inline, since: (1, 3).} =
+    ## Mutates ``v``, with only the ``1`` bits in the range of ``slice``
+    ## matching those of ``v`` set to 1.
+    ##
+    ## Effectively maps to a `bitand` operation.
+    runnableExamples:
+      var v = 0b0000_1011'u8
+      v.mask(1 .. 3)
+      doAssert v == 0b0000_1010'u8
+
+    v = bitand(v, toMask[T](slice))
+
+  func setMasked*[T: SomeInteger](v, mask :T): T {.inline, since: (1, 3).} =
     ## Returns ``v``, with all the ``1`` bits from ``mask`` set to 1.
+    ##
+    ## Effectively maps to a `bitor` operation.
+    runnableExamples:
+      var v = 0b0000_0011'u8
+      doAssert v.setMasked(0b0000_1010'u8) == 0b0000_1011'u8
+
+    bitor(v, mask)
+
+  func setMasked*[T: SomeInteger](v: T; slice: Slice[int]): T {.inline, since: (1, 3).} =
+    ## Returns ``v``, with all the ``1`` bits in the range of ``slice`` set to 1.
+    ##
+    ## Effectively maps to a `bitor` operation.
+    runnableExamples:
+      var v = 0b0000_0011'u8
+      doAssert v.setMasked(2 .. 3) == 0b0000_1111'u8
+
+    bitor(v, toMask[T](slice))
+
+  proc setMask*[T: SomeInteger](v: var T; mask: T) {.inline.} =
+    ## Mutates ``v``, with all the ``1`` bits from ``mask`` set to 1.
+    ##
+    ## Effectively maps to a `bitor` operation.
     runnableExamples:
       var v = 0b0000_0011'u8
       v.setMask(0b0000_1010'u8)
       doAssert v == 0b0000_1011'u8
 
-    v = v or mask
+    v = bitor(v, mask)
+
+  proc setMask*[T: SomeInteger](v: var T; slice: Slice[int]) {.inline, since: (1, 3).} =
+    ## Mutates ``v``, with all the ``1`` bits in the range of ``slice`` set to 1.
+    ##
+    ## Effectively maps to a `bitor` operation.
+    runnableExamples:
+      var v = 0b0000_0011'u8
+      v.setMask(2 .. 3)
+      doAssert v == 0b0000_1111'u8
 
-  proc clearMask*[T: SomeInteger](v: var T, mask: T) {.inline.} =
+    v = bitor(v, toMask[T](slice))
+
+  func clearMasked*[T: SomeInteger](v, mask :T): T {.inline, since: (1, 3).} =
     ## Returns ``v``, with all the ``1`` bits from ``mask`` set to 0.
+    ##
+    ## Effectively maps to a `bitand` operation with an *inverted mask.*
+    runnableExamples:
+      var v = 0b0000_0011'u8
+      doAssert v.clearMasked(0b0000_1010'u8) == 0b0000_0001'u8
+
+    bitand(v, bitnot(mask))
+
+  func clearMasked*[T: SomeInteger](v: T; slice: Slice[int]): T {.inline, since: (1, 3).} =
+    ## Returns ``v``, with all the ``1`` bits in the range of ``slice`` set to 0.
+    ##
+    ## Effectively maps to a `bitand` operation with an *inverted mask.*
+    runnableExamples:
+      var v = 0b0000_0011'u8
+      doAssert v.clearMasked(1 .. 3) == 0b0000_0001'u8
+
+    bitand(v, bitnot(toMask[T](slice)))
+
+  proc clearMask*[T: SomeInteger](v: var T; mask: T) {.inline.} =
+    ## Mutates ``v``, with all the ``1`` bits from ``mask`` set to 0.
+    ##
+    ## Effectively maps to a `bitand` operation with an *inverted mask.*
     runnableExamples:
       var v = 0b0000_0011'u8
       v.clearMask(0b0000_1010'u8)
       doAssert v == 0b0000_0001'u8
 
-    v = v and not mask
+    v = bitand(v, bitnot(mask))
+
+  proc clearMask*[T: SomeInteger](v: var T; slice: Slice[int]) {.inline, since: (1, 3).} =
+    ## Mutates ``v``, with all the ``1`` bits in the range of ``slice`` set to 0.
+    ##
+    ## Effectively maps to a `bitand` operation with an *inverted mask.*
+    runnableExamples:
+      var v = 0b0000_0011'u8
+      v.clearMask(1 .. 3)
+      doAssert v == 0b0000_0001'u8
+
+    v = bitand(v, bitnot(toMask[T](slice)))
 
-  proc flipMask*[T: SomeInteger](v: var T, mask: T) {.inline.} =
+  func flipMasked*[T: SomeInteger](v, mask :T): T {.inline, since: (1, 3).} =
     ## Returns ``v``, with all the ``1`` bits from ``mask`` flipped.
+    ##
+    ## Effectively maps to a `bitxor` operation.
+    runnableExamples:
+      var v = 0b0000_0011'u8
+      doAssert v.flipMasked(0b0000_1010'u8) == 0b0000_1001'u8
+
+    bitxor(v, mask)
+
+  func flipMasked*[T: SomeInteger](v: T; slice: Slice[int]): T {.inline, since: (1, 3).} =
+    ## Returns ``v``, with all the ``1`` bits in the range of ``slice`` flipped.
+    ##
+    ## Effectively maps to a `bitxor` operation.
+    runnableExamples:
+      var v = 0b0000_0011'u8
+      doAssert v.flipMasked(1 .. 3) == 0b0000_1001'u8
+
+    bitxor(v, toMask[T](slice))
+
+  proc flipMask*[T: SomeInteger](v: var T; mask: T) {.inline.} =
+    ## Mutates ``v``, with all the ``1`` bits from ``mask`` flipped.
+    ##
+    ## Effectively maps to a `bitxor` operation.
     runnableExamples:
       var v = 0b0000_0011'u8
       v.flipMask(0b0000_1010'u8)
       doAssert v == 0b0000_1001'u8
 
-    v = v xor mask
+    v = bitxor(v, mask)
 
-  proc setBit*[T: SomeInteger](v: var T, bit: BitsRange[T]) {.inline.} =
-    ## Returns ``v``, with the bit at position ``bit`` set to 1.
+  proc flipMask*[T: SomeInteger](v: var T; slice: Slice[int]) {.inline, since: (1, 3).} =
+    ## Mutates ``v``, with all the ``1`` bits in the range of ``slice`` flipped.
+    ##
+    ## Effectively maps to a `bitxor` operation.
+    runnableExamples:
+      var v = 0b0000_0011'u8
+      v.flipMask(1 .. 3)
+      doAssert v == 0b0000_1001'u8
+
+    v = bitxor(v, toMask[T](slice))
+
+  proc setBit*[T: SomeInteger](v: var T; bit: BitsRange[T]) {.inline.} =
+    ## Mutates ``v``, with the bit at position ``bit`` set to 1
     runnableExamples:
       var v = 0b0000_0011'u8
       v.setBit(5'u8)
@@ -123,8 +307,8 @@ when defined(nimHasalignOf):
 
     v.setMask(1.T shl bit)
 
-  proc clearBit*[T: SomeInteger](v: var T, bit: BitsRange[T]) {.inline.} =
-    ## Returns ``v``, with the bit at position ``bit`` set to 0.
+  proc clearBit*[T: SomeInteger](v: var T; bit: BitsRange[T]) {.inline.} =
+    ## Mutates ``v``, with the bit at position ``bit`` set to 0
     runnableExamples:
       var v = 0b0000_0011'u8
       v.clearBit(1'u8)
@@ -132,8 +316,8 @@ when defined(nimHasalignOf):
 
     v.clearMask(1.T shl bit)
 
-  proc flipBit*[T: SomeInteger](v: var T, bit: BitsRange[T]) {.inline.} =
-    ## Returns ``v``, with the bit at position ``bit`` flipped.
+  proc flipBit*[T: SomeInteger](v: var T; bit: BitsRange[T]) {.inline.} =
+    ## Mutates ``v``, with the bit at position ``bit`` flipped
     runnableExamples:
       var v = 0b0000_0011'u8
       v.flipBit(1'u8)
@@ -145,8 +329,8 @@ when defined(nimHasalignOf):
 
     v.flipMask(1.T shl bit)
 
-  macro setBits*(v: var typed, bits: varargs[typed]): untyped =
-    ## Returns ``v``, with the bits at positions ``bits`` set to 1.
+  macro setBits*(v: typed; bits: varargs[typed]): untyped =
+    ## Mutates ``v``, with the bits at positions ``bits`` set to 1
     runnableExamples:
       var v = 0b0000_0011'u8
       v.setBits(3, 5, 7)
@@ -157,8 +341,8 @@ when defined(nimHasalignOf):
     for bit in bits:
       result.add newCall("setBit", v, bit)
 
-  macro clearBits*(v: var typed, bits: varargs[typed]): untyped =
-    ## Returns ``v``, with the bits at positions ``bits`` set to 0.
+  macro clearBits*(v: typed; bits: varargs[typed]): untyped =
+    ## Mutates ``v``, with the bits at positions ``bits`` set to 0
     runnableExamples:
       var v = 0b1111_1111'u8
       v.clearBits(1, 3, 5, 7)
@@ -169,8 +353,8 @@ when defined(nimHasalignOf):
     for bit in bits:
       result.add newCall("clearBit", v, bit)
 
-  macro flipBits*(v: var typed, bits: varargs[typed]): untyped =
-    ## Returns ``v``, with the bits at positions ``bits`` set to 0.
+  macro flipBits*(v: typed; bits: varargs[typed]): untyped =
+    ## Mutates ``v``, with the bits at positions ``bits`` set to 0
     runnableExamples:
       var v = 0b0000_1111'u8
       v.flipBits(1, 3, 5, 7)
@@ -181,8 +365,9 @@ when defined(nimHasalignOf):
     for bit in bits:
       result.add newCall("flipBit", v, bit)
 
-  proc testBit*[T: SomeInteger](v: T, bit: BitsRange[T]): bool {.inline.} =
-    ## Returns true if the bit in ``v`` at positions ``bit`` is set to 1.
+
+  proc testBit*[T: SomeInteger](v: T; bit: BitsRange[T]): bool {.inline.} =
+    ## Returns true if the bit in ``v`` at positions ``bit`` is set to 1
     runnableExamples:
       var v = 0b0000_1111'u8
       doAssert v.testBit(0)
diff --git a/tests/stdlib/tbitops.nim b/tests/stdlib/tbitops.nim
index 1bd4da5c4..2baa1c718 100644
--- a/tests/stdlib/tbitops.nim
+++ b/tests/stdlib/tbitops.nim
@@ -171,7 +171,7 @@ proc main1() =
     doAssert( U64A.rotateRightBits(64) == U64A)
 
   block:
-    # mask operations
+    # basic mask operations (mutating)
     var v: uint8
     v.setMask(0b1100_0000)
     v.setMask(0b0000_1100)
@@ -182,6 +182,75 @@ proc main1() =
     doAssert(v == 0b0001_0001)
     v.clearMask(0b0001_0001)
     doAssert(v == 0b0000_0000)
+    v.setMask(0b0001_1110)
+    doAssert(v == 0b0001_1110)
+    v.mask(0b0101_0100)
+    doAssert(v == 0b0001_0100)
+  block:
+    # basic mask operations (non-mutating)
+    let v = 0b1100_0000'u8
+    doAssert(v.masked(0b0000_1100) == 0b0000_0000)
+    doAssert(v.masked(0b1000_1100) == 0b1000_0000)
+    doAssert(v.setMasked(0b0000_1100) == 0b1100_1100)
+    doAssert(v.setMasked(0b1000_1110) == 0b1100_1110)
+    doAssert(v.flipMasked(0b1100_1000) == 0b0000_1000)
+    doAssert(v.flipMasked(0b0000_1100) == 0b1100_1100)
+    let t = 0b1100_0110'u8
+    doAssert(t.clearMasked(0b0100_1100) == 0b1000_0010)
+    doAssert(t.clearMasked(0b1100_0000) == 0b0000_0110)
+  block:
+    # basic bitslice opeartions
+    let a = 0b1111_1011'u8
+    doAssert(a.bitsliced(0 .. 3) == 0b1011)
+    doAssert(a.bitsliced(2 .. 3) == 0b10)
+    doAssert(a.bitsliced(4 .. 7) == 0b1111)
+
+    # same thing, but with exclusive ranges.
+    doAssert(a.bitsliced(0 ..< 4) == 0b1011)
+    doAssert(a.bitsliced(2 ..< 4) == 0b10)
+    doAssert(a.bitsliced(4 ..< 8) == 0b1111)
+
+    # mutating
+    var b = 0b1111_1011'u8
+    b.bitslice(1 .. 3)
+    doAssert(b == 0b101)
+
+    # loop test:
+    let c = 0b1111_1111'u8
+    for i in 0 .. 7:
+      doAssert(c.bitsliced(i .. 7) == c shr i)
+  block:
+    # bitslice versions of mask operations (mutating)
+    var a = 0b1100_1100'u8
+    let b = toMask[uint8](2 .. 3)
+    a.mask(b)
+    doAssert(a == 0b0000_1100)
+    a.setMask(4 .. 7)
+    doAssert(a == 0b1111_1100)
+    a.flipMask(1 .. 3)
+    doAssert(a == 0b1111_0010)
+    a.flipMask(2 .. 4)
+    doAssert(a == 0b1110_1110)
+    a.clearMask(2 .. 4)
+    doAssert(a == 0b1110_0010)
+    a.mask(0 .. 3)
+    doAssert(a == 0b0000_0010)
+
+    # composition of mask from slices:
+    let c = bitor(toMask[uint8](2 .. 3), toMask[uint8](5 .. 7))
+    doAssert(c == 0b1110_1100'u8)
+  block:
+    # bitslice versions of mask operations (non-mutating)
+    let a = 0b1100_1100'u8
+    doAssert(a.masked(toMask[uint8](2 .. 3)) == 0b0000_1100)
+    doAssert(a.masked(2 .. 3) == 0b0000_1100)
+    doAssert(a.setMasked(0 .. 3) == 0b1100_1111)
+    doAssert(a.setMasked(3 .. 4) == 0b1101_1100)
+    doAssert(a.flipMasked(0 .. 3) == 0b1100_0011)
+    doAssert(a.flipMasked(0 .. 7) == 0b0011_0011)
+    doAssert(a.flipMasked(2 .. 3) == 0b1100_0000)
+    doAssert(a.clearMasked(2 .. 3) == 0b1100_0000)
+    doAssert(a.clearMasked(3 .. 6) == 0b1000_0100)
   block:
     # single bit operations
     var v: uint8