Class: Philiprehberger::BitField::Base

Inherits:
Object
  • Object
show all
Includes:
Comparable
Defined in:
lib/philiprehberger/bit_field.rb

Overview

Named bit flags with symbolic access, set operations, and serialization

Examples:

class Permissions < Philiprehberger::BitField::Base
  flag :read, 0
  flag :write, 1
  flag :execute, 2
  group :read_write, [:read, :write]
end
perms = Permissions.new(:read, :write)
perms.read?    # => true
perms.execute? # => false

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(*initial_flags) ⇒ Base

Create a new bit field with the given flags set

Parameters:

  • initial_flags (Array<Symbol>)

    flags to set initially



139
140
141
142
# File 'lib/philiprehberger/bit_field.rb', line 139

def initialize(*initial_flags)
  @value = 0
  initial_flags.each { |f| set(f) }
end

Class Method Details

.flag(name, position) ⇒ void

This method returns an undefined value.

Define a named flag at the given bit position

Parameters:

  • name (Symbol)

    the flag name

  • position (Integer)

    the bit position (0-based)

Raises:



31
32
33
34
35
36
37
38
39
40
41
# File 'lib/philiprehberger/bit_field.rb', line 31

def flag(name, position)
  raise Error, 'position must be a non-negative integer' unless position.is_a?(Integer) && position >= 0
  raise Error, "flag #{name} already defined" if flags_map.key?(name)
  raise Error, "position #{position} already used" if flags_map.any? { |_, p| p == position }

  flags_map[name] = position

  define_method(:"#{name}?") do
    flag_set?(name)
  end
end

.flagsArray<Symbol>

Return all defined flag names

Returns:

  • (Array<Symbol>)


61
62
63
# File 'lib/philiprehberger/bit_field.rb', line 61

def flags
  flags_map.keys
end

.flags_mapObject

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.



118
119
120
# File 'lib/philiprehberger/bit_field.rb', line 118

def flags_map
  @flags_map ||= {}
end

.from_h(hash) ⇒ Base

Create an instance from a hash

Parameters:

  • hash (Hash)

    hash with :flags/:value or “flags”/“value” keys

Returns:

Raises:



110
111
112
113
114
115
# File 'lib/philiprehberger/bit_field.rb', line 110

def from_h(hash)
  value = hash[:value] || hash['value']
  raise Error, 'hash must include a value key' if value.nil?

  from_i(value)
end

.from_i(value) ⇒ Base

Create an instance from an integer value

Parameters:

  • value (Integer)

    the integer representation

Returns:

Raises:



89
90
91
92
93
94
95
# File 'lib/philiprehberger/bit_field.rb', line 89

def from_i(value)
  raise Error, 'value must be a non-negative integer' unless value.is_a?(Integer) && value >= 0

  instance = new
  instance.instance_variable_set(:@value, value)
  instance
end

.from_json(json_string) ⇒ Base

Create an instance from a JSON string

Parameters:

  • json_string (String)

    JSON with “flags” and “value” keys

Returns:



101
102
103
104
# File 'lib/philiprehberger/bit_field.rb', line 101

def from_json(json_string)
  data = JSON.parse(json_string)
  from_h(data)
end

.group(name, flag_names) ⇒ void

This method returns an undefined value.

Define a named group of flags

Parameters:

  • name (Symbol)

    the group name

  • flag_names (Array<Symbol>)

    the flags in this group

Raises:



48
49
50
51
52
53
54
55
56
# File 'lib/philiprehberger/bit_field.rb', line 48

def group(name, flag_names)
  raise Error, "group #{name} already defined" if groups_map.key?(name)

  flag_names.each do |f|
    raise Error, "unknown flag #{f} in group #{name}" unless flags_map.key?(f)
  end

  groups_map[name] = flag_names.dup.freeze
end

.groupsHash{Symbol => Array<Symbol>}

Return all defined group definitions

Returns:

  • (Hash{Symbol => Array<Symbol>})


68
69
70
# File 'lib/philiprehberger/bit_field.rb', line 68

def groups
  groups_map.dup
end

.groups_mapObject

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.



123
124
125
# File 'lib/philiprehberger/bit_field.rb', line 123

def groups_map
  @groups_map ||= {}
end

.inherited(subclass) ⇒ Object

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.



128
129
130
131
132
# File 'lib/philiprehberger/bit_field.rb', line 128

def inherited(subclass)
  super
  subclass.instance_variable_set(:@flags_map, flags_map.dup)
  subclass.instance_variable_set(:@groups_map, groups_map.dup)
end

.strict(*initial_flags) ⇒ Base

Create an instance that raises on unknown flag names

Parameters:

  • initial_flags (Array<Symbol>)

    flags to set initially

Returns:

Raises:

  • (Error)

    if any flag name is not defined



77
78
79
80
81
82
83
# File 'lib/philiprehberger/bit_field.rb', line 77

