Skip to content

Commit b92ba6e

Browse files
committed
add the ability to lock fields in the bitfield
1 parent fac1f5e commit b92ba6e

File tree

4 files changed

+72
-7
lines changed

4 files changed

+72
-7
lines changed

README.md

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ The goal that BitField strives to accomplish is to allow for the creation of min
2323
require "bitfield"
2424
```
2525

26+
### Standard Definition
27+
2628
You can define both numeric and boolean fields. When defining a numeric field, its type is that of the class' generic type. Additionally, the entire value of the bitfield is accessible though the #value method. An example bitfield might look something like this
2729

2830
```crystal
@@ -44,6 +46,36 @@ bf.bool = false
4446
bf.value # => 0x94
4547
```
4648

49+
### Locking Values
50+
51+
Since I've primarily developed this shard for use in my emulator projects where bitfields are typically used to define IO registers, it's typical to have a register where fields may need to be locked in place in a bitfield. That is achievable by simply providing a `lock: true` argument with the field you want to lock.
52+
53+
```crystal
54+
class TestLock < BitField(UInt8)
55+
num top, 3
56+
num mid, 2, lock: true
57+
num bot, 3
58+
end
59+
```
60+
61+
The effect of locking a field is that it will not change when writing to bitfield's #value method. If it needs to be mutated after initialization, it will need to be through the field's specific setter method.
62+
63+
```crystal
64+
bf = TestLock.new 0x00
65+
bf.top # => 0x0
66+
bf.mid # => 0x0
67+
bf.bot # => 0x0
68+
bf.value = 0xFF
69+
bf.top # => 0x7
70+
bf.mid # => 0x0
71+
bf.bot # => 0x7
72+
bf.value # => 0xE7
73+
bf.mid = 0x3
74+
bf.value # => 0xFF
75+
```
76+
77+
### Errors
78+
4779
The full number of bits must be specified. For example, if you define your bitfield over a UInt8, you must specify exactly 8 bits in the field. If you fail to do so, you will get a message like this at runtime.
4880

4981
```

shard.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
name: bitfield
2-
version: 0.1.2
2+
version: 0.1.3
33

44
authors:
55
- Matthew Berry <[email protected]>

spec/bitfield_spec.cr

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,14 @@ class TestMethods < BitField(UInt8)
3030
end
3131
end
3232

33+
class TestLock < BitField(UInt8)
34+
bool bool
35+
bool locked_bool, lock: true
36+
num num, 2
37+
num locked_num, 2, lock: true
38+
num extra, 2
39+
end
40+
3341
describe BitField do
3442
it "gets whole value" do
3543
bf = Test8.new 0xAF
@@ -124,4 +132,17 @@ describe BitField do
124132
bf.hash.should_not eq Test8.new(0xFA).hash
125133
Test8.new(0xAF).hash.should_not eq Test8.new(0xFA).hash
126134
end
135+
136+
it "allows locking values" do
137+
bf = TestLock.new 0b00000000
138+
bf.value.should eq 0b00000000
139+
bf.value = 0xFF
140+
bf.value.should eq 0b10110011
141+
bf.locked_num = 0b01
142+
bf.value.should eq 0b10110111
143+
bf.locked_bool = true
144+
bf.value.should eq 0b11110111
145+
bf.value = 0
146+
bf.value.should eq 0b01000100
147+
end
127148
end

src/bitfield.cr

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,12 @@ abstract class BitField(T)
22
macro inherited
33
{% raise "Cannot create a BitField from a non-integer" unless T <= Int %}
44

5-
FIELDS = [] of Tuple(String, Symbol, Int32) # name, type, size (types don't actually matter here..)
5+
FIELDS = [] of Tuple(String, Symbol, Int32, Bool) # name, type, size, lock (types don't actually matter here..)
66

77
macro finished
88
build_methods
99

10-
property value : T
10+
getter value : T
1111

1212
def initialize(@value : T)
1313
bits = sizeof(T) * 8
@@ -18,27 +18,35 @@ abstract class BitField(T)
1818
end
1919
end
2020

21-
macro num(name, size)
22-
{% FIELDS << {name, :num, size} %}
21+
macro num(name, size, lock = false)
22+
{% FIELDS << {name, :num, size, lock} %}
2323
end
2424

25-
macro bool(name)
26-
{% FIELDS << {name, :bool, 1} %}
25+
macro bool(name, lock = false)
26+
{% FIELDS << {name, :bool, 1, lock} %}
2727
end
2828

2929
macro build_methods
3030
{% pos = 0 %}
3131
{% FIELDS.map { |f| pos += f[2] } %}
3232
SIZE = {{pos}}
33+
{% mask = 0 %}
3334

3435
{% for field in FIELDS %}
3536
{% name = field[0].id %}
3637
{% bool = field[1] == :bool %}
3738
{% size = field[2] %}
39+
{% lock = field[3] %}
3840
{% type = bool ? Bool : T %}
3941

4042
{% pos -= size %}
4143

44+
{% if lock %}
45+
{% for i in (0...size) %}
46+
{% mask = mask | 1 << (pos + i) %}
47+
{% end %}
48+
{% end %}
49+
4250
def {{name}} : {{type}}
4351
get_val({{size}}, {{pos}}) {% if bool %} > 0 {% end %}
4452
end
@@ -48,6 +56,10 @@ abstract class BitField(T)
4856
set_val({{size}}, {{pos}})
4957
end
5058
{% end %}
59+
60+
def value=(value : T)
61+
@value = (@value & {{mask}}) | (value & ~{{mask}})
62+
end
5163
end
5264

5365
macro get_val(size, start)

0 commit comments

Comments
 (0)