Class: CTypes::Enum

Inherits:
Object
  • Object
show all
Extended by:
Forwardable
Includes:
Type
Defined in:
lib/ctypes/enum.rb

Overview

Pack & unpack C enum values

Examples:

32-bit contiguous enum

state = Enum.new(%i[start stop block])
state.pack(:block)                # => "\2\0\0\0"
state.unpack("\0\0\0\0")          # => :start

8-bit contiguous enum

state = Enum.new(UInt8, %i[start stop block])
state.pack(:block)                # => "\2"
state.unpack("\1")                # => :stop

sparse enum

state = Enum.new do |e|
  e << %i{a b c}
  e << {d: 32}
end
state.pack(:d)                    # => "\x20\x00\x00\x00"
state.unpack("\x02\x00\x00\x00")  # => :c

Defined Under Namespace

Classes: Builder

Instance Attribute Summary collapse

Attributes included from Type

#dry_type, #endian

Instance Method Summary collapse

Methods included from Type

#default_endian, #fixed_size?, #greedy?, #pread, #read, #unpack, #unpack_all, #with_endian, #without_endian

Constructor Details

#initialize(type = Helpers.uint32, values = nil, permissive: false, &block) ⇒ Enum

Returns a new instance of Enum.

Examples:

contiguous 32-bit enum

Enum.new([:a, :b, :c])
Enum.new(%i[a b c])

contiguous 8-bit enum

Enum.new(:uint8, %i[a b c])

sparse enum

Enum.new({a: 46, b: 789})

16-bit sparse enum

Enum.new(UInt16, {a: 46, b: 789})

8-bit sparse enum

Enum.new(Uint8) do |e|
  e << %i{zero one two}         # define 0, 1, 2
  e << {eighty: 80}             # skip 3-79 (incl), define 80
  e << :eighty_one
  e << {a: 100, b: 200}         # define multiple sparse values
end

dynamically generated enum

Enum.new do |e|
  # declare state_0 through state_31
  32.times do |i|
    e << "state_#{i}"
  end
end

See Also:



64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
# File 'lib/ctypes/enum.rb', line 64

def initialize(type = Helpers.uint32, values = nil, permissive: false,
  &block)
  builder = if block
    Builder.new(&block)
  else
    if values.nil?
      values = type
      type = Helpers.uint32
    end

    Builder.new { |b| b << values }
  end

  @dry_type = Dry::Types["symbol"]
    .default(builder.default)
    .enum(builder.map)
  @type = type
  @size = @type.size
  @dry_type = @dry_type.lax if permissive
end

Instance Attribute Details

#sizeObject (readonly)

Returns the value of attribute size.



84
85
86
# File 'lib/ctypes/enum.rb', line 84

def size
  @size
end

#typeObject (readonly)

Returns the value of attribute type.



84
85
86
# File 'lib/ctypes/enum.rb', line 84

def type
  @type
end

Instance Method Details

#==(other) ⇒ Object



171
172
173
174
# File 'lib/ctypes/enum.rb', line 171

def ==(other)
  return false unless other.is_a?(Enum)
  other.type == @type && other.mapping == @dry_type.mapping
end

#[](arg) ⇒ Symbol, ...

Convert a enum key to value, or value to key

Examples:

e = Enum.new(%i[a b c])
e[1]    # => :b
e[5]    # => nil
e[:b]   # => 1
e[:x]   # => nil

Parameters:

  • arg (Integer, Symbol)

    key or value

Returns:

  • (Symbol, Integer, nil)

    value or key if known, nil if unknown



198
199
200
201
202
203
204
205
206
207
208
# File 'lib/ctypes/enum.rb', line 198

def [](arg)
  case arg
  when Integer
    @inverted_mapping ||= @dry_type.mapping.invert
    @inverted_mapping[arg]
  when Symbol
    @dry_type.mapping[arg]
  else
    raise ArgumentError, "arg must be Integer or Symbol: %p" % [arg]
  end
end

#default_valueObject



176
177
178
179
180
181
182
# File 'lib/ctypes/enum.rb', line 176

def default_value
  # with `.lax` added to dry_type for permissive enum, the standard
  # `dry_type[]` doesn't work for a default.  Instead, we're going to try
  # whatever key is set for 0, and failing that, just the first value
  # defined for the enum
  self[0] || @dry_type.mapping.first.first
end

#export_type(q) ⇒ Object



152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
# File 'lib/ctypes/enum.rb', line 152

def export_type(q)
  q << "enum("
  if @type != UInt32
    q << @type
    q << ", "
  end
  q << "{"
  q.break

  q.nest(2) do
    @dry_type.mapping.each do |name, value|
      q << "#{name}: #{value},"
      q.break
    end
  end
  q << "})"
  q << ".permissive" if @dry_type.is_a?(Dry::Types::Lax)
end

#mappingObject

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.



133
134
135
# File 'lib/ctypes/enum.rb', line 133

def mapping
  @dry_type.mapping
end

#pack(value, endian: default_endian, validate: true) ⇒ ::String

encode a ruby type into a String containing the binary representation of the enum

Examples:

e = Enum.new(%i[stopped running blocked])
e.pack(:running)                  # => "\1\0\0\0"
e.pack(2)                         # => "\2\0\0\0"

Parameters:

  • value (Symbol, Integer)

    value to be encoded

  • endian (Symbol) (defaults to: default_endian)

    endian to pack with

  • validate (Boolean) (defaults to: true)

    set to false to disable value validation

Returns:

  • (::String)

    binary encoding for value



99
100
101
102
103
104
105
106
107
108
109
110
111
112
# File 'lib/ctypes/enum.rb', line 99

def pack(value, endian: default_endian, validate: true)
  value = @dry_type[value] if validate
  out = @dry_type.mapping[value]
  out ||= case value
  when /\Aunknown_(\h+)\z/
    out = $1.to_i(16)
  when Integer
    value
  else
    raise Error, "unknown enum value: %p" % value
  end

  @type.pack(out, endian: @type.endian || endian, validate:)
end

#permissiveObject



184
185
186
# File 'lib/ctypes/enum.rb', line 184

def permissive
  Enum.new(@type, @dry_type.mapping, permissive: true)
end

#pretty_print(q) ⇒ Object

:nodoc:



137
138
139
140
141
142
143
144
145
146
147
148
149
# File 'lib/ctypes/enum.rb', line 137

def pretty_print(q) # :nodoc:
  q.group(2, "enum(", ")") do
    if @type != Helpers.uint32
      q.pp(@type)
      q.comma_breakable
    end
    q.group(0, "{", "}") do
      q.seplist(@dry_type.mapping) do |name, value|
        q.text("#{name}: #{value}")
      end
    end
  end
end

#unpack_one(buf, endian: default_endian) ⇒ Array(Symbol, ::String)

convert a String containing the binary represention of a c enum into the ruby value

Examples:

e = Enum.new(%i[stopped running blocked])
e.unpack("\1\0\0\0")            # => :running

Parameters:

  • buf (String)

    bytes that make up the type

  • endian (Symbol) (defaults to: default_endian)

    endian of data within buf

Returns:

  • (Array(Symbol, ::String))

    decoded type, and remaining bytes

See Also:



125
126
127
128
129
130
# File 'lib/ctypes/enum.rb', line 125

def unpack_one(buf, endian: default_endian)
  value, rest = @type.unpack_one(buf, endian: @type.endian || endian)
  out = @dry_type[value]
  out = ("unknown_%0#{@size * 2}x" % value).to_sym unless out.is_a?(Symbol)
  [out, rest]
end