def strict(*initial_flags)
  initial_flags.each do |f|
    raise Error, "unknown flag: #{f}" unless flags_map.key?(f)
  end

  new(*initial_flags)
end

Instance Method Details

#&(other) ⇒ Base

Bitwise AND

Parameters:

  • other (Base)

    another bit field of the same type

Returns:

Raises:



346
347
348
349
350
# File 'lib/philiprehberger/bit_field.rb', line 346

def &(other)
  raise Error, 'cannot combine different bit field types' unless other.is_a?(self.class)

  self.class.from_i(@value & other.to_i)
end

#<=>(other) ⇒ Integer?

Compare by integer value

Parameters:

  • other (Base)

    another bit field

Returns:

  • (Integer, nil)


366
367
368
369
370
# File 'lib/philiprehberger/bit_field.rb', line 366

def <=>(other)
  return nil unless other.is_a?(self.class)

  @value <=> other.to_i
end

#==(other) ⇒ Boolean Also known as: eql?

Equality check

Parameters:

  • other (Base)

    another bit field

Returns:

  • (Boolean)


376
377
378
# File 'lib/philiprehberger/bit_field.rb', line 376

def ==(other)
  other.is_a?(self.class) && @value == other.to_i
end

#^(other) ⇒ Base

Bitwise XOR

Parameters:

  • other (Base)

    another bit field of the same type

Returns:

Raises:



356
357
358
359
360
# File 'lib/philiprehberger/bit_field.rb', line 356

def ^(other)
  raise Error, 'cannot combine different bit field types' unless other.is_a?(self.class)

  self.class.from_i(@value ^ other.to_i)
end

#added_flags(other) ⇒ Array<Symbol>

Return flags set in self but not in other

Parameters:

  • other (Base)

    another bit field of the same type

Returns:

  • (Array<Symbol>)

Raises:



391
392
393
394
395
# File 'lib/philiprehberger/bit_field.rb', line 391

def added_flags(other)
  raise Error, 'cannot compare different bit field types' unless other.is_a?(self.class)

  self.class.flags.select { |f| flag_set?(f) && !other.flag_set?(f) }
end

#clear(flag) ⇒ self

Clear a flag

Parameters:

  • flag (Symbol)

    the flag name

Returns:

  • (self)


189
190
191
192
193
# File 'lib/philiprehberger/bit_field.rb', line 189

def clear(flag)
  pos = position_for(flag)
  @value &= ~(1 << pos)
  self
end

#clear_allself

Clear all defined flags

Returns:

  • (self)


216
217
218
219
# File 'lib/philiprehberger/bit_field.rb', line 216

def clear_all
  @value = 0
  self
end

#clear_flags(*flag_names) ⇒ self

Clear multiple specific flags at once

Parameters:

  • flag_names (Array<Symbol>)

    flags to clear

Returns:

  • (self)


234
235
236
237
# File 'lib/philiprehberger/bit_field.rb', line 234

def clear_flags(*flag_names)
  flag_names.each { |f| clear(f) }
  self
end

#clear_group(group_name) ⇒ self

Clear all flags in a group

Parameters:

  • group_name (Symbol)

    the group name

Returns:

  • (self)


252
253
254
255
# File 'lib/philiprehberger/bit_field.rb', line 252

def clear_group(group_name)
  group_flags(group_name).each { |f| clear(f) }
  self
end

#count_clearInteger

Return the number of flags currently clear

Returns:

  • (Integer)


171
172
173
# File 'lib/philiprehberger/bit_field.rb', line 171

def count_clear
  self.class.flags.size - count_set
end

#count_setInteger

Return the number of flags currently set

Returns:

  • (Integer)


164
165
166
# File 'lib/philiprehberger/bit_field.rb', line 164

def count_set
  self.class.flags.count { |f| flag_set?(f) }
end

#flag_clear?(flag) ⇒ Boolean

Check if a flag is clear (not set)

Parameters:

  • flag (Symbol)

    the flag name

Returns:

  • (Boolean)


157
158
159
# File 'lib/philiprehberger/bit_field.rb', line 157

def flag_clear?(flag)
  !flag_set?(flag)
end

#flag_set?(flag) ⇒ Boolean

Check if a flag is set

Parameters:

  • flag (Symbol)

    the flag name

Returns:

  • (Boolean)


148
149
150
151
# File 'lib/philiprehberger/bit_field.rb', line 148

def flag_set?(flag)
  pos = position_for(flag)
  @value.anybits?(1 << pos)
end

#flagsArray<Symbol>

Return all defined flag names

Returns:

  • (Array<Symbol>)


314
315
316
# File 'lib/philiprehberger/bit_field.rb', line 314

def flags
  self.class.flags
end

#group_any_set?(group_name) ⇒ Boolean

Check if any flag in a group is set

Parameters:

  • group_name (Symbol)

    the group name

Returns:

  • (Boolean)


269
270
271
# File 'lib/philiprehberger/bit_field.rb', line 269

def group_any_set?(group_name)
  group_flags(group_name).any? { |f| flag_set?(f) }
