├── .gitattributes ├── .github └── workflows │ ├── CI.yml │ └── REUSE.yml ├── .gitignore ├── LICENSE ├── LICENSES ├── CC0-1.0.txt └── MIT.txt ├── README.md ├── REUSE.toml ├── bitjuggle.zig ├── build.zig └── build.zig.zon /.gitattributes: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: CC0-1.0 2 | # SPDX-FileCopyrightText: 2024 Lee Cannon 3 | 4 | *.zig text eol=lf 5 | *.zon text eol=lf 6 | -------------------------------------------------------------------------------- /.github/workflows/CI.yml: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: CC0-1.0 2 | # SPDX-FileCopyrightText: 2024 Lee Cannon 3 | 4 | name: CI 5 | 6 | on: 7 | push: 8 | branches: 9 | - main 10 | paths: 11 | - ".github/workflows/CI.yml" 12 | - "**.zig" 13 | - "**.zig.zon" 14 | pull_request: 15 | paths: 16 | - ".github/workflows/CI.yml" 17 | - "**.zig" 18 | - "**.zig.zon" 19 | schedule: 20 | - cron: "0 0 * * *" 21 | workflow_dispatch: 22 | 23 | jobs: 24 | CI: 25 | runs-on: ubuntu-latest 26 | steps: 27 | - uses: actions/checkout@v4 28 | - uses: mlugg/setup-zig@v1 29 | with: 30 | version: master 31 | 32 | - name: Lint 33 | run: zig fmt --check --ast-check . 34 | 35 | - name: Test 36 | run: zig build test --summary all 37 | -------------------------------------------------------------------------------- /.github/workflows/REUSE.yml: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: CC0-1.0 2 | # SPDX-FileCopyrightText: 2024 Lee Cannon 3 | 4 | name: REUSE Compliance Check 5 | 6 | on: 7 | push: 8 | pull_request: 9 | branches: 10 | - main 11 | 12 | jobs: 13 | compliance_check: 14 | runs-on: ubuntu-latest 15 | steps: 16 | - name: Checkout 17 | uses: actions/checkout@v4 18 | 19 | - name: REUSE Compliance Check 20 | uses: fsfe/reuse-action@v5 21 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: CC0-1.0 2 | # SPDX-FileCopyrightText: 2024 Lee Cannon 3 | 4 | .zig-cache/ 5 | zig-out/ 6 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2025 Lee Cannon 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /LICENSES/CC0-1.0.txt: -------------------------------------------------------------------------------- 1 | Creative Commons Legal Code 2 | 3 | CC0 1.0 Universal 4 | 5 | CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE 6 | LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN 7 | ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS 8 | INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES 9 | REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS 10 | PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM 11 | THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED 12 | HEREUNDER. 13 | 14 | Statement of Purpose 15 | 16 | The laws of most jurisdictions throughout the world automatically confer 17 | exclusive Copyright and Related Rights (defined below) upon the creator 18 | and subsequent owner(s) (each and all, an "owner") of an original work of 19 | authorship and/or a database (each, a "Work"). 20 | 21 | Certain owners wish to permanently relinquish those rights to a Work for 22 | the purpose of contributing to a commons of creative, cultural and 23 | scientific works ("Commons") that the public can reliably and without fear 24 | of later claims of infringement build upon, modify, incorporate in other 25 | works, reuse and redistribute as freely as possible in any form whatsoever 26 | and for any purposes, including without limitation commercial purposes. 27 | These owners may contribute to the Commons to promote the ideal of a free 28 | culture and the further production of creative, cultural and scientific 29 | works, or to gain reputation or greater distribution for their Work in 30 | part through the use and efforts of others. 31 | 32 | For these and/or other purposes and motivations, and without any 33 | expectation of additional consideration or compensation, the person 34 | associating CC0 with a Work (the "Affirmer"), to the extent that he or she 35 | is an owner of Copyright and Related Rights in the Work, voluntarily 36 | elects to apply CC0 to the Work and publicly distribute the Work under its 37 | terms, with knowledge of his or her Copyright and Related Rights in the 38 | Work and the meaning and intended legal effect of CC0 on those rights. 39 | 40 | 1. Copyright and Related Rights. A Work made available under CC0 may be 41 | protected by copyright and related or neighboring rights ("Copyright and 42 | Related Rights"). Copyright and Related Rights include, but are not 43 | limited to, the following: 44 | 45 | i. the right to reproduce, adapt, distribute, perform, display, 46 | communicate, and translate a Work; 47 | ii. moral rights retained by the original author(s) and/or performer(s); 48 | iii. publicity and privacy rights pertaining to a person's image or 49 | likeness depicted in a Work; 50 | iv. rights protecting against unfair competition in regards to a Work, 51 | subject to the limitations in paragraph 4(a), below; 52 | v. rights protecting the extraction, dissemination, use and reuse of data 53 | in a Work; 54 | vi. database rights (such as those arising under Directive 96/9/EC of the 55 | European Parliament and of the Council of 11 March 1996 on the legal 56 | protection of databases, and under any national implementation 57 | thereof, including any amended or successor version of such 58 | directive); and 59 | vii. other similar, equivalent or corresponding rights throughout the 60 | world based on applicable law or treaty, and any national 61 | implementations thereof. 62 | 63 | 2. Waiver. To the greatest extent permitted by, but not in contravention 64 | of, applicable law, Affirmer hereby overtly, fully, permanently, 65 | irrevocably and unconditionally waives, abandons, and surrenders all of 66 | Affirmer's Copyright and Related Rights and associated claims and causes 67 | of action, whether now known or unknown (including existing as well as 68 | future claims and causes of action), in the Work (i) in all territories 69 | worldwide, (ii) for the maximum duration provided by applicable law or 70 | treaty (including future time extensions), (iii) in any current or future 71 | medium and for any number of copies, and (iv) for any purpose whatsoever, 72 | including without limitation commercial, advertising or promotional 73 | purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each 74 | member of the public at large and to the detriment of Affirmer's heirs and 75 | successors, fully intending that such Waiver shall not be subject to 76 | revocation, rescission, cancellation, termination, or any other legal or 77 | equitable action to disrupt the quiet enjoyment of the Work by the public 78 | as contemplated by Affirmer's express Statement of Purpose. 79 | 80 | 3. Public License Fallback. Should any part of the Waiver for any reason 81 | be judged legally invalid or ineffective under applicable law, then the 82 | Waiver shall be preserved to the maximum extent permitted taking into 83 | account Affirmer's express Statement of Purpose. In addition, to the 84 | extent the Waiver is so judged Affirmer hereby grants to each affected 85 | person a royalty-free, non transferable, non sublicensable, non exclusive, 86 | irrevocable and unconditional license to exercise Affirmer's Copyright and 87 | Related Rights in the Work (i) in all territories worldwide, (ii) for the 88 | maximum duration provided by applicable law or treaty (including future 89 | time extensions), (iii) in any current or future medium and for any number 90 | of copies, and (iv) for any purpose whatsoever, including without 91 | limitation commercial, advertising or promotional purposes (the 92 | "License"). The License shall be deemed effective as of the date CC0 was 93 | applied by Affirmer to the Work. Should any part of the License for any 94 | reason be judged legally invalid or ineffective under applicable law, such 95 | partial invalidity or ineffectiveness shall not invalidate the remainder 96 | of the License, and in such case Affirmer hereby affirms that he or she 97 | will not (i) exercise any of his or her remaining Copyright and Related 98 | Rights in the Work or (ii) assert any associated claims and causes of 99 | action with respect to the Work, in either case contrary to Affirmer's 100 | express Statement of Purpose. 101 | 102 | 4. Limitations and Disclaimers. 103 | 104 | a. No trademark or patent rights held by Affirmer are waived, abandoned, 105 | surrendered, licensed or otherwise affected by this document. 106 | b. Affirmer offers the Work as-is and makes no representations or 107 | warranties of any kind concerning the Work, express, implied, 108 | statutory or otherwise, including without limitation warranties of 109 | title, merchantability, fitness for a particular purpose, non 110 | infringement, or the absence of latent or other defects, accuracy, or 111 | the present or absence of errors, whether or not discoverable, all to 112 | the greatest extent permissible under applicable law. 113 | c. Affirmer disclaims responsibility for clearing rights of other persons 114 | that may apply to the Work or any use thereof, including without 115 | limitation any person's Copyright and Related Rights in the Work. 116 | Further, Affirmer disclaims responsibility for obtaining any necessary 117 | consents, permissions or other rights required for any use of the 118 | Work. 119 | d. Affirmer understands and acknowledges that Creative Commons is not a 120 | party to this document and has no duty or obligation with respect to 121 | this CC0 or use of the Work. 122 | -------------------------------------------------------------------------------- /LICENSES/MIT.txt: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and 6 | associated documentation files (the "Software"), to deal in the Software without restriction, including 7 | without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the 9 | following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in all copies or substantial 12 | portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT 15 | LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO 16 | EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 17 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE 18 | USE OR OTHER DEALINGS IN THE SOFTWARE. 19 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # zig-bitjuggle 2 | 3 | This package contains various "bit juggling" helpers and functionality: 4 | 5 | - `isBitSet` - Check if a bit is set 6 | - `getBit` - Get the value of a bit 7 | - `getBits` - Get a range of bits 8 | - `setBit` - Set a specific bit 9 | - `setBits` - Set a range of bits 10 | - `Bitfield` - Used along with `extern union` to represent arbitrary bit fields 11 | - `Bit` - Used along with `extern union` to represent bit fields 12 | - `Boolean` - Used along with `extern union` to represent boolean bit fields 13 | 14 | ## Installation 15 | 16 | Add the dependency to `build.zig.zon`: 17 | 18 | ```sh 19 | zig fetch --save git+https://github.com/leecannon/zig-bitjuggle 20 | ``` 21 | 22 | Then add the following to `build.zig`: 23 | 24 | ```zig 25 | const bitjuggle = b.dependency("bitjuggle", .{}); 26 | exe.root_module.addImport("bitjuggle", bitjuggle.module("bitjuggle")); 27 | ``` 28 | -------------------------------------------------------------------------------- /REUSE.toml: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: CC0-1.0 2 | # SPDX-FileCopyrightText: 2025 Lee Cannon 3 | 4 | version = 1 5 | SPDX-PackageName = "zig-bitjuggle" 6 | SPDX-PackageSupplier = "Lee Cannon " 7 | SPDX-PackageDownloadLocation = "https://github.com/leecannon/zig-bitjuggle" 8 | 9 | [[annotations]] 10 | path = "**.md" 11 | precedence = "aggregate" 12 | SPDX-License-Identifier = "CC0-1.0" 13 | SPDX-FileCopyrightText = "2025 Lee Cannon " 14 | -------------------------------------------------------------------------------- /bitjuggle.zig: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | // SPDX-FileCopyrightText: 2025 Lee Cannon 3 | 4 | /// Returns `true` if the the bit at index `bit` is set (equals 1). 5 | /// 6 | /// Note: that index 0 is the least significant bit, while index `length() - 1` is the most significant bit. 7 | /// 8 | /// ```zig 9 | /// const a: u8 = 0b00000010; 10 | /// 11 | /// try std.testing.expect(!isBitSet(a, 0)); 12 | /// try std.testing.expect(isBitSet(a, 1)); 13 | /// ``` 14 | pub inline fn isBitSet(target: anytype, comptime bit: comptime_int) bool { 15 | const TargetType = @TypeOf(target); 16 | 17 | comptime { 18 | if (@typeInfo(TargetType) == .int) { 19 | if (@typeInfo(TargetType).int.signedness != .unsigned) { 20 | @compileError("requires an unsigned integer, found " ++ @typeName(TargetType)); 21 | } 22 | if (bit >= @bitSizeOf(TargetType)) { 23 | @compileError("bit index is out of bounds of the bit field"); 24 | } 25 | } else if (@typeInfo(TargetType) == .comptime_int) { 26 | if (target < 0) { 27 | @compileError("requires an unsigned integer, found " ++ @typeName(TargetType)); 28 | } 29 | } else { 30 | @compileError("requires an unsigned integer, found " ++ @typeName(TargetType)); 31 | } 32 | } 33 | 34 | const mask: TargetType = comptime blk: { 35 | const MaskType = std.meta.Int(.unsigned, bit + 1); 36 | var temp: MaskType = std.math.maxInt(MaskType); 37 | temp <<= bit; 38 | break :blk temp; 39 | }; 40 | 41 | return (target & mask) != 0; 42 | } 43 | 44 | test isBitSet { 45 | // comptime 46 | comptime { 47 | const a: comptime_int = 0b00000000; 48 | try std.testing.expect(!isBitSet(a, 0)); 49 | try std.testing.expect(!isBitSet(a, 1)); 50 | 51 | const b: comptime_int = 0b11111111; 52 | try std.testing.expect(isBitSet(b, 0)); 53 | try std.testing.expect(isBitSet(b, 1)); 54 | 55 | const c: comptime_int = 0b00000010; 56 | try std.testing.expect(!isBitSet(c, 0)); 57 | try std.testing.expect(isBitSet(c, 1)); 58 | } 59 | 60 | // runtime 61 | { 62 | var value: u8 = 0b00000000; 63 | try std.testing.expect(!isBitSet(value, 0)); 64 | try std.testing.expect(!isBitSet(value, 1)); 65 | 66 | value = 0b11111111; 67 | try std.testing.expect(isBitSet(value, 0)); 68 | try std.testing.expect(isBitSet(value, 1)); 69 | 70 | value = 0b00000010; 71 | try std.testing.expect(!isBitSet(value, 0)); 72 | try std.testing.expect(isBitSet(value, 1)); 73 | } 74 | } 75 | 76 | /// Get the value of the bit at index `bit`. 77 | /// 78 | /// Note: that index 0 is the least significant bit, while index `length() - 1` is the most significant bit. 79 | /// 80 | /// ```zig 81 | /// const a: u8 = 0b00000010; 82 | /// 83 | /// try std.testing.expect(getBit(a, 0) == 0); 84 | /// try std.testing.expect(getBit(a, 1) == 1); 85 | /// ``` 86 | pub inline fn getBit(target: anytype, comptime bit: comptime_int) u1 { 87 | return @intFromBool(isBitSet(target, bit)); 88 | } 89 | 90 | test getBit { 91 | // comptime 92 | comptime { 93 | const a: comptime_int = 0b00000000; 94 | try std.testing.expectEqual(@as(u1, 0), getBit(a, 0)); 95 | try std.testing.expectEqual(@as(u1, 0), getBit(a, 1)); 96 | 97 | const b: comptime_int = 0b11111111; 98 | try std.testing.expectEqual(@as(u1, 1), getBit(b, 0)); 99 | try std.testing.expectEqual(@as(u1, 1), getBit(b, 1)); 100 | 101 | const c: comptime_int = 0b00000010; 102 | try std.testing.expectEqual(@as(u1, 0), getBit(c, 0)); 103 | try std.testing.expectEqual(@as(u1, 1), getBit(c, 1)); 104 | } 105 | 106 | // runtime 107 | { 108 | var value: u8 = 0b00000000; 109 | try std.testing.expectEqual(@as(u1, 0), getBit(value, 0)); 110 | try std.testing.expectEqual(@as(u1, 0), getBit(value, 1)); 111 | 112 | value = 0b11111111; 113 | try std.testing.expectEqual(@as(u1, 1), getBit(value, 0)); 114 | try std.testing.expectEqual(@as(u1, 1), getBit(value, 1)); 115 | 116 | value = 0b00000010; 117 | try std.testing.expectEqual(@as(u1, 0), getBit(value, 0)); 118 | try std.testing.expectEqual(@as(u1, 1), getBit(value, 1)); 119 | } 120 | } 121 | 122 | /// Obtains the `number_of_bits` bits starting at `start_bit`. 123 | /// 124 | /// Where `start_bit` is the lowest significant bit to fetch. 125 | /// 126 | /// ```zig 127 | /// const a: u8 = 0b01101100; 128 | /// const b = getBits(a, 2, 4); 129 | /// try std.testing.expectEqual(@as(u4,0b1011), b); 130 | /// ``` 131 | pub inline fn getBits( 132 | target: anytype, 133 | comptime start_bit: comptime_int, 134 | comptime number_of_bits: comptime_int, 135 | ) std.meta.Int(.unsigned, number_of_bits) { 136 | const TargetType = @TypeOf(target); 137 | 138 | comptime { 139 | if (number_of_bits == 0) @compileError("non-zero number_of_bits must be provided"); 140 | 141 | if (@typeInfo(TargetType) == .int) { 142 | if (@typeInfo(TargetType).int.signedness != .unsigned) { 143 | @compileError("requires an unsigned integer, found " ++ @typeName(TargetType)); 144 | } 145 | if (start_bit >= @bitSizeOf(TargetType)) { 146 | @compileError("start_bit index is out of bounds of the bit field"); 147 | } 148 | if (start_bit + number_of_bits > @bitSizeOf(TargetType)) { 149 | @compileError("start_bit + number_of_bits is out of bounds of the bit field"); 150 | } 151 | } else if (@typeInfo(TargetType) == .comptime_int) { 152 | if (target < 0) { 153 | @compileError("requires a positive integer, found a negative"); 154 | } 155 | } else { 156 | @compileError("requires an unsigned integer, found " ++ @typeName(TargetType)); 157 | } 158 | } 159 | 160 | return @truncate(target >> start_bit); 161 | } 162 | 163 | test getBits { 164 | // comptime 165 | comptime { 166 | const a: comptime_int = 0b01101100; 167 | const b = getBits(a, 2, 4); 168 | try std.testing.expectEqual(@as(u4, 0b1011), b); 169 | } 170 | 171 | // runtime 172 | { 173 | var value: u8 = 0b01101100; 174 | try std.testing.expectEqual( 175 | @as(u4, 0b1011), 176 | getBits(value, 2, 4), 177 | ); 178 | 179 | value = 0b01101100; 180 | try std.testing.expectEqual( 181 | @as(u3, 0b100), 182 | getBits(value, 0, 3), 183 | ); 184 | } 185 | } 186 | 187 | /// Sets the bit at the index `bit` to the value `value` (where true means a value of '1' and false means a value of '0'). 188 | /// 189 | /// Note: that index 0 is the least significant bit, while index `length() - 1` is the most significant bit. 190 | /// 191 | /// ```zig 192 | /// var val: u8 = 0b00000000; 193 | /// try std.testing.expect(!getBit(val, 0)); 194 | /// setBit( &val, 0, true); 195 | /// try std.testing.expect(getBit(val, 0)); 196 | /// ``` 197 | pub inline fn setBit(target: anytype, comptime bit: comptime_int, value: u1) void { 198 | const ptr_type_info: std.builtin.Type = @typeInfo(@TypeOf(target)); 199 | comptime { 200 | if (ptr_type_info != .pointer) @compileError("not a pointer"); 201 | } 202 | 203 | const TargetType = ptr_type_info.pointer.child; 204 | 205 | comptime { 206 | if (@typeInfo(TargetType) == .int) { 207 | if (@typeInfo(TargetType).int.signedness != .unsigned) { 208 | @compileError("requires an unsigned integer, found " ++ @typeName(TargetType)); 209 | } 210 | if (bit >= @bitSizeOf(TargetType)) { 211 | @compileError("bit index is out of bounds of the bit field"); 212 | } 213 | } else if (@typeInfo(TargetType) == .comptime_int) { 214 | @compileError("comptime_int is unsupported"); 215 | } else { 216 | @compileError("requires an unsigned integer, found " ++ @typeName(TargetType)); 217 | } 218 | } 219 | 220 | const mask = comptime ~(@as(TargetType, 1) << bit); 221 | 222 | target.* = (target.* & mask) | (@as(TargetType, value) << bit); 223 | } 224 | 225 | test setBit { 226 | var val: u8 = 0b00000000; 227 | try std.testing.expect(!isBitSet(val, 0)); 228 | setBit(&val, 0, 1); 229 | try std.testing.expect(isBitSet(val, 0)); 230 | setBit(&val, 0, 0); 231 | try std.testing.expect(!isBitSet(val, 0)); 232 | } 233 | 234 | /// Sets the range of bits starting at `start_bit` upto and excluding `start_bit` + `number_of_bits`. 235 | /// 236 | /// ```zig 237 | /// var val: u8 = 0b10000000; 238 | /// setBits(&val, 2, 4, 0b00001101); 239 | /// try std.testing.expectEqual(@as(u8, 0b10110100), val); 240 | /// ``` 241 | /// 242 | /// ## Panic 243 | /// In safe modes this method will panic if the `value` exceeds the bit range of the type of `target`. 244 | pub fn setBits( 245 | target: anytype, 246 | comptime start_bit: comptime_int, 247 | comptime number_of_bits: comptime_int, 248 | value: anytype, 249 | ) void { 250 | const ptr_type_info: std.builtin.Type = @typeInfo(@TypeOf(target)); 251 | comptime { 252 | if (ptr_type_info != .pointer) @compileError("not a pointer"); 253 | } 254 | 255 | const TargetType = ptr_type_info.pointer.child; 256 | const end_bit = start_bit + number_of_bits; 257 | 258 | comptime { 259 | if (number_of_bits == 0) @compileError("non-zero number_of_bits must be provided"); 260 | 261 | if (@typeInfo(TargetType) == .int) { 262 | if (@typeInfo(TargetType).int.signedness != .unsigned) { 263 | @compileError("requires an unsigned integer, found " ++ @typeName(TargetType)); 264 | } 265 | if (start_bit >= @bitSizeOf(TargetType)) { 266 | @compileError("start_bit index is out of bounds of the bit field"); 267 | } 268 | if (end_bit > @bitSizeOf(TargetType)) { 269 | @compileError("start_bit + number_of_bits is out of bounds of the bit field"); 270 | } 271 | } else if (@typeInfo(TargetType) == .comptime_int) { 272 | @compileError("comptime_int is unsupported"); 273 | } else { 274 | @compileError("requires an unsigned integer, found " ++ @typeName(TargetType)); 275 | } 276 | } 277 | 278 | const peer_value: TargetType = value; 279 | 280 | if (std.debug.runtime_safety) { 281 | if (getBits(peer_value, 0, (end_bit - start_bit)) != peer_value) { 282 | @panic("value exceeds bit range"); 283 | } 284 | } 285 | 286 | const bitmask: TargetType = comptime blk: { 287 | var bitmask = ~@as(TargetType, 0); 288 | bitmask <<= (@bitSizeOf(TargetType) - end_bit); 289 | bitmask >>= (@bitSizeOf(TargetType) - end_bit); 290 | bitmask >>= start_bit; 291 | bitmask <<= start_bit; 292 | break :blk ~bitmask; 293 | }; 294 | 295 | target.* = (target.* & bitmask) | (peer_value << start_bit); 296 | } 297 | 298 | test setBits { 299 | var val: u8 = 0b10000000; 300 | setBits(&val, 2, 4, 0b00001101); 301 | try std.testing.expectEqual(@as(u8, 0b10110100), val); 302 | } 303 | 304 | /// Defines a bitfield. 305 | pub fn Bitfield( 306 | /// The type of the underlying integer containing the bitfield. 307 | comptime FieldType: type, 308 | /// The starting bit index of the bitfield. 309 | comptime shift_amount: usize, 310 | /// The number of bits in the bitfield. 311 | comptime num_bits: usize, 312 | ) type { 313 | if (shift_amount + num_bits > @bitSizeOf(FieldType)) { 314 | @compileError("bitfield doesn't fit"); 315 | } 316 | 317 | const self_mask: FieldType = ((1 << num_bits) - 1) << shift_amount; 318 | 319 | const ValueType: type = std.meta.Int(.unsigned, num_bits); 320 | 321 | return extern struct { 322 | dummy: FieldType, 323 | 324 | const Self = @This(); 325 | 326 | pub fn write(self: *Self, val: ValueType) void { 327 | self.writeNoShiftFullSize(@as(FieldType, val) << shift_amount); 328 | } 329 | 330 | /// Writes a value to the bitfield without shifting, all bits in `val` not in the bitfield are ignored. 331 | /// 332 | /// Not atomic. 333 | pub fn writeNoShiftFullSize(self: *Self, val: FieldType) void { 334 | self.field().* = 335 | (self.field().* & ~self_mask) | 336 | (val & self_mask); 337 | } 338 | 339 | pub fn read(self: Self) ValueType { 340 | return @truncate(self.readNoShiftFullSize() >> shift_amount); 341 | } 342 | 343 | /// Reads the full value of the bitfield without shifting and without truncating the type. 344 | /// 345 | /// All bits not in the bitfield will be zero. 346 | pub inline fn readNoShiftFullSize(self: Self) FieldType { 347 | return (self.field().* & self_mask); 348 | } 349 | 350 | /// A function to access the underlying integer as `FieldType`. 351 | /// Uses `anytype` to support both const and non-const access. 352 | inline fn field(self: anytype) PtrCastPreserveCV(Self, @TypeOf(self), FieldType) { 353 | return @ptrCast(self); 354 | } 355 | }; 356 | } 357 | 358 | test Bitfield { 359 | const S = extern union { 360 | low: Bitfield(u32, 0, 16), 361 | high: Bitfield(u32, 16, 16), 362 | val: u32, 363 | }; 364 | 365 | try std.testing.expect(@sizeOf(S) == 4); 366 | try std.testing.expect(@bitSizeOf(S) == 32); 367 | 368 | var s: S = .{ .val = 0x13376969 }; 369 | 370 | try std.testing.expect(s.low.read() == 0x6969); 371 | try std.testing.expect(s.high.read() == 0x1337); 372 | 373 | s.low.write(0x1337); 374 | s.high.write(0x6969); 375 | 376 | try std.testing.expect(s.val == 0x69691337); 377 | } 378 | 379 | /// Defines a struct representing a single bit. 380 | fn BitType( 381 | /// The type of the underlying integer containing the bit. 382 | comptime FieldType: type, 383 | /// The bit index of the bit. 384 | comptime shift_amount: usize, 385 | /// The type of the bit value, either u1 or bool. 386 | comptime ValueType: type, 387 | ) type { 388 | return extern struct { 389 | bits: Bitfield(FieldType, shift_amount, 1), 390 | 391 | const Self = @This(); 392 | 393 | pub fn read(self: Self) ValueType { 394 | return @bitCast(getBit(self.bits.field().*, shift_amount)); 395 | } 396 | 397 | pub fn write(self: *Self, val: ValueType) void { 398 | setBit(self.bits.field(), shift_amount, @bitCast(val)); 399 | } 400 | }; 401 | } 402 | 403 | /// Defines a struct representing a single bit with a u1 value. 404 | pub fn Bit( 405 | /// The type of the underlying integer containing the bit. 406 | comptime FieldType: type, 407 | /// The bit index of the bit. 408 | comptime shift_amount: usize, 409 | ) type { 410 | return BitType(FieldType, shift_amount, u1); 411 | } 412 | 413 | test Bit { 414 | const S = extern union { 415 | low: Bit(u32, 0), 416 | high: Bit(u32, 1), 417 | val: u32, 418 | }; 419 | 420 | try std.testing.expect(@sizeOf(S) == 4); 421 | try std.testing.expect(@bitSizeOf(S) == 32); 422 | 423 | var s: S = .{ .val = 1 }; 424 | 425 | try std.testing.expect(s.low.read() == 1); 426 | try std.testing.expect(s.high.read() == 0); 427 | 428 | s.low.write(0); 429 | s.high.write(1); 430 | 431 | try std.testing.expect(s.val == 2); 432 | } 433 | 434 | /// Defines a struct representing a single bit with a boolean value. 435 | pub fn Boolean( 436 | /// The type of the underlying integer containing the bit. 437 | comptime FieldType: type, 438 | /// The bit index of the bit. 439 | comptime shift_amount: usize, 440 | ) type { 441 | return BitType(FieldType, shift_amount, bool); 442 | } 443 | 444 | test Boolean { 445 | const S = extern union { 446 | low: Boolean(u32, 0), 447 | high: Boolean(u32, 1), 448 | val: u32, 449 | }; 450 | 451 | try std.testing.expect(@sizeOf(S) == 4); 452 | try std.testing.expect(@bitSizeOf(S) == 32); 453 | 454 | var s: S = .{ .val = 2 }; 455 | 456 | try std.testing.expect(s.low.read() == false); 457 | try std.testing.expect(s.high.read() == true); 458 | 459 | s.low.write(true); 460 | s.high.write(false); 461 | 462 | try std.testing.expect(s.val == 1); 463 | } 464 | 465 | /// Casts a pointer while preserving const/volatile qualifiers. 466 | inline fn PtrCastPreserveCV(comptime T: type, comptime PtrToT: type, comptime NewT: type) type { 467 | return switch (PtrToT) { 468 | *T => *NewT, 469 | *const T => *const NewT, 470 | *volatile T => *volatile NewT, 471 | *const volatile T => *const volatile NewT, 472 | else => @compileError("invalid type " ++ @typeName(PtrToT) ++ " given to PtrCastPreserveCV"), 473 | }; 474 | } 475 | 476 | comptime { 477 | if (builtin.cpu.arch.endian() != .little) @compileError("'bitjuggle' assumes little endian"); 478 | } 479 | 480 | comptime { 481 | std.testing.refAllDeclsRecursive(@This()); 482 | } 483 | 484 | const std = @import("std"); 485 | const builtin = @import("builtin"); 486 | -------------------------------------------------------------------------------- /build.zig: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | // SPDX-FileCopyrightText: 2025 Lee Cannon 3 | 4 | const std = @import("std"); 5 | 6 | pub fn build(b: *std.Build) !void { 7 | _ = b.addModule("bitjuggle", .{ 8 | .root_source_file = b.path("bitjuggle.zig"), 9 | }); 10 | 11 | const target = b.standardTargetOptions(.{}); 12 | const optimize = b.standardOptimizeOption(.{}); 13 | 14 | const test_exe = b.addTest(.{ 15 | .root_module = b.createModule(.{ 16 | .root_source_file = b.path("bitjuggle.zig"), 17 | .target = target, 18 | .optimize = optimize, 19 | }), 20 | }); 21 | const run_test_exe = b.addRunArtifact(test_exe); 22 | const test_step = b.step("test", "Run tests"); 23 | test_step.dependOn(&run_test_exe.step); 24 | 25 | // check step 26 | { 27 | const check_test_exe = b.addTest(.{ 28 | .root_module = b.createModule(.{ 29 | .root_source_file = b.path("bitjuggle.zig"), 30 | .target = target, 31 | .optimize = optimize, 32 | }), 33 | }); 34 | const check_test_step = b.step("check", ""); 35 | check_test_step.dependOn(&check_test_exe.step); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /build.zig.zon: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | // SPDX-FileCopyrightText: 2025 Lee Cannon 3 | 4 | .{ 5 | .name = .bitjuggle, 6 | 7 | .version = "1.0.12", 8 | 9 | .minimum_zig_version = "0.14.0-dev.3445+6c3cbb0c8", 10 | 11 | .paths = .{""}, 12 | 13 | .fingerprint = 0xc4bb12c4ef549748, 14 | } 15 | --------------------------------------------------------------------------------