rstruct 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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