class PackedStruct::Directive
Constants
- BYTES_IN_STRING
The number of bytes a type takes up in the string.
Attributes
The modifiers for this directive.
@return [Set<Modifier>]
The name of the directive. This is passed as the first value of the directive; from {Package}, it is the name of the method call.
@return [Symbol]
Public Class Methods
Initialize the directive.
@param name [Symbol] the name of the directive. @param package [Package] the package this directive is a part
of.
# File lib/packed_struct/directive.rb, line 28 def initialize(name) @name = name @modifiers = [] @finalized = true @tags = { :endian => :native, :signedness => :signed, :size => nil, :precision => :single, :size_mod => 0 } end
Public Instance Methods
(see -
)
# File lib/packed_struct/directive.rb, line 111 def +(other) if other.is_a? Numeric tags[:size_mod] = +other self else self_equiv, arg_equiv = other.coerce(self) self_equiv + arg_equiv end end
Modifies self
such that it sizes itself (on {#to_s}ing), and keeping this size modification in mind. In other words, this is meant to be used in another directive’s {#[]} method for telling it what size it should be, and this method modifies that size by the given amount.
@example
some_directive[another_directive - 5]
@param other [Numeric, coerce
] @return [self, Object]
# File lib/packed_struct/directive.rb, line 100 def -(other) if other.is_a? Numeric tags[:size_mod] = -other self else self_equiv, arg_equiv = other.coerce(self) self_equiv - arg_equiv end end
Changes the size of the directive to the given size. It is possible for the given value to the a directive; if it is, it just uses the name of the directive.
@param new_size [Numeric, Directive] @return [self]
# File lib/packed_struct/directive.rb, line 69 def [](new_size) if new_size.is_a? Directive tags.merge! new_size.tags_for_sized_directive else tags[:size] = new_size end self end
Add a modifier to this directive.
@param mod [Modifier] @return [self]
# File lib/packed_struct/directive.rb, line 57 def add_modifier(mod) @finalized = false modifiers << mod self end
The number of bytes this takes up in the resulting packed string.
@param (see to_s
) @return [Numeric]
# File lib/packed_struct/directive.rb, line 223 def bytesize(data = {}) case tags[:type] when nil (size(data) || 8) / 8 when :short, :int, :long BYTES_IN_STRING.fetch tags[:type] when :float if tags[:precision] == :double BYTES_IN_STRING[:float_double] else BYTES_IN_STRING[:float_single] end when :null size(data) || 1 when :string size(data) else 0 end end
Coerces self
into a format that can be used with Numeric
to add or subtract from this class.
@example
some_directive[1 + another_directive]
@param other [Object] @return [Array<(self, Object)>]
# File lib/packed_struct/directive.rb, line 128 def coerce(other) [self, other] end
Determines whether or not this directive is empty. It is considered empty when its tags has all of the default values, it has no modifiers, and its name is not :null
.
@return [Boolean]
# File lib/packed_struct/directive.rb, line 47 def empty? tags == { :endian => :native, :signedness => :signed, :size => nil, :precision => :single, :size_mod => 0 } && modifiers.length == 0 && name != :null end
Finalizes the directive.
@return [void]
# File lib/packed_struct/directive.rb, line 144 def finalize! return if finalized? modifiers.each do |modifier| modifier.type.each_with_index do |type, i| case type when :endian, :signedness, :precision, :type, :string_type tags[type] = modifier.value[i] when :size tags[:size] = modifier.value[i] unless tags[:size] else raise UnknownModifierError, "Unknown modifier: #{type}" end end end @finalized = true cache_string end
Whether or not this directive has finalized. It is finalized until a modifier is added, and then {#finalize!} is required to finalize the directive.
@return [Boolean]
# File lib/packed_struct/directive.rb, line 137 def finalized? @finalized end
The size of this directive.
@param (see to_s
) @return [nil, Numeric]
# File lib/packed_struct/directive.rb, line 248 def size(data = {}) if tags[:size].is_a? Symbol data.fetch(tags[:size]) else tags[:size] end end
Turn the directive into a string, with the given data. It shouldn’t need the data unless tags[:size]
is a Symbol.
@param data [Hash<Symbol, Object>] the data that may be used for
the length.
@return [String]
# File lib/packed_struct/directive.rb, line 179 def to_s(data = {}) return @_cache unless undefined_size? || !@_cache return "x" * (tags[:size] || 1) if name == :null out = case tags[:type] when :short modify_if_needed "S" when :int modify_if_needed "I" when :long modify_if_needed "L" when :string handle_string_type when :float handle_float_type when nil handle_empty_type else nil end if tags[:size].is_a? Symbol out << data.fetch(tags[:size]).to_s elsif tags[:size] && ![:null, nil].include?(tags[:type]) out << tags[:size].to_s end out end
Returns whether or not this directive depends on another directive for its value.
@return [Boolean]
# File lib/packed_struct/directive.rb, line 169 def undefined_size? tags[:size].is_a?(Symbol) end
Private Instance Methods
Tries to cache the string value of this directive. It cannot if tags[:size]
is a Symbol, since it depends on the value of the directive named by that symbol.
@return [String]
# File lib/packed_struct/directive.rb, line 263 def cache_string return if tags[:size].is_a? Symbol return @_cache = "x" * (tags[:size] || 1) if name == :null @_cache = to_s end
Handles the type if there is no type, i.e. a type modifier was not specified. Can only handle directives with sizes 0 (default), 8, 16, 32, and 64.
@see modify_if_needed
@return [String]
# File lib/packed_struct/directive.rb, line 276 def handle_empty_type maps = { 0 => "x", 8 => "C", 16 => "S", 32 => "L", 64 => "Q" } modify_if_needed maps.fetch(tags[:size] || 0), tags[:size] != 8 end
Handles the float type. Handles the endianness and the precision, returning the correct character for the float type.
@return [String]
# File lib/packed_struct/directive.rb, line 314 def handle_float_type case [tags[:endian], tags[:precision]] when [:native, :double] "D" when [:native, :single] "F" when [:little, :double] "E" when [:little, :single] "e" when [:big, :double] "G" when [:big, :single] "g" end end
Handles the type if it is string. Defaults to a null-padded string, but if a :hex
, :base64
, or :bit
modifier is specified, it will be used.
If :hex
is specified, the endianness will be used to determine which nibble will go first.
If :base64
is specified, the endianness will be used to determine whether MSB
or the LSB
will go first.
# File lib/packed_struct/directive.rb, line 297 def handle_string_type case tags[:string_type] when :hex modify_for_endianness "H", true when :base64 "m" when :bit modify_for_endianness "B", true else modify_for_endianness "a", true end end
Modifies the given string to account for endianness. If use_case
is true, it modifies the case of the given string to represent endianness; otherwise, it appends data to the string to represent endianness.
@param str [String] the string to modify. @param use_case [Boolean] @return [String]
# File lib/packed_struct/directive.rb, line 360 def modify_for_endianness(str, use_case = false) case [tags[:endian], use_case] when [:little, true] str.swapcase when [:little, false] str + "<" when [:big, true] str when [:big, false] str + ">" else str end end
Modifies the given string if it’s needed, according to signness and endianness. This assumes that a signed directive should be in lowercase.
@param str [String] the string to modify. @param include_endian [Boolean] whether or not to include the
endianness.
@return [String]
# File lib/packed_struct/directive.rb, line 339 def modify_if_needed(str, include_endian = true) base = if @tags[:signedness] == :signed str.swapcase else str end if include_endian modify_for_endianness(base) else base end end