end

#group_none_set?(group_name) ⇒ Boolean

Check if no flags in a group are set

Parameters:

  • group_name (Symbol)

    the group name

Returns:

  • (Boolean)


277
278
279
# File 'lib/philiprehberger/bit_field.rb', line 277

def group_none_set?(group_name)
  group_flags(group_name).none? { |f| flag_set?(f) }
end

#group_set?(group_name) ⇒ Boolean

Check if all flags in a group are set

Parameters:

  • group_name (Symbol)

    the group name

Returns:

  • (Boolean)


261
262
263
# File 'lib/philiprehberger/bit_field.rb', line 261

def group_set?(group_name)
  group_flags(group_name).all? { |f| flag_set?(f) }
end

#hashInteger

Hash code for use in Hash keys

Returns:

  • (Integer)


383
384
385
# File 'lib/philiprehberger/bit_field.rb', line 383

def hash
  [self.class, @value].hash
end

#removed_flags(other) ⇒ Array<Symbol>

Return flags set in other but not in self

Parameters:

  • other (Base)

    another bit field of the same type

Returns:

  • (Array<Symbol>)

Raises:



401
402
403
404
405
# File 'lib/philiprehberger/bit_field.rb', line 401

def removed_flags(other)
  raise Error, 'cannot compare different bit field types' unless other.is_a?(self.class)

  self.class.flags.select { |f| !flag_set?(f) && other.flag_set?(f) }
end

#set(flag) ⇒ self

Set a flag

Parameters:

  • flag (Symbol)

    the flag name

Returns:

  • (self)


179
180
181
182
183
# File 'lib/philiprehberger/bit_field.rb', line 179

def set(flag)
  pos = position_for(flag)
  @value |= (1 << pos)
  self
end

#set_allself

Set all defined flags

Returns:

  • (self)


208
209
210
211
# File 'lib/philiprehberger/bit_field.rb', line 208

def set_all
  self.class.flags.each { |f| set(f) }
  self
end

#set_flags(*flag_names) ⇒ self

Set multiple specific flags at once

Parameters:

  • flag_names (Array<Symbol>)

    flags to set

Returns:

  • (self)


225
226
227
228
# File 'lib/philiprehberger/bit_field.rb', line 225

def set_flags(*flag_names)
  flag_names.each { |f| set(f) }
  self
end

#set_group(group_name) ⇒ self

Set all flags in a group

Parameters:

  • group_name (Symbol)

    the group name

Returns:

  • (self)


243
244
245
246
# File 'lib/philiprehberger/bit_field.rb', line 243

def set_group(group_name)
  group_flags(group_name).each { |f| set(f) }
  self
end

#to_aArray<Symbol>

Return an array of set flag names

Returns:

  • (Array<Symbol>)


307
308
309
# File 'lib/philiprehberger/bit_field.rb', line 307

def to_a
  self.class.flags.select { |f| flag_set?(f) }
end

#to_binary_string(width: nil) ⇒ String

Return the field as a binary string (MSB-first)

By default the string is padded with leading zeros to the declared flag count. If width is given and larger than the natural length, the string is padded to that width. If width is smaller than the natural length, the full representation is returned without truncation to preserve correctness.

Parameters:

  • width (Integer, nil) (defaults to: nil)

    optional explicit width

Returns:

  • (String)


298
299
300
301
302
# File 'lib/philiprehberger/bit_field.rb', line 298

def to_binary_string(width: nil)
  bits = self.class.flags.size
  effective_width = width || bits
  to_i.to_s(2).rjust(effective_width, '0')
end

#to_hHash{Symbol => Object}

Return a hash representation

Returns:

  • (Hash{Symbol => Object})


321
322
323
# File 'lib/philiprehberger/bit_field.rb', line 321

def to_h
  { flags: to_a, value: @value }
end

#to_iInteger

Return the integer representation

Returns:

  • (Integer)


284
285
286
# File 'lib/philiprehberger/bit_field.rb', line 284

def to_i
  @value
end

#to_json(*_args) ⇒ String

Return a JSON string representation

Returns:

  • (String)


328
329
330
# File 'lib/philiprehberger/bit_field.rb', line 328

def to_json(*_args)
  { flags: to_a.map(&:to_s), value: @value }.to_json
end

#toggle(flag) ⇒ self

Toggle a flag

Parameters:

  • flag (Symbol)

    the flag name

Returns:

  • (self)


199
200
201
202
203
# File 'lib/philiprehberger/bit_field.rb', line 199

def toggle(flag)
  pos = position_for(flag)
  @value ^= (1 << pos)
  self
end

#|(other) ⇒ Base

Bitwise OR

Parameters:

  • other (Base)

    another bit field of the same type

Returns:

Raises:



336
337
338
339
340
# File 'lib/philiprehberger/bit_field.rb', line 336

def |(other)
  raise Error, 'cannot combine different bit field types' unless other.is_a?(self.class)

  self.class.from_i(@value | other.to_i)
end