diff options
-rw-r--r-- | changelog.md | 4 | ||||
-rw-r--r-- | lib/pure/bitops.nim | 227 | ||||
-rw-r--r-- | tests/stdlib/tbitops.nim | 71 |
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 |