I have some code written originally for Ruby 1.8 that works in Ruby 1.9, but fails in Rubinus 2.3.0 with the following exception:
undefined conversion for '#<Example::Header foo_component="\xfeXYZ", command=0, beer_buffer="\u0000\u0000\u0000\u0000", flags=24, flags2=0, pid_high=0, signature="\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000", vid=0, xid=0, zid=0, yid=0>' from UTF-8 to ASCII-8BIT (Encoding::CompatibilityError)
I don't know if it's an issue with the Rubinus or bit-struct, but Ruby 2.1 doesn't have this problem (FOO_CONTAINER constant may require enforce_encoding
to make it work, but others remain). This is definitely a problem of using String as container for binary data.
# This is an standalone example causing encoding issue in Rubinus
require 'rubygems'
require 'bit-struct'
require 'stringio'
class Enum
include Comparable
class << self
# Return true if the classes are close enough
def checkclass(k1, k2)
if k1.kind_of?(Integer) && k2.kind_of?(Integer)
return true
end
return k1.class == k2.class
end
def initialize_enum(hash)
self.to_s.match(/(.*)::[^:]*/)
module0 = $1.nil?() ? "Kernel" : $1
@typical = hash.first[1]
@value_to_name = Hash.new()
hash.each { |name, value|
if !checkclass(value, @typical)
raise "Error mixed type Enum. Enum previously initialized with #{@typical}:#{@typical.class} initialized with #{name} => #{value}:#{value.class}"
end
hashed_value = value.hash
if @value_to_name[hashed_value].nil?()
@value_to_name[hashed_value] = Array.new
end
@value_to_name[hashed_value].push(name)
eval(module0).const_set(name.intern(), self.new(value))
}
end
def value_to_name(value)
names = @value_to_name[value.hash]
return "<unknown>" if names.nil? || names.empty?
result = ""
names.each() { |name|
if !result.empty?()
result += " or "
end
result += name
}
return result
end
def value_is_valid(value)
names = @value_to_name[value.hash]
return !names.nil? && !names.empty?
end
def create_value(value)
if @typical.kind_of?(Integer)
value
else
@typical.class.new(value)
end
end
end
def initialize(value)
@value = self.class.create_value(value.to_i)
end
attr :value
def <=>(rhs)
if rhs.kind_of?(self.class)
return @value <=> rhs.value
else
return @value <=> rhs
end
end
def to_i()
return @value.to_i
end
def to_int()
return @value.to_int
end
def to_s()
os = StringIO.new()
os << self.class.value_to_name(@value)
if @value.kind_of?(Numeric)
os << " (0x" << @value.to_s(16) << ")"
else
os << " " << @value.to_s
end
return os.string()
end
def <<(rhs)
return @value << rhs
end
def &(rhs)
return self.class.new(@value & rhs)
end
def |(rhs)
return self.class.new(@value | rhs)
end
def ~
return self.class.new(~@value)
end
end
class TestStatus < Enum
initialize_enum({
# Success
"STATUS_SUCCESS" => 0x00000000
})
end
class ExampleStatus < Enum
initialize_enum({
"EXAMPLE_STATUS" => 0x0100b70f
})
end
class FlagsField
include Comparable
class << self
def initialize_flags(hash)
self.to_s.match(/(.*)::[^:]*/)
module0 = $1.nil?() ? "Kernel" : $1
@value_to_name = Hash.new()
hash.each { |name, value|
if @value_to_name[value].nil?
@value_to_name[value] = Array.new
end
@value_to_name[value].push(name)
eval(module0).const_set(name.intern(), self.new(value))
}
end
def value_to_name(value)
names = @value_to_name[value]
if names.nil?() || names.empty?()
return [nil, "<unknown> (0x#{value.to_s(16)})"]
end
result = ""
names.each() { |name|
if !result.empty?()
result += " or "
end
result += name
}
return [value, result]
end
end
def initialize(value)
@value = value.to_i
end
attr :value
def <=>(rhs)
return @value <=> get_value_from(rhs)
end
def &(rhs)
return self.class.new(@value & get_value_from(rhs))
end
def ^(rhs)
return self.class.new(@value ^ get_value_from(rhs))
end
def |(rhs)
return self.class.new(@value | get_value_from(rhs))
end
def ~
return self.class.new(~@value)
end
def <<(rhs)
return self.class.new(@value << rhs)
end
def to_i()
return @value
end
def to_int()
return @value
end
def to_s()
os = StringIO.new()
os << "0x" << @value.to_s(16)
prefix = " ("
if @value != 0
it = 1
while @value >= it
if (@value & it) != 0
os << prefix << self.class.value_to_name(it)[1]
prefix = ", "
end
it <<= 1
end
os << ")"
else
result, name = self.class.value_to_name(0)
os << prefix << name << ")" if result
end
return os.string()
end
def inspect
return to_s
end
def hash
@value.hash
end
private
def get_value_from(rhs)
return rhs.kind_of?(self.class) ? rhs.value : rhs
end
end
class Flags < FlagsField
initialize_flags(
"FLAGS_EXAMPLE1" => 0x80,
"FLAGS_EXAMPLE2" => 0x40,
"FLAGS_EXAMPLE4" => 0x10,
"FLAGS_EXAMPLE5" => 0x08,
)
end
class Flags2 < FlagsField
initialize_flags(
"FLAGS_EXAMPLE11" => 0x8000,
"FLAGS_EXAMPLE12" => 0x4000,
"FLAGS_EXAMPLE15" => 0x0800,
"FLAGS_EXAMPLE16" => 0x0040,
"FLAGS_EXAMPLE19" => 0x0001
)
end
FOO_COMPONENT = "\xfeXYZ"
module Example
class Header < BitStruct
char :foo_component, 32, "Foo Component"
unsigned :command, 8, "Command", :endian => :little
char :beer_buffer, 32, "Status"
unsigned :flags, 8, "Flags", :endian => :little
unsigned :flags2, 16, "Flags2", :endian => :little
unsigned :pid_high, 16, "PID High", :endian => :little
char :signature, 64, "Signature"
pad :reserved, 16
unsigned :vid, 16, "VID", :endian => :little
unsigned :xid, 16, "XID", :endian => :little
unsigned :zid, 16, "ZID", :endian => :little
unsigned :yid, 16, "YID", :endian => :little
def dos_status # SRCDOC_PROTEST_GETTER_SETTER
if (self.flags2 & FLAGS_EXAMPLE12) != 0
raise "Error: ..."
end
return self.beer_buffer.unpack("N")[0]
end
def dos_status=(status)
self.beer_buffer = [status].pack("N")
self.flags2 |= ~FLAGS_EXAMPLE12
end
def nt_status
if (self.flags2 & FLAGS_EXAMPLE12) == 0
raise "Error: ..."
end
return TestStatus.new(self.beer_buffer.unpack("V")[0])
end
def nt_status=(status)
self.beer_buffer = [status].pack("V")
self.flags2 &= FLAGS_EXAMPLE12
end
def status
if (self.flags2 & FLAGS_EXAMPLE12) != 0
return self.nt_status
else
return ExampleStatus.new(self.dos_status)
end
end
def status=(status)
if status.class == ExampleStatus
self.dos_status = status
else
self.nt_status = status
end
end
def unicode_enabled
(flags2 & FLAGS_EXAMPLE11) == FLAGS_EXAMPLE11
end
def disable_unicode
self.flags2 &= ~FLAGS_EXAMPLE11
end
def method_missing(id, *args, &block)
os = StringIO.new
os << "undefined field '" << id.id2name << "' for " << self.class << $endl
os << "Available fields are: {" << $endl
os << " Example::Header:" << $endl
(Header.new.public_methods - BitStruct.new.public_methods).each { |method|
if method.include?("=")
os << " " << method[0...-1] << " => ..." << $endl
end
}
os << $endl
os << " " << self.class << $endl
(self.public_methods - Header.new.public_methods - BitStruct.new.public_methods).each { |method|
if method.include?("=")
os << " " << method[0...-1] << " => ..." << $endl
end
}
os << "}" << $endl
raise os.string
end
initial_value.foo_component = FOO_COMPONENT
initial_value.command = 0
initial_value.beer_buffer = "\0"
initial_value.status = STATUS_SUCCESS
initial_value.flags = FLAGS_EXAMPLE4 | FLAGS_EXAMPLE5
initial_value.flags2 = FLAGS_EXAMPLE11 | FLAGS_EXAMPLE12 | FLAGS_EXAMPLE15 | FLAGS_EXAMPLE16 | FLAGS_EXAMPLE19
initial_value.pid_high = 0
initial_value.signature = "\0\0\0\0\0\0\0\0"
initial_value.vid = 0
initial_value.xid = 0xbadbeef
initial_value.zid = 0xffff # Will be filled in automatically
initial_value.yid = 0xffff # Will be filled in automatically
end
end # module Example