bindata 0.5.0
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of bindata might be problematic. Click here for more details.
- data/COPYING +52 -0
- data/ChangeLog +9 -0
- data/GPL +339 -0
- data/INSTALL +7 -0
- data/README +215 -0
- data/TODO +14 -0
- data/examples/gzip.rb +174 -0
- data/lib/bindata.rb +13 -0
- data/lib/bindata/array.rb +160 -0
- data/lib/bindata/base.rb +260 -0
- data/lib/bindata/choice.rb +120 -0
- data/lib/bindata/int.rb +171 -0
- data/lib/bindata/lazy.rb +71 -0
- data/lib/bindata/registry.rb +37 -0
- data/lib/bindata/single.rb +170 -0
- data/lib/bindata/string.rb +98 -0
- data/lib/bindata/stringz.rb +83 -0
- data/lib/bindata/struct.rb +292 -0
- data/spec/array_spec.rb +121 -0
- data/spec/base_spec.rb +194 -0
- data/spec/choice_spec.rb +105 -0
- data/spec/int_spec.rb +141 -0
- data/spec/lazy_spec.rb +120 -0
- data/spec/registry_spec.rb +47 -0
- data/spec/single_spec.rb +210 -0
- data/spec/spec_common.rb +10 -0
- data/spec/string_spec.rb +205 -0
- data/spec/stringz_spec.rb +159 -0
- data/spec/struct_spec.rb +190 -0
- metadata +78 -0
@@ -0,0 +1,120 @@
|
|
1
|
+
require 'bindata/base'
|
2
|
+
|
3
|
+
module BinData
|
4
|
+
# A Choice is a collection of data objects of which only one is active
|
5
|
+
# at any particular time.
|
6
|
+
#
|
7
|
+
# require 'bindata'
|
8
|
+
# require 'stringio'
|
9
|
+
#
|
10
|
+
# choices = [ [:int8, {:value => 3}], [:int8, {:value => 5}] ]
|
11
|
+
# a = BinData::Choice.new(:choices => choices, :selection => 1)
|
12
|
+
# a.value # => 5
|
13
|
+
#
|
14
|
+
# == Parameters
|
15
|
+
#
|
16
|
+
# Parameters may be provided at initialisation to control the behaviour of
|
17
|
+
# an object. These params are:
|
18
|
+
#
|
19
|
+
# <tt>:choices</tt>:: An array specifying the possible data objects.
|
20
|
+
# The format of the array is a list of symbols
|
21
|
+
# representing the data object type. If a choice
|
22
|
+
# is to have params passed to it, then it should be
|
23
|
+
# provided as [type_symbol, hash_params].
|
24
|
+
# <tt>:selection</tt>:: An index into the :choices array which specifies
|
25
|
+
# the currently active choice.
|
26
|
+
class Choice < Base
|
27
|
+
|
28
|
+
# Register this class
|
29
|
+
register(self.name, self)
|
30
|
+
|
31
|
+
# These are the parameters used by this class.
|
32
|
+
mandatory_parameters :choices, :selection
|
33
|
+
|
34
|
+
def initialize(params = {}, env = nil)
|
35
|
+
super(params, env)
|
36
|
+
|
37
|
+
# instantiate all choices
|
38
|
+
@choices = []
|
39
|
+
param(:choices).each do |choice_type, choice_params|
|
40
|
+
choice_params ||= {}
|
41
|
+
klass = self.class.lookup(choice_type)
|
42
|
+
if klass.nil?
|
43
|
+
raise TypeError, "unknown type '#{choice_type.id2name}' for #{self}"
|
44
|
+
end
|
45
|
+
@choices << klass.new(choice_params, create_env)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
# Resets the internal state to that of a newly created object.
|
50
|
+
def clear
|
51
|
+
the_choice.clear
|
52
|
+
end
|
53
|
+
|
54
|
+
# Returns if the selected data object is clear?.
|
55
|
+
def clear?
|
56
|
+
the_choice.clear?
|
57
|
+
end
|
58
|
+
|
59
|
+
# Reads the value of the selected data object from +io+.
|
60
|
+
def _do_read(io)
|
61
|
+
the_choice.do_read(io)
|
62
|
+
end
|
63
|
+
|
64
|
+
# To be called after calling #do_read.
|
65
|
+
def done_read
|
66
|
+
the_choice.done_read
|
67
|
+
end
|
68
|
+
|
69
|
+
# Writes the value of the selected data object to +io+.
|
70
|
+
def _write(io)
|
71
|
+
the_choice.write(io)
|
72
|
+
end
|
73
|
+
|
74
|
+
# Returns the number of bytes it will take to write the
|
75
|
+
# selected data object.
|
76
|
+
def _num_bytes(what)
|
77
|
+
the_choice.num_bytes(what)
|
78
|
+
end
|
79
|
+
|
80
|
+
# Returns a snapshot of the selected data object.
|
81
|
+
def snapshot
|
82
|
+
the_choice.snapshot
|
83
|
+
end
|
84
|
+
|
85
|
+
# Returns a list of the names of all fields of the selected data object.
|
86
|
+
def field_names
|
87
|
+
the_choice.field_names
|
88
|
+
end
|
89
|
+
|
90
|
+
# Returns the data object that stores values for +name+.
|
91
|
+
def find_obj_for_name(name)
|
92
|
+
field_names.include?(name) ? the_choice.find_obj_for_name(name) : nil
|
93
|
+
end
|
94
|
+
|
95
|
+
# Override to include selected data object.
|
96
|
+
def respond_to?(symbol, include_private = false)
|
97
|
+
super || the_choice.respond_to?(symbol, include_private)
|
98
|
+
end
|
99
|
+
|
100
|
+
def method_missing(symbol, *args)
|
101
|
+
if the_choice.respond_to?(symbol)
|
102
|
+
the_choice.__send__(symbol, *args)
|
103
|
+
else
|
104
|
+
super
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
#---------------
|
109
|
+
private
|
110
|
+
|
111
|
+
# Returns the selected data object.
|
112
|
+
def the_choice
|
113
|
+
index = eval_param(:selection)
|
114
|
+
if index < 0 or index >= @choices.length
|
115
|
+
raise IndexError, "selection #{index} is out of range"
|
116
|
+
end
|
117
|
+
@choices[index]
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
data/lib/bindata/int.rb
ADDED
@@ -0,0 +1,171 @@
|
|
1
|
+
require 'bindata/single'
|
2
|
+
|
3
|
+
module BinData
|
4
|
+
# Provides a number of classes that contain an integer. The integer
|
5
|
+
# is defined by endian, signedness and number of bytes.
|
6
|
+
|
7
|
+
module BaseUint #:nodoc: all
|
8
|
+
|
9
|
+
def value=(val)
|
10
|
+
super(clamp(val))
|
11
|
+
end
|
12
|
+
|
13
|
+
#---------------
|
14
|
+
private
|
15
|
+
|
16
|
+
def sensible_default
|
17
|
+
0
|
18
|
+
end
|
19
|
+
|
20
|
+
def val_to_str(val)
|
21
|
+
_val_to_str(clamp(val))
|
22
|
+
end
|
23
|
+
|
24
|
+
# Clamps +val+ to the range 0 .. max_val
|
25
|
+
def clamp(val)
|
26
|
+
v = val
|
27
|
+
nbytes = val_num_bytes(0)
|
28
|
+
min = 0
|
29
|
+
max = (1 << (nbytes * 8)) - 1
|
30
|
+
val = min if val < min
|
31
|
+
val = max if val > max
|
32
|
+
val
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
module BaseInt #:nodoc: all
|
37
|
+
def uint2int(val)
|
38
|
+
nbytes = val_num_bytes(0)
|
39
|
+
mask = (1 << (nbytes * 8 - 1)) - 1
|
40
|
+
msb = (val >> (nbytes * 8 - 1)) & 0x1
|
41
|
+
(msb == 1) ? -(((~val) & mask) + 1) : val & mask
|
42
|
+
end
|
43
|
+
|
44
|
+
def int2uint(val)
|
45
|
+
nbytes = val_num_bytes(0)
|
46
|
+
mask = (1 << (nbytes * 8)) - 1
|
47
|
+
val & mask
|
48
|
+
end
|
49
|
+
|
50
|
+
def value=(val)
|
51
|
+
super(clamp(val))
|
52
|
+
end
|
53
|
+
|
54
|
+
#---------------
|
55
|
+
private
|
56
|
+
|
57
|
+
def sensible_default
|
58
|
+
0
|
59
|
+
end
|
60
|
+
|
61
|
+
def val_to_str(val)
|
62
|
+
_val_to_str(int2uint(clamp(val)))
|
63
|
+
end
|
64
|
+
|
65
|
+
def read_val(io)
|
66
|
+
uint2int(_read_val(io))
|
67
|
+
end
|
68
|
+
|
69
|
+
# Clamps +val+ to the range min_val .. max_val, where min and max
|
70
|
+
# are the largest representable integers.
|
71
|
+
def clamp(val)
|
72
|
+
nbytes = val_num_bytes(0)
|
73
|
+
max = (1 << (nbytes * 8 - 1)) - 1
|
74
|
+
min = -(max + 1)
|
75
|
+
val = min if val < min
|
76
|
+
val = max if val > max
|
77
|
+
val
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
|
82
|
+
# Unsigned 1 byte integer.
|
83
|
+
class Uint8 < Single
|
84
|
+
include BaseUint
|
85
|
+
private
|
86
|
+
def val_num_bytes(val) 1 end
|
87
|
+
def read_val(io) readbytes(io,1)[0] end
|
88
|
+
def _val_to_str(val) val.chr end
|
89
|
+
end
|
90
|
+
|
91
|
+
# Unsigned 2 byte little endian integer.
|
92
|
+
class Uint16le < Single
|
93
|
+
include BaseUint
|
94
|
+
private
|
95
|
+
def val_num_bytes(val) 2 end
|
96
|
+
def read_val(io) readbytes(io,2).unpack("v")[0] end
|
97
|
+
def _val_to_str(val) [val].pack("v") end
|
98
|
+
end
|
99
|
+
|
100
|
+
# Unsigned 2 byte big endian integer.
|
101
|
+
class Uint16be < Single
|
102
|
+
include BaseUint
|
103
|
+
private
|
104
|
+
def val_num_bytes(val) 2 end
|
105
|
+
def read_val(io) readbytes(io,2).unpack("n")[0] end
|
106
|
+
def _val_to_str(val) [val].pack("n") end
|
107
|
+
end
|
108
|
+
|
109
|
+
# Unsigned 4 byte little endian integer.
|
110
|
+
class Uint32le < Single
|
111
|
+
include BaseUint
|
112
|
+
private
|
113
|
+
def val_num_bytes(val) 4 end
|
114
|
+
def read_val(io) readbytes(io,4).unpack("V")[0] end
|
115
|
+
def _val_to_str(val) [val].pack("V") end
|
116
|
+
end
|
117
|
+
|
118
|
+
# Unsigned 4 byte big endian integer.
|
119
|
+
class Uint32be < Single
|
120
|
+
include BaseUint
|
121
|
+
private
|
122
|
+
def val_num_bytes(val) 4 end
|
123
|
+
def read_val(io) readbytes(io,4).unpack("N")[0] end
|
124
|
+
def _val_to_str(val) [val].pack("N") end
|
125
|
+
end
|
126
|
+
|
127
|
+
# Signed 1 byte integer.
|
128
|
+
class Int8 < Single
|
129
|
+
include BaseInt
|
130
|
+
private
|
131
|
+
def val_num_bytes(val) 1 end
|
132
|
+
def _read_val(io) readbytes(io,1)[0] end
|
133
|
+
def _val_to_str(val) val.chr end
|
134
|
+
end
|
135
|
+
|
136
|
+
# Signed 2 byte little endian integer.
|
137
|
+
class Int16le < Single
|
138
|
+
include BaseInt
|
139
|
+
private
|
140
|
+
def val_num_bytes(val) 2 end
|
141
|
+
def _read_val(io) readbytes(io,2).unpack("v")[0] end
|
142
|
+
def _val_to_str(val) [val].pack("v") end
|
143
|
+
end
|
144
|
+
|
145
|
+
# Signed 2 byte big endian integer.
|
146
|
+
class Int16be < Single
|
147
|
+
include BaseInt
|
148
|
+
private
|
149
|
+
def val_num_bytes(val) 2 end
|
150
|
+
def _read_val(io) readbytes(io,2).unpack("n")[0] end
|
151
|
+
def _val_to_str(val) [val].pack("n") end
|
152
|
+
end
|
153
|
+
|
154
|
+
# Signed 4 byte little endian integer.
|
155
|
+
class Int32le < Single
|
156
|
+
include BaseInt
|
157
|
+
private
|
158
|
+
def val_num_bytes(val) 4 end
|
159
|
+
def _read_val(io) readbytes(io,4).unpack("V")[0] end
|
160
|
+
def _val_to_str(val) [val].pack("V") end
|
161
|
+
end
|
162
|
+
|
163
|
+
# Signed 4 byte big endian integer.
|
164
|
+
class Int32be < Single
|
165
|
+
include BaseInt
|
166
|
+
private
|
167
|
+
def val_num_bytes(val) 4 end
|
168
|
+
def _read_val(io) readbytes(io,4).unpack("N")[0] end
|
169
|
+
def _val_to_str(val) [val].pack("N") end
|
170
|
+
end
|
171
|
+
end
|
data/lib/bindata/lazy.rb
ADDED
@@ -0,0 +1,71 @@
|
|
1
|
+
module BinData
|
2
|
+
# The enviroment in which a lazily evaluated lamba is called. These lambdas
|
3
|
+
# are those that are passed to data objects as parameters. Each lambda
|
4
|
+
# has access to the following:
|
5
|
+
#
|
6
|
+
# parent:: the environment of the parent data object
|
7
|
+
# params:: any extra parameters that have been passed to the data object.
|
8
|
+
# The value of a parameter is either a lambda, a symbol or a
|
9
|
+
# literal value (such as a Fixnum).
|
10
|
+
# value:: the value of the data object if it is single
|
11
|
+
# index:: the index of the data object if it is in an array
|
12
|
+
# offset:: the current offset of the IO object when reading
|
13
|
+
#
|
14
|
+
# Unknown methods are resolved in the context of the parent data object,
|
15
|
+
# first as keys in the extra parameters, and secondly as methods in the
|
16
|
+
# parent data object. This makes the lambda easier to read as we just write
|
17
|
+
# <tt>field</tt> instead of <tt>obj.field</tt>.
|
18
|
+
class LazyEvalEnv
|
19
|
+
# Creates a new environment. +parent+ is the environment of the
|
20
|
+
# parent data object.
|
21
|
+
def initialize(parent = nil)
|
22
|
+
@parent = parent
|
23
|
+
end
|
24
|
+
attr_reader :parent
|
25
|
+
attr_accessor :data_object, :params, :index, :offset
|
26
|
+
|
27
|
+
# only accessible by another LazyEvalEnv
|
28
|
+
protected :data_object
|
29
|
+
|
30
|
+
# TODO: offset_of needs to be better thought out
|
31
|
+
def offset_of(sym)
|
32
|
+
if @parent and @parent.data_object and
|
33
|
+
@parent.data_object.respond_to?(:offset_of)
|
34
|
+
@parent.data_object.offset_of(sym)
|
35
|
+
else
|
36
|
+
nil
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
# Returns the value of the data object wrapped by this environment.
|
41
|
+
def value
|
42
|
+
@data_object.respond_to?(:value) ? @data_object.value : nil
|
43
|
+
end
|
44
|
+
|
45
|
+
# Evaluates +obj+ in the context of this environment. Evaluation
|
46
|
+
# recurses until it yields a value that is not a symbol or lambda.
|
47
|
+
def lazy_eval(obj)
|
48
|
+
if obj.is_a? Symbol
|
49
|
+
# treat :foo as lambda { foo }
|
50
|
+
lazy_eval(__send__(obj))
|
51
|
+
elsif obj.respond_to? :arity
|
52
|
+
instance_eval(&obj)
|
53
|
+
else
|
54
|
+
obj
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def method_missing(symbol, *args)
|
59
|
+
if @parent and @parent.params and @parent.params.has_key?(symbol)
|
60
|
+
# is there a param with this name?
|
61
|
+
@parent.lazy_eval(@parent.params[symbol])
|
62
|
+
elsif @parent and @parent.data_object and
|
63
|
+
@parent.data_object.respond_to?(symbol)
|
64
|
+
# how about a field or method in the parent?
|
65
|
+
@parent.data_object.__send__(symbol, *args)
|
66
|
+
else
|
67
|
+
super
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
require 'singleton'
|
2
|
+
|
3
|
+
module BinData
|
4
|
+
# This registry contains a register of name -> class mappings.
|
5
|
+
class Registry
|
6
|
+
include Singleton
|
7
|
+
|
8
|
+
def initialize
|
9
|
+
@registry = {}
|
10
|
+
end
|
11
|
+
|
12
|
+
# Registers the mapping of +name+ to +klass+. +name+ is converted
|
13
|
+
# from camelCase to underscore style.
|
14
|
+
# Returns the converted name
|
15
|
+
def register(name, klass)
|
16
|
+
# convert camelCase name to underscore style
|
17
|
+
underscore_name = name.to_s.sub(/.*::/, "").
|
18
|
+
gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2').
|
19
|
+
gsub(/([a-z\d])([A-Z])/,'\1_\2').
|
20
|
+
tr("-", "_").
|
21
|
+
downcase
|
22
|
+
|
23
|
+
# warn if replacing an existing class
|
24
|
+
if $VERBOSE and (existing = @registry[underscore_name])
|
25
|
+
warn "warning: replacing registered class #{existing} with #{klass}"
|
26
|
+
end
|
27
|
+
|
28
|
+
@registry[underscore_name] = klass
|
29
|
+
underscore_name.dup
|
30
|
+
end
|
31
|
+
|
32
|
+
# Returns the class matching a previously registered +name+.
|
33
|
+
def lookup(name)
|
34
|
+
@registry[name.to_s]
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,170 @@
|
|
1
|
+
require 'bindata/base'
|
2
|
+
|
3
|
+
module BinData
|
4
|
+
# A BinData::Single object is a container for a value that has a particular
|
5
|
+
# binary representation. A value corresponds to a primitive type such as
|
6
|
+
# as integer, float or string. Only one value can be contained by this
|
7
|
+
# object. This value can be read from or written to an IO stream.
|
8
|
+
#
|
9
|
+
# == Parameters
|
10
|
+
#
|
11
|
+
# Parameters may be provided at initialisation to control the behaviour of
|
12
|
+
# an object. These params include those for BinData::Base as well as:
|
13
|
+
#
|
14
|
+
# [<tt>:initial_value</tt>] This is the initial value to use before one is
|
15
|
+
# either #read or explicitly set with #value=.
|
16
|
+
# [<tt>:value</tt>] The object will always have this value.
|
17
|
+
# Explicitly calling #value= is prohibited when
|
18
|
+
# using this param. In the interval between
|
19
|
+
# calls to #do_read and #done_read, #value
|
20
|
+
# will return the value of the data read from the
|
21
|
+
# IO, not the result of the <tt>:value</tt> param.
|
22
|
+
# [<tt>:check_value</tt>] Raise an error unless the value read in meets
|
23
|
+
# this criteria. A boolean return indicates
|
24
|
+
# success or failure. Any other return is compared
|
25
|
+
# to the value just read in.
|
26
|
+
class Single < Base
|
27
|
+
# These are the parameters used by this class.
|
28
|
+
optional_parameters :initial_value, :value, :check_value
|
29
|
+
|
30
|
+
# Register the names of all subclasses of this class.
|
31
|
+
def self.inherited(subclass) #:nodoc:
|
32
|
+
register(subclass.name, subclass)
|
33
|
+
end
|
34
|
+
|
35
|
+
def initialize(params = {}, env = nil)
|
36
|
+
super(params, env)
|
37
|
+
ensure_mutual_exclusion(:initial_value, :value)
|
38
|
+
clear
|
39
|
+
end
|
40
|
+
|
41
|
+
# Resets the internal state to that of a newly created object.
|
42
|
+
def clear
|
43
|
+
@value = nil
|
44
|
+
@in_read = false
|
45
|
+
end
|
46
|
+
|
47
|
+
# Returns if the value of this data has been read or explicitly set.
|
48
|
+
def clear?
|
49
|
+
@value.nil?
|
50
|
+
end
|
51
|
+
|
52
|
+
# Reads the value for this data from +io+.
|
53
|
+
def _do_read(io)
|
54
|
+
@in_read = true
|
55
|
+
@value = read_val(io)
|
56
|
+
|
57
|
+
# does the value meet expectations?
|
58
|
+
if has_param?(:check_value)
|
59
|
+
expected = eval_param(:check_value)
|
60
|
+
if not expected
|
61
|
+
raise ValidityError, "value not as expected"
|
62
|
+
elsif @value != expected and expected != true
|
63
|
+
raise ValidityError, "value is '#{@value}' but " +
|
64
|
+
"expected '#{expected}'"
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
# To be called after calling #do_read.
|
70
|
+
def done_read
|
71
|
+
@in_read = false
|
72
|
+
end
|
73
|
+
|
74
|
+
# Writes the value for this data to +io+.
|
75
|
+
def _write(io)
|
76
|
+
raise "can't write whilst reading" if @in_read
|
77
|
+
io.write(val_to_str(_value))
|
78
|
+
end
|
79
|
+
|
80
|
+
# Returns the number of bytes it will take to write this data.
|
81
|
+
def _num_bytes(ignored)
|
82
|
+
val_to_str(_value).length
|
83
|
+
end
|
84
|
+
|
85
|
+
# Returns a snapshot of this data object.
|
86
|
+
def snapshot
|
87
|
+
value
|
88
|
+
end
|
89
|
+
|
90
|
+
# Single objects don't contain fields so this returns an empty list.
|
91
|
+
def field_names
|
92
|
+
[]
|
93
|
+
end
|
94
|
+
|
95
|
+
# Returns the current value of this data.
|
96
|
+
def value
|
97
|
+
_value
|
98
|
+
end
|
99
|
+
|
100
|
+
# Sets the value of this data.
|
101
|
+
def value=(v)
|
102
|
+
# only allow modification if the value isn't predefined
|
103
|
+
unless has_param?(:value)
|
104
|
+
raise ArgumentError, "can't set a nil value" if v.nil?
|
105
|
+
@value = v
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
#---------------
|
110
|
+
private
|
111
|
+
|
112
|
+
# The unmodified value of this data object. Note that #value calls this
|
113
|
+
# method. This is so that #value can be overridden in subclasses to
|
114
|
+
# modify the value.
|
115
|
+
def _value
|
116
|
+
# Table of possible preconditions and expected outcome
|
117
|
+
# 1. :value and !in_read -> :value
|
118
|
+
# 2. :value and in_read -> @value
|
119
|
+
# 3. :initial_value and clear? -> :initial_value
|
120
|
+
# 4. :initial_value and !clear? -> @value
|
121
|
+
# 5. clear? -> sensible_default
|
122
|
+
# 6. !clear? -> @value
|
123
|
+
|
124
|
+
if not @in_read and (evaluated_value = eval_param(:value))
|
125
|
+
# rule 1 above
|
126
|
+
evaluated_value
|
127
|
+
else
|
128
|
+
# combining all other rules gives this simplified expression
|
129
|
+
@value || eval_param(:value) ||
|
130
|
+
eval_param(:initial_value) || sensible_default()
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
# Usuable by subclasses
|
135
|
+
|
136
|
+
# Reads exactly +n+ bytes from +io+. This should be used by subclasses
|
137
|
+
# in preference to <tt>io.read(n)</tt>.
|
138
|
+
#
|
139
|
+
# If the data read is nil an EOFError is raised.
|
140
|
+
#
|
141
|
+
# If the data read is too short an IOError is raised.
|
142
|
+
def readbytes(io, n)
|
143
|
+
str = io.read(n)
|
144
|
+
raise EOFError, "End of file reached" if str == nil
|
145
|
+
raise IOError, "data truncated" if str.size < n
|
146
|
+
str
|
147
|
+
end
|
148
|
+
|
149
|
+
=begin
|
150
|
+
# To be implemented by subclasses
|
151
|
+
|
152
|
+
# Return the string representation that +val+ will take when written.
|
153
|
+
def val_to_str(val)
|
154
|
+
raise NotImplementedError
|
155
|
+
end
|
156
|
+
|
157
|
+
# Read a number of bytes from +io+ and return the value they represent.
|
158
|
+
def read_val(io)
|
159
|
+
raise NotImplementedError
|
160
|
+
end
|
161
|
+
|
162
|
+
# Return a sensible default for this data.
|
163
|
+
def sensible_default
|
164
|
+
raise NotImplementedError
|
165
|
+
end
|
166
|
+
|
167
|
+
# To be implemented by subclasses
|
168
|
+
=end
|
169
|
+
end
|
170
|
+
end
|