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:



353
354
355
356
357
# File 'lib/philiprehberger/bit_field.rb', line 353

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)


373
374
375
376
377
# File 'lib/philiprehberger/bit_field.rb', line 373

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)


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

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:



363
364
365
366
367
# File 'lib/philiprehberger/bit_field.rb', line 363

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:



398
399
400
401
402
# File 'lib/philiprehberger/bit_field.rb', line 398

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)


196
197
198
199
200
# File 'lib/philiprehberger/bit_field.rb', line 196

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

#clear_allself

Clear all defined flags

Returns:

  • (self)


223
224
225
226
# File 'lib/philiprehberger/bit_field.rb', line 223

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)


241
242
243
244
# File 'lib/philiprehberger/bit_field.rb', line 241

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)


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

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

#empty?Boolean

Return true when no flags are set

Returns:

  • (Boolean)


178
179
180
# File 'lib/philiprehberger/bit_field.rb', line 178

def empty?
  @value.zero?
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>)


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

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)


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

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)


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

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)


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

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)


390
391
392
# File 'lib/philiprehberger/bit_field.rb', line 390

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:



408
409
410
411
412
# File 'lib/philiprehberger/bit_field.rb', line 408

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)


186
187
188
189
190
# File 'lib/philiprehberger/bit_field.rb', line 186

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

#set_allself

Set all defined flags

Returns:

  • (self)


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

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)


232
233
234
235
# File 'lib/philiprehberger/bit_field.rb', line 232

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)


250
251
252
253
# File 'lib/philiprehberger/bit_field.rb', line 250

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

#subset_of?(other) ⇒ Boolean

Check whether every flag set in self is also set in ‘other`.

Parameters:

  • other (Base)

    another instance of the same BitField subclass

Returns:

  • (Boolean)

Raises:

  • (ArgumentError)

    when ‘other` is not the same BitField subclass



419
420
421
422
423
# File 'lib/philiprehberger/bit_field.rb', line 419

def subset_of?(other)
  raise ArgumentError, "expected #{self.class}, got #{other.class}" unless other.is_a?(self.class)

  other.to_i.allbits?(to_i)
end

#to_aArray<Symbol>

Return an array of set flag names

Returns:

  • (Array<Symbol>)


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

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)


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

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})


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

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

#to_iInteger

Return the integer representation

Returns:

  • (Integer)


291
292
293
# File 'lib/philiprehberger/bit_field.rb', line 291

def to_i
  @value
end

#to_json(*_args) ⇒ String

Return a JSON string representation

Returns:

  • (String)


335
336
337
# File 'lib/philiprehberger/bit_field.rb', line 335

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)


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

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:



343
344
345
346
347
# File 'lib/philiprehberger/bit_field.rb', line 343

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