/ bitnest.nim
bitnest.nim
1 import macros 2 3 macro make_bitnest*(name: untyped; base: type; blk: untyped): untyped = 4 # Parse block to extract field names and bit widths 5 var fields = new_seq[tuple[field_name: Nim_node, bit_width: int, offset: int]]() 6 var bit_offset = 0 7 8 # Determine the bit width of the base type 9 proc get_bit_width(t: Nim_node): int = 10 case $t 11 of "uint8": 8 12 of "uint16": 16 13 of "uint32": 32 14 of "uint64": 64 15 of "int8": 8 16 of "int16": 16 17 of "int32": 32 18 of "int64": 64 19 else: 0 20 21 let base_bit_width = get_bit_width(base) 22 23 # Helper function to process a statement list recursively 24 proc process_stmt_list(node: Nim_node, offset: var int, depth: int = 0): void = 25 if node.kind == nnk_stmt_list: 26 for i in 0 ..< node.len: 27 let child = node[i] 28 29 # Handle block: statements - these create overlapping fields 30 if child.kind == nnk_block_stmt: 31 # Get the block's statement list (it's second child, first is Empty) 32 let block_body = child[1] 33 # Create a local copy of offset for the block 34 # Fields within the block follow normal offset progression 35 # But the block's offset changes don't affect the outer scope 36 var block_offset = offset 37 process_stmt_list(block_body, block_offset, depth + 1) 38 # After block, offset is unchanged (block's fields overlap with what comes after) 39 40 elif (child.kind == nnk_call or child.kind == nnkCommand) and child.len >= 2: 41 let call_name = $child[0] 42 # Handle "reserved x" or "reserve x" - advance bit offset without creating fields 43 if call_name == "reserved" or call_name == "reserve": 44 let bit_width_node = child[1] 45 var bit_width: int 46 if bit_width_node.kind == nnkIntLit: 47 bit_width = bit_width_node.int_val.int 48 else: 49 bit_width = bit_width_node[0].int_val.int 50 offset += bit_width 51 # Taking apart Nim ASTs is truly obnoxious... 52 # This in particular pulls apart the following stanza: 53 # field_name: bits 54 # And populates the `fields` table with the relevant name and size. 55 elif child.kind == nnk_call: 56 let field_name = child[0] 57 let bit_width = child[1][0].int_val.int 58 fields.add((field_name, bit_width, offset)) 59 offset += bit_width 60 61 # Process the top-level statement list 62 process_stmt_list(blk, bit_offset) 63 64 # Validate that no field exceeds the base type's bit width 65 if base_bit_width > 0: 66 for (field_name, bit_width, offset) in fields: 67 let end_pos = offset + bit_width 68 if end_pos > base_bit_width: 69 error("Field '" & $field_name & "' with bit width " & $bit_width & 70 " at offset " & $offset & " exceeds base type size of " & $base_bit_width & " bits " & 71 "(ends at position " & $end_pos & ")") 72 elif base_bit_width == 0: 73 # Try to provide a helpful error message for unknown types 74 error("Cannot determine bit width for base type '" & $base & 75 "'. Supported types: uint8, uint16, uint32, uint64, int8, int16, int32, int64") 76 77 # Build result using templates instead of complex AST 78 result = new_stmt_list() 79 80 # Add type definition 81 result.add quote do: 82 type `name`* = distinct `base` 83 84 # Create getter/setter for each field 85 for (field_name, bit_width, offset) in fields: 86 let mask_val = (1 shl bit_width) - 1 87 88 let setter_name = ident($field_name & "=") 89 let mask_name = ident($name & "_" & $field_name & "_Mask") 90 let shift_name = ident($name & "_" & $field_name & "_Shift") 91 92 # Create mask constant 93 result.add quote do: 94 const 95 `mask_name`* = `mask_val` 96 `shift_name`* = `offset` 97 98 # Create getter 99 result.add quote do: 100 proc `field_name`*(self: `name`): `base` = 101 `base`(self) shr `offset` and `base`(`mask_val`) 102 103 # Create setter 104 result.add quote do: 105 proc `setter_name`*(self: var `name`, value: `base`) = 106 self = `name`( 107 (`base`(self) and not `base`(`mask_val` shl `offset`)) or 108 ((value and `base`(`mask_val`)) shl `offset`) 109 ) 110 111 when is_main_module: 112 make_bitnest(BlorpOp, uint32): 113 op: 8 114 x: 8 115 y: 16 116 117 make_bitnest(MeepOp, uint32): 118 op: 8 119 block: 120 x: 8 121 y: 16 122 block: 123 w: 24 124 125 make_bitnest(HolyHoles, uint32): 126 op: 8 127 reserve 8 128 w: 16 129 130 var blorp: BlorpOp = cast[BlorpOp](-1) 131 132 assert BlorpOp_op_mask == 0xFF 133 assert BlorpOp_op_shift == 0 134 assert BlorpOp_x_mask == 0xFF 135 assert BlorpOp_x_shift == 8 136 137 assert blorp.op == 255 138 assert blorp.x == 255 139 assert blorp.y == 65535 140 141 blorp.op = 53 142 143 assert blorp.op == 53 144 assert blorp.x == 255 145 assert blorp.y == 65535 146 147 blorp.y = 0 148 149 assert blorp.op == 53 150 assert blorp.x == 255 151 assert blorp.y == 0 152 153 var meep: MeepOp = cast[MeepOp](-1) 154 155 assert meep.op == 255 156 assert meep.w == 16777215 157 158 meep.x = 12 159 160 assert meep.x == 12 161 162 meep.w = 911 163 164 assert meep.op == 255 165 assert meep.w == 911 166 167 # Test nested blocks 168 make_bitnest(NestedOp, uint64): 169 outer1: 8 170 block: 171 inner1: 8 172 block: 173 deep1: 8 174 deep2: 8 175 inner2: 8 176 outer2: 8 177 178 var nested: NestedOp = cast[NestedOp](-1) 179 180 # All fields should have max value of their bit width 181 assert nested.outer1 == 255 182 assert nested.inner1 == 255 183 assert nested.deep1 == 255 184 assert nested.deep2 == 255 185 assert nested.inner2 == 255 186 assert nested.outer2 == 255 187 188 # Set nested field and verify it doesn't affect outer fields 189 nested.deep1 = 42 190 assert nested.outer1 == 255 191 assert nested.deep1 == 42 192 assert nested.outer2 == 255 193 194 # Verify outer2 overlaps with block fields at offset 8 195 nested.inner1 = 123 196 assert nested.inner1 == 123 197 assert nested.outer2 == 123 # outer2 should be overwritten by inner1 (both at offset 8) 198 199 # And setting outer2 affects inner fields at offset 8 200 nested.outer2 = 99 201 assert nested.outer2 == 99 202 assert nested.inner1 == 99 # inner1 is at offset 8 203 # inner2 is at offset 16 (after nested block), so it's unaffected 204 205 # the holed part shouldn't get touched' 206 var holed: HolyHoles = cast[HolyHoles](-1) 207 holed.op = 0 208 holed.w = 0 209 assert cast[uint32](holed) == 0xFF00 210