rstruct 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,55 @@
1
+ module Rstruct
2
+ class Type
3
+ attr_reader :name, :params
4
+
5
+ def initialize(name, params={}, &block)
6
+ @params = params.dup
7
+ @name = name.to_sym
8
+
9
+ reg = @params.delete(:register)
10
+ aliases = @params.delete(:alias)
11
+ regnames = ((aliases)? ([aliases] << @name).flatten : [@name]).uniq.compact
12
+
13
+ reg=nil if reg==true
14
+ register(regnames, reg) unless reg == false
15
+
16
+ instance_eval &block if block
17
+ end
18
+
19
+ def register(names=nil, reg=nil)
20
+ names ||= to_sym
21
+ reg ||= Registry::DEFAULT_REGISTRY
22
+ reg.register(self, *names)
23
+ end
24
+
25
+ def groupable?
26
+ @groupable or false
27
+ end
28
+
29
+ def container?
30
+ @container or false
31
+ end
32
+
33
+ def claim_value(vals, predecessors=nil)
34
+ if @claim_cb
35
+ @claim_cb.call(vals, predecessors)
36
+ else
37
+ vals.shift
38
+ end
39
+ end
40
+
41
+ def sizeof
42
+ self.size
43
+ end
44
+
45
+ private
46
+ # sets up a call back for claiming values out of an unpacked
47
+ # value array
48
+ def claim(&block)
49
+ @claim_cb = block
50
+ end
51
+
52
+ end
53
+
54
+ end
55
+
@@ -0,0 +1,22 @@
1
+ module Rstruct
2
+ class Field
3
+ attr_reader :name, :typ_name, :typ, :args, :block
4
+
5
+ def initialize(name, typ, typ_name, args, block)
6
+ @name=name
7
+ @typ=typ
8
+ @typ_name = typ_name || typ
9
+ @args=args
10
+ @block=block
11
+ end
12
+
13
+ def respond_to?(arg)
14
+ super(arg) || @typ.respond_to?(arg)
15
+ end
16
+
17
+ def method_missing(name, *args, &block)
18
+ @typ.__send__(name, *args, &block)
19
+ end
20
+ end
21
+ end
22
+
@@ -0,0 +1,66 @@
1
+ require 'set'
2
+
3
+ module Rstruct
4
+ class TypeConflictError < StandardError
5
+ end
6
+
7
+ class InvalidTypeError < StandardError
8
+ end
9
+
10
+ class Registry
11
+ attr_reader :name, :inherits
12
+
13
+ def initialize(name, *inherits)
14
+ @name = name.to_sym
15
+ @registry = Hash.new()
16
+
17
+ @inherits = []
18
+ if @name != :default
19
+ @inherits << DEFAULT_REGISTRY
20
+ end
21
+ @inherits.concat(inherits).uniq!
22
+ end
23
+
24
+ def typedef(p,t,opts={})
25
+ if (pt = get(p))
26
+ set(t, pt)
27
+ else
28
+ raise(InvalidTypeError, "unknown type: #{p}")
29
+ end
30
+ end
31
+
32
+ def get(typ)
33
+ if t=@registry[typ]
34
+ return t
35
+ else
36
+ @inherits.each do |inc|
37
+ if t=inc.get(typ)
38
+ return t
39
+ end
40
+ end
41
+ return nil
42
+ end
43
+ end
44
+
45
+ alias [] get
46
+
47
+ def set(n,typ)
48
+ if n.nil?
49
+ raise(TypeConflictError, "can't register nil type")
50
+ elsif v=get(n)
51
+ raise(TypeConflictError, "type already registered: #{n} => #{v.inspect}" )
52
+ end
53
+ @registry[n]=typ
54
+ end
55
+
56
+ alias []= set
57
+
58
+ def register(typ, *names)
59
+ names.each do |n|
60
+ set(n.to_sym,typ)
61
+ end
62
+ end
63
+
64
+ DEFAULT_REGISTRY=new(:default)
65
+ end
66
+ end
@@ -0,0 +1,30 @@
1
+ require 'rstruct/registry'
2
+ require 'rstruct/field'
3
+
4
+ module Rstruct
5
+
6
+ class StructBuilder
7
+ attr_reader :__fields, :__registry
8
+
9
+ def initialize(registry=nil, &block)
10
+ @__fields = []
11
+ @__registry = registry || Registry::DEFAULT_REGISTRY
12
+ instance_eval &block
13
+ end
14
+
15
+ def field(name, typ, typ_name, *args, &block)
16
+ @__fields << Field.new(name,typ,typ_name,args,block)
17
+ end
18
+
19
+ def method_missing(typ_arg, *args, &block)
20
+ name = args.shift
21
+ unless typ = @__registry.get(typ_arg)
22
+ raise(InvalidTypeError, "invalid field type: #{typ_arg}")
23
+ end
24
+
25
+ field(name, typ, typ_arg, *args, &block)
26
+ end
27
+ end
28
+
29
+ end
30
+
@@ -0,0 +1,59 @@
1
+ require 'rstruct/base_types'
2
+ require 'rstruct/struct_builder'
3
+ require 'rstruct/registry'
4
+
5
+ module Rstruct
6
+ class StructError < StandardError
7
+ end
8
+
9
+ class Structure < ContainerType
10
+ attr_reader :fields
11
+
12
+ def initialize(name, opts={}, &block)
13
+ lkupreg = (opts[:fields_from]) # allow a seperate reg for member types
14
+ builder = opts[:builder] || StructBuilder
15
+ reg = opts[:register]
16
+ reg=nil if reg==true
17
+
18
+ # pass a nil block to super to ensure we're claiming the caller's
19
+ super(name, opts, &(nil))
20
+
21
+ @fields = builder.new((lkupreg || reg), &block).__fields
22
+ raise(StructError, "no fields were defined") if @fields.empty?
23
+
24
+ # set up our internal struct container class
25
+ # we are taking the field name 'structure'
26
+ # to reference ourselves
27
+ @mystruct = Struct.new(*self.field_names)
28
+ end
29
+
30
+ def instance(values=nil)
31
+ values ||= {}
32
+ vals = []
33
+ self.fields.each do |f|
34
+ v = values[f.name]
35
+ vals << (f.typ.respond_to?(:instance) ? f.typ.instance(v) : v)
36
+ end
37
+ s = @mystruct.new(*vals).extend(ContainerMixins)
38
+ s.rstruct_type = self
39
+ yield(s) if block_given?
40
+ return s
41
+ end
42
+
43
+ def claim_value(vals, obj=nil)
44
+ if @claim_cb
45
+ @claim_cb.call(vals, obj)
46
+ else
47
+ # create our struct container
48
+ s = instance()
49
+
50
+ # iterate through the fields assigning values in the
51
+ # container and pass it along with values to each
52
+ # field's claim_value method.
53
+ self.fields.do {|f| s[f.name] = f.typ.claim_value(vals,s) }
54
+
55
+ return s
56
+ end
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,44 @@
1
+ require 'rstruct/base_types'
2
+
3
+ module Rstruct
4
+ Char = PackedType.new(:char, 1, "A", :alias => [:char, :char_t])
5
+
6
+ Byte = PackedType.new(:byte, 1, "c", :alias => [:BYTE, :signed_byte])
7
+
8
+ Ubyte = PackedType.new(:ubyte, 1, "C", :alias => [:UBYTE, :unsigned_byte])
9
+
10
+ Int = PackedType.new(:int, [0].pack("i_").size, "i_", :alias => [:int_t, :bool, :BOOL, :signed_int])
11
+
12
+ Uint = PackedType.new(:uint, [0].pack("I_").size, "I_", :alias => [:uint_t, :unsigned_int])
13
+
14
+ Short = PackedType.new(:short, [0].pack("s_").size, "s_", :alias => [:short_t, :signed_short])
15
+
16
+ Ushort = PackedType.new(:ushort, [0].pack("S_").size, "S_", :alias => [:ushort_t, :unsigned_short])
17
+
18
+ Long = PackedType.new(:long, [0].pack("l_").size, "l_", :alias => [:long_t, :signed_long])
19
+
20
+ Ulong = PackedType.new(:ulong, [0].pack("L_").size, "L_", :alias => [:ulong_t, :unsigned_long])
21
+
22
+ Int16 = PackedType.new(:int16, 2, 's', :alias => [:int16_t, :i16, :i16_t, :signed_int16])
23
+
24
+ Uint16 = PackedType.new(:uint16, 2, 'S', :alias => [:uint16_t, :u16, :u16_t, :unsigned_int16])
25
+
26
+ Int32 = PackedType.new(:int32, 4, "l", :alias => [:int32_t, :i32, :i32_t, :signed_int32])
27
+
28
+ Uint32 = PackedType.new(:uint32, 4, "L", :alias => [:uint32_t, :u32, :u32_t, :unsigned_int32])
29
+
30
+ Int64 = PackedType.new(:int64, 8, "q", :alias => [:int64_t, :i64, :i64_t, :signed_int64])
31
+
32
+ Uint64 = PackedType.new(:uint64, 8, "Q", :alias => [:uint64_t, :u64, :u64_t, :unsigned_int64])
33
+
34
+ Uint16le = PackedType.new(:uint16le, 2, "v", :alias => [:uint16_le, :ul16, :le16])
35
+
36
+ Uint32le = PackedType.new(:uint32le, 4, "V", :alias => [:uint32_le, :ul32, :le32])
37
+
38
+ Uint16be = PackedType.new(:uint16be, 2, "n", :alias => [:uint16_be, :ub16, :be16])
39
+
40
+ Uint32be = PackedType.new(:uint32be, 4, "N", :alias => [:uint32_be, :ub32, :be32])
41
+
42
+ Pointer = PackedType.new(:pointer, [0].pack("L_").size , "L_", :alias => :pointer)
43
+
44
+ end
@@ -0,0 +1,78 @@
1
+ #!/usr/bin/env ruby
2
+ # A basic Rstruct example using Apple's FAT file structure.
3
+ # To compare the structs to their C counterparts, see:
4
+ # http://fxr.watson.org/fxr/source/EXTERNAL_HEADERS/mach-o/fat.h?v=xnu-1228
5
+
6
+ $: << File.join(File.dirname(__FILE__), '..', 'lib')
7
+
8
+ require 'rstruct'
9
+ extend Rstruct::ClassMethods
10
+
11
+ FAT_MAGIC = 0xcafebabe
12
+
13
+ fat_header = struct(:fat_header) {
14
+ uint32be :magic; # FAT_MAGIC
15
+ uint32be :nfat_arch; # number of structs that follow
16
+ }
17
+
18
+ typedef :uint32be, :cpu_type_t
19
+ typedef :uint32be, :cpu_subtype_t
20
+
21
+ fat_arch = struct(:fat_arch) {
22
+ cpu_type_t :cputype # cpu specifier (int)
23
+ cpu_subtype_t :cpusubtype # machine specifier (int)
24
+ uint32be :offset # file offset to this object file
25
+ uint32be :size # size of this object file
26
+ uint32be :align # alignment as a power of 2
27
+ }
28
+
29
+ typedef :uint32le, :cpu_type_le
30
+ typedef :uint32le, :cpu_subtype_le
31
+
32
+ mach_header = struct(:mach_header) { # little endian this time
33
+ uint32le :magic # mach magic number identifier
34
+ cpu_type_le :cputype # cpu specifier
35
+ cpu_subtype_le :cpusubtype # machine specifier
36
+ uint32le :filetype # type of file
37
+ uint32le :ncmds # number of load commands
38
+ uint32le :sizeofcmds # the size of all the load commands
39
+ uint32le :flags # flags
40
+ }
41
+
42
+ # a basic helper to produce textual dumps of fields
43
+ def dump(struct)
44
+ struct.each_pair.map {|k,v| " #{k} = 0x%0.8x" % v}
45
+ end
46
+
47
+ ARGV.each do |fname|
48
+ File.open(fname,'r') do |f|
49
+
50
+ # Read and dump the FAT header from the file
51
+ head = fat_header.read(f)
52
+ unless head.magic == FAT_MAGIC
53
+ STDERR.puts "Error: #{fname} does not look like a FAT binary"
54
+ next
55
+ end
56
+ puts "File: #{fname}", "FAT header:", dump(head)
57
+ puts
58
+
59
+ # parse the architectures located after the FAT header
60
+ (head.nfat_arch).times do |i|
61
+ arch = fat_arch.read(f)
62
+ puts " Architecture #{i}:"
63
+ puts " " << dump(arch).join("\n ")
64
+ puts
65
+
66
+ # dump the mach header
67
+ opos = f.pos # save our file position so we get the next arch
68
+ f.pos = arch.offset # jump to where the mach header should be
69
+ mach = mach_header.read(f) # parse a mach_header struct
70
+ f.pos = opos # return to saved architecture position
71
+
72
+ puts " Mach Header:"
73
+ puts " " << dump(mach).join("\n ")
74
+ puts
75
+
76
+ end
77
+ end
78
+ end
@@ -0,0 +1,64 @@
1
+
2
+ shared_examples_for "a basic Rstruct registry" do
3
+ it "should have a name" do
4
+ @registry.name.should == @reg_name
5
+ end
6
+
7
+ it "should have basic types already registered or inherited" do
8
+ @registry.get(:byte).should == Rstruct::Byte
9
+ @registry.get(:char).should == Rstruct::Char
10
+ @registry.get(:int).should == Rstruct::Int
11
+ @registry.get(:short).should == Rstruct::Short
12
+ @registry.get(:long).should == Rstruct::Long
13
+ @registry.get(:uint).should == Rstruct::Uint
14
+ @registry.get(:ushort).should == Rstruct::Ushort
15
+ @registry.get(:ulong).should == Rstruct::Ulong
16
+ end
17
+
18
+ it "should allow types to be retrieved with 'get' or '[]'" do
19
+ (a=@registry[:byte]).should == Rstruct::Byte
20
+ @registry.get(:byte).should == a
21
+ end
22
+
23
+ it "should allow new types to be defined with 'register' or '[]='" do
24
+ typ1 = :"create_#{@registry.name}_1"
25
+ c1 = Class.new(Rstruct::Type)
26
+ @registry[typ1]= c1
27
+ @registry[typ1].should == c1
28
+
29
+ typ2 = :"create_#{@registry.name}_2"
30
+ c2 = Class.new(Rstruct::Type)
31
+ @registry.register(c2, typ2)
32
+ @registry[typ2].should == c2
33
+ end
34
+
35
+ it "should allow multiple names to be registered at once with 'register'" do
36
+ typ1 = :"create_#{@registry.name}_3"
37
+ typ2 = :"create_#{@registry.name}_4"
38
+ c = Class.new(Rstruct::Type)
39
+ @registry.register(c, typ1, typ2)
40
+ @registry[typ1].should == c
41
+ @registry[typ2].should == c
42
+ end
43
+
44
+ it "should raise an exception when a type is defined which already exists" do
45
+ typ = :"create_#{@registry.name}_5"
46
+ c = Class.new(Rstruct::Type)
47
+ @registry[typ] = c
48
+ @registry[typ].should == c
49
+ c2 = Class.new(Rstruct::Type)
50
+ lambda{ @registry[typ] = c2 }.should raise_error(Rstruct::TypeConflictError)
51
+ end
52
+
53
+ it "should allow type aliases to be defined with 'typedef'" do
54
+ typ = :"typedef_#{@registry.name}_1"
55
+ @registry.typedef :int, typ
56
+ @registry[:int].should == @registry[typ]
57
+ end
58
+
59
+ it "should raise an exception when invalid types are aliased in 'typedef'" do
60
+ typ = :"typedef_#{@registry.name}_2"
61
+ lambda{ @registry.typedef :not_a_type, typ}.should raise_error(Rstruct::InvalidTypeError)
62
+ end
63
+
64
+ end
@@ -0,0 +1,61 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
2
+ require 'registry_behaviors'
3
+
4
+ describe Rstruct::Registry do
5
+ before(:all) do
6
+ @dflt_reg = Rstruct::Registry::DEFAULT_REGISTRY
7
+ end
8
+
9
+ it "should have a default registry" do
10
+ @dflt_reg.should be_a(Rstruct::Registry)
11
+ end
12
+
13
+ context "default registry" do
14
+ before(:all) do
15
+ @registry = @dflt_reg
16
+ @reg_name = :default
17
+ end
18
+
19
+ it_should_behave_like "a basic Rstruct registry"
20
+
21
+ end
22
+
23
+ context "creating a new registry" do
24
+ before(:all) do
25
+ @reg_name = :rspec_test_registry
26
+ @preg = Rstruct::Registry.new(:parent_reg)
27
+ @registry = Rstruct::Registry.new(@reg_name)
28
+ end
29
+
30
+ it "should inherit types from the default registry" do
31
+ @registry[:byte].should == Rstruct::Byte
32
+ @registry[:int].should == Rstruct::Int
33
+ @registry.inherits.should == [Rstruct.default_registry]
34
+ end
35
+
36
+ it "should inherit types from arbitrary registries" do
37
+ begin
38
+ @registry.inherits.unshift(@preg)
39
+ c=Rstruct::Type.new(:some_parent_reg_type, :register => @preg)
40
+ @preg[:some_parent_reg_type].should == c
41
+ @registry[:some_parent_reg_type].should == c
42
+ Rstruct.default_registry[c].should be_nil
43
+ ensure
44
+ @registry.inherits.delete(@preg)
45
+ end
46
+
47
+ end
48
+
49
+ it "should register types only to its own registry" do
50
+ typ = :"test_parent_reg_#{@registry.name}_1"
51
+ obj=Class.new(Rstruct::Type)
52
+
53
+ @registry.register(obj,typ)
54
+ @registry[typ].should == obj
55
+ Rstruct.default_registry[typ].should be_nil
56
+ end
57
+
58
+ it_should_behave_like "a basic Rstruct registry"
59
+
60
+ end
61
+ end