/ 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