hexstruct2/hexstruct2.rb
#
# Copyright (C) 2005 Minero Aoki
#
# This program is free software.
# You can distribute/modify this program under the terms of the Ruby License.
#
require 'stringio'
class HexStruct2
class << self
def define(&block)
new_class(&block)
end
def fixed_size_field(name, byte_size)
define_field name, FixedSizeField.new(nil, byte_size)
end
def variable_size_field(name)
define_field name, VariableSizeField.new(nil)
end
def struct_field(name, &block)
c = new_class(&block)
define_field name, StructField.new(nil, c)
c
end
def list_field(name, &block)
c = new_class(&block)
define_field name, ListField.new([], c)
c
end
private
def new_class(&block)
c = Class.new(::HexStruct2)
c.instance_variable_set :@field_specs, []
c.module_eval(&block)
c
end
def define_field(name, prototype)
@field_specs.push [name.to_s.intern, prototype]
define_field_accessor name
end
def define_field_accessor(name)
define_method(name) {
self[name].value
}
define_method("#{name}=") {|obj|
self[name].value = obj
}
end
end
class << self
def byte_size
@field_specs.map {|name, prototype| prototype }\
.inject(0) {|sum, prototype| sum + prototype.byte_size }
end
alias size byte_size
end
class << self
def parse(str)
f = StringIO.new(str)
result = for_io(f)
raise ArgumentError, 'string too long' unless f.eof?
result
end
def new(values = nil)
return for_io(DevZero.new) unless values
super
end
def for_io(f)
new(@field_specs.map {|name, prototype| prototype.read_value(f) })
end
end
class DevZero
def read(len = nil)
len ? ('0' * len) : ''
end
def eof?
true
end
end
def initialize(values)
specs = self.class.instance_variable_get(:@field_specs)
unless values.size == specs.size
raise ArgumentError,"wrong # of values (#{values.size} for #{specs.size})"
end
@alist = specs.zip(values).map{|(name, proto), val| [name, proto.new(val)] }
end
def inspect
"\#<#{self.class} #{@alist.map {|name, field| "#{name}=#{field.value.inspect}" }.join(', ')}>"
end
def byte_size
fields().inject(0) {|sum, field| sum + field.byte_size }
end
alias size byte_size
def [](name)
name, field = @alist.assoc(name)
raise KeyError, "wrong member name: #{name}" unless field
field
end
def fields
@alist.map {|name, field| field }
end
def ==(other)
self.class == other.class and self.to_s == other.to_s
end
def string
fields().map {|field| field.string }.join('')
end
alias to_s string
def to_a
fields().map {|field| field.to_a_item }
end
class FixedSizeField
def initialize(int, byte_size)
@value = nil
@byte_size = byte_size
self.value = int if int
end
def read_value(f)
s = f.read(@byte_size * 2)
raise ArgumentError, 'field too short' unless s
raise ArgumentError, 'field too short' unless s.size == @byte_size * 2
s.hex
end
def new(val)
self.class.new(val, @byte_size)
end
attr_reader :value
def value=(int)
if (int >> (@byte_size * 8)) > 0
raise ArgumentError, "integer too big: #{int}"
end
@value = int
end
attr_reader :byte_size
alias size byte_size
def string
sprintf("%0#{@byte_size * 2}X", @value)
end
alias to_a_item value
end
class VariableSizeField
def initialize(val, byte_size = nil)
case
when val.kind_of?(Integer), val.nil?
@value = val
@byte_size = (byte_size || 0)
when val.kind_of?(String)
@value = val.hex
@byte_size = val.size / 2
else
raise TypeError, "unexpected type: #{val.class}"
end
end
def read_value(f)
f.read
end
def new(val)
self.class.new(val)
end
attr_accessor :value
def byte_size
[@byte_size, string().size / 2].max
end
alias size byte_size
attr_writer :byte_size
alias size= byte_size=
def string
return '' unless @value
return '' if @value == 0
s = sprintf("%0#{@byte_size * 2}X", @value)
(s.size % 2 == 0) ? s : '0' + s
end
alias to_a_item value
end
class StructField
def initialize(struct, klass = nil)
@value = struct
@class = klass
end
def read_value(f)
@class.for_io(f)
end
def new(val)
self.class.new(val)
end
attr_accessor :value
def byte_size
(@value || @class).byte_size
end
alias size byte_size
def string
@value.string
end
def to_a_item
@value.to_a
end
end
class ListField
def initialize(list, klass = nil)
@value = list
@class = klass
end
def read_value(f)
list = []
until f.eof?
list.push @class.for_io(f)
end
list
end
def new(val)
self.class.new(val)
end
attr_accessor :value
def byte_size
@value.inject(0) {|sum, struct| sum + struct.byte_size }
end
alias size byte_size
def string
@value.map {|struct| struct.string }.join('')
end
alias to_a_item value
end